feat(cli): add deploy-adapter command for user-controlled LoRA deployment#191
feat(cli): add deploy-adapter command for user-controlled LoRA deployment#191
Conversation
- Dataset upload: try 0G Storage first, fallback to TEE on failure - Model download: default 'auto' mode tries 0G Storage first, TEE fallback - Upload function now returns root hash from 0g-storage-client output - Add 'auto' download method (default) alongside 'tee' and '0g-storage' - Update 0g-storage-client binary to latest main (fixes contract ABI) Co-authored-by: Cursor <cursoragent@cursor.com>
…sh verification The method was missing after cherry-pick, causing TypeScript build failure. This method verifies downloaded model hash against on-chain modelRootHash when using TEE fallback download. Co-authored-by: Cursor <cursoragent@cursor.com>
When data-path points to an existing directory, 0G Storage client fails
with "is a directory" error. Now auto-appends a filename (model_{taskId}.bin)
when the path is a directory, so 0G Storage client can create the output file.
Co-authored-by: Cursor <cursoragent@cursor.com>
- [HIGH] Reject if root hash is empty after successful upload in zg-storage - [MEDIUM] Replace console.log/error with logger in SDK code (dataset.ts, zg-storage.ts) - [LOW] Fix JSDoc: default downloadMethod is 'auto', not '0g-storage' - [LOW] Improve CLI --download-method option description formatting Co-authored-by: Cursor <cursoragent@cursor.com>
- Add makeAdapterName utility for consistent LoRA adapter naming - Add `fine-tuning get-adapter-name` command - Add `fine-tuning chat` command with --adapter-name option - Add `inference get-secret --duration` for non-interactive API key generation These commands enable CLI-driven E2E testing of the LoRA serving flow. Made-with: Cursor
…ge-model New command `fine-tuning deploy-adapter` triggers adapter deployment to the inference GPU via the broker's new /v1/lora/adapters/deploy API. The `acknowledge-model` command now accepts `--deploy` to automatically wait for the broker to download the adapter and deploy it in one step. Without --deploy, acknowledge only downloads the model; users must call deploy-adapter separately when they want to go live.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
- Fix deploy-adapter to resolve adapter name from broker API by taskId (handles base model name mismatch between CLI and broker internal paths) - Add --broker-url option to deploy-adapter for direct broker targeting (bypasses contract lookup in dev/2-CVM setups) - Fix malformed .option() syntax in model-usage and download commands (template literal used incorrectly as first argument to Commander) Tested: full E2E flow with real fine-tuning, deploy, and inference on 2-CVM testnet setup.
In 2-CVM setups where the fine-tuning provider differs from the inference provider, the --deploy flag on acknowledge-model failed because it tried to look up the inference broker URL using the fine-tuning provider address. Adding --broker-url allows users to specify the inference broker URL directly.
- chat command now queries broker by taskId to get the real adapter name, fixing 400 errors when broker uses path-based model names - deploy-adapter polling loop re-resolves adapter name dynamically, fixing timeout when called right after acknowledge (before broker processes the on-chain event)
abd3dcc to
9d3e8ad
Compare
…-flow-v2 # Conflicts: # cli.commonjs/cli/fine-tuning.d.ts.map # cli.commonjs/cli/fine-tuning.js # cli.commonjs/cli/fine-tuning.js.map # cli.commonjs/cli/inference.d.ts.map # cli.commonjs/cli/inference.js # cli.commonjs/cli/inference.js.map # cli.commonjs/cli/ledger.d.ts.map # cli.commonjs/cli/util.d.ts.map # cli.commonjs/sdk/common/utils/index.d.ts # cli.commonjs/sdk/common/utils/index.d.ts.map # cli.commonjs/sdk/common/utils/index.js # cli.commonjs/sdk/common/utils/index.js.map # cli.commonjs/sdk/fine-tuning/broker/broker.d.ts # cli.commonjs/sdk/fine-tuning/broker/broker.d.ts.map # cli.commonjs/sdk/fine-tuning/broker/broker.js # cli.commonjs/sdk/fine-tuning/broker/broker.js.map # cli.commonjs/sdk/fine-tuning/broker/dataset.d.ts # cli.commonjs/sdk/fine-tuning/broker/dataset.d.ts.map # cli.commonjs/sdk/fine-tuning/broker/dataset.js # cli.commonjs/sdk/fine-tuning/broker/dataset.js.map # cli.commonjs/sdk/fine-tuning/broker/model.d.ts # cli.commonjs/sdk/fine-tuning/broker/model.d.ts.map # cli.commonjs/sdk/fine-tuning/broker/model.js # cli.commonjs/sdk/fine-tuning/broker/model.js.map # cli.commonjs/sdk/fine-tuning/zg-storage/zg-storage.d.ts # cli.commonjs/sdk/fine-tuning/zg-storage/zg-storage.d.ts.map # cli.commonjs/sdk/fine-tuning/zg-storage/zg-storage.js # cli.commonjs/sdk/fine-tuning/zg-storage/zg-storage.js.map # cli.commonjs/sdk/inference/broker/broker.d.ts.map # cli.commonjs/sdk/ledger/broker.d.ts.map # lib.commonjs/common/utils/index.d.ts # lib.commonjs/common/utils/index.d.ts.map # lib.commonjs/common/utils/index.js # lib.commonjs/common/utils/index.js.map # lib.commonjs/fine-tuning/broker/broker.d.ts # lib.commonjs/fine-tuning/broker/broker.d.ts.map # lib.commonjs/fine-tuning/broker/broker.js # lib.commonjs/fine-tuning/broker/broker.js.map # lib.commonjs/fine-tuning/broker/dataset.d.ts # lib.commonjs/fine-tuning/broker/dataset.d.ts.map # lib.commonjs/fine-tuning/broker/dataset.js # lib.commonjs/fine-tuning/broker/dataset.js.map # lib.commonjs/fine-tuning/broker/model.d.ts # lib.commonjs/fine-tuning/broker/model.d.ts.map # lib.commonjs/fine-tuning/broker/model.js # lib.commonjs/fine-tuning/broker/model.js.map # lib.commonjs/fine-tuning/zg-storage/zg-storage.d.ts # lib.commonjs/fine-tuning/zg-storage/zg-storage.d.ts.map # lib.commonjs/fine-tuning/zg-storage/zg-storage.js # lib.commonjs/fine-tuning/zg-storage/zg-storage.js.map # lib.commonjs/inference/broker/broker.d.ts.map # lib.commonjs/ledger/broker.d.ts.map # lib.esm/_commonjsHelpers-0f3c985d.js # lib.esm/adm-zip-0609067e.js # lib.esm/adm-zip-0609067e.js.map # lib.esm/browser-0fd4d439.js # lib.esm/browser-0fd4d439.js.map # lib.esm/common/utils/index.d.ts # lib.esm/common/utils/index.d.ts.map # lib.esm/fine-tuning/broker/broker.d.ts # lib.esm/fine-tuning/broker/broker.d.ts.map # lib.esm/fine-tuning/broker/dataset.d.ts # lib.esm/fine-tuning/broker/dataset.d.ts.map # lib.esm/fine-tuning/broker/model.d.ts # lib.esm/fine-tuning/broker/model.d.ts.map # lib.esm/fine-tuning/zg-storage/zg-storage.d.ts # lib.esm/fine-tuning/zg-storage/zg-storage.d.ts.map # lib.esm/index-291e4713.js # lib.esm/index.d.ts # lib.esm/index.mjs # lib.esm/inference/broker/broker.d.ts.map # lib.esm/ledger/broker.d.ts.map # src.ts/cli/fine-tuning.ts # src.ts/sdk/fine-tuning/broker/dataset.ts # src.ts/sdk/fine-tuning/broker/model.ts # src.ts/sdk/fine-tuning/zg-storage/zg-storage.ts # types/common/utils/index.d.ts # types/common/utils/index.d.ts.map # types/fine-tuning/broker/broker.d.ts # types/fine-tuning/broker/broker.d.ts.map # types/fine-tuning/broker/dataset.d.ts # types/fine-tuning/broker/dataset.d.ts.map # types/fine-tuning/broker/model.d.ts # types/fine-tuning/broker/model.d.ts.map # types/fine-tuning/zg-storage/zg-storage.d.ts # types/fine-tuning/zg-storage/zg-storage.d.ts.map # types/inference/broker/broker.d.ts.map # types/ledger/broker.d.ts.map
These commands need getServiceMetadata/getRequestHeaders which are not available on ReadOnlyInferenceBroker.
|
@claude Review PR |
|
Claude finished @HONGJICAI's task in 2m 51s —— View job Code Review Complete ✅Reviewed PR #191: SummaryThis PR adds new CLI commands for deploying fine-tuned LoRA adapters to inference GPUs. While the feature is useful, there are significant architecture violations that need to be addressed before merging.
|
|
@HONGJICAI Thanks for triggering the review. Here's my response to the Claude bot's findings: Addressed / Will Fix[HIGH] Business logic in CLI (deploy, chat, status polling) [MEDIUM] [MEDIUM] Code duplication (adapter name resolution 3x) [LOW] Magic numbers (poll intervals, timeouts) ContextThis PR was initially built as a quick CLI prototype to validate the E2E flow (broker → 0G Storage → vLLM → LoRA serving). Now that the flow is validated end-to-end (see test history), the refactor to proper SDK architecture makes sense as a natural next step. Plan
Will push the refactored version shortly. |
- New `LoRAProcessor` class in `src.ts/sdk/inference/broker/lora.ts` with typed interfaces (AdapterInfo, AdapterStatusResponse, etc.) - New SDK methods on InferenceBroker: deployAdapter(), getAdapterStatus(), listAdapters(), resolveAdapterName(), chatWithFineTunedModel() - CLI deploy-adapter and chat commands are now thin wrappers calling SDK - Eliminated 3x duplicated adapter name resolution logic - Replaced all `any` types with proper TypeScript interfaces - Extracted magic numbers to named constants Addresses code review feedback from Claude bot on PR #191.
Update: All review items addressed ✅All issues from the Claude bot review have been fixed and pushed in commit [HIGH] Business logic moved from CLI to SDKCreated new
New public methods on CLI is now a thin wrapper:
[MEDIUM]
|
|
@claude review this pr |
|
Claude finished @zeyuyuyu's task in 5m 13s —— View job PR Review Complete ✅Reviewed PR #191: Executive SummaryExcellent refactoring work! 🎉 The author has successfully addressed all major issues from the previous review. The business logic has been properly moved from CLI to SDK, TypeScript types are well-defined, code duplication is eliminated, and the architecture now correctly follows CLAUDE.md standards. Changes Summary
Detailed Review✅ Architecture Compliance - EXCELLENTPrevious Issue: 360+ lines of business logic in CLI The refactoring successfully separates concerns:
This is exactly what CLAUDE.md requires:
✅ Perfect implementation! ✅ TypeScript Type Safety - GREATLY IMPROVEDPrevious Issue: 5+ instances of New Interfaces (lines 8-64 in type AdapterState = 'init' | 'pending' | 'downloading' | 'ready' | 'active' | ...
interface AdapterInfo { adapterName, taskId, baseModel, userAddress, state, ... }
interface AdapterStatusResponse { adapterName, state, error? }
interface DeployResponse { message, adapterName? }
interface ChatResponse { id, model, choices, usage? }
interface DeployAdapterOptions { wait?, timeoutSeconds?, brokerUrlOverride?, onProgress? }
interface ChatOptions { systemPrompt? }✅ Excellent type definitions! All interfaces are well-structured and comprehensive. Remaining Minor Issue 🔍: Location: async function deployAdapterToBroker(
broker: { inference: { deployAdapter: Function; resolveAdapterName: Function } },
// ^^ Should use proper type instead of inline type with Function
...
)Suggestion: Use proper type: import type { ZGComputeNetworkBroker } from '../sdk'
async function deployAdapterToBroker(
broker: ZGComputeNetworkBroker,
providerAddress: string,
baseModel: string,
taskId: string,
wait: boolean,
timeoutSeconds: number,
brokerUrlOverride?: string
)Impact: Low - this is internal CLI code, but for consistency should use proper types. ✅ Code Duplication - ELIMINATEDPrevious Issue: Adapter name resolution logic duplicated 3+ times Solution:
✅ Perfect DRY principle application! ✅ Magic Numbers - EXTRACTEDPrevious Issue: Hardcoded intervals and timeouts Constants ( const ADAPTER_POLL_INTERVAL_MS = 3000
const DEPLOY_POLL_INTERVAL_MS = 2000
const DEFAULT_DEPLOY_TIMEOUT_SECONDS = 120✅ Good practice! Makes the code more maintainable. Code Quality Review✅ Good Practices Observed
🔍 Minor Issues & Suggestions[LOW] Type Annotation Could Be More SpecificLocation: this.getHeaders = getHeaders
// ^^ Returns Record<string, string> but is actually used as request headersSuggestion: Consider a more specific type: type RequestHeaders = Record<string, string> & {
Authorization?: string
'Content-Type'?: string
// Add other expected headers
}Impact: Very low - current type is acceptable. [LOW] Potential Race Condition in Name ResolutionLocation: Observation: During the wait loop, adapter name resolution is attempted multiple times: while (Date.now() < deadline) {
if (!nameResolved) {
try {
const resolved = await this.resolveAdapterName(...)
if (resolved !== localName) {
adapterName = resolved
nameResolved = true
}
} catch { }
}
// ... status check uses adapterName
}Potential Issue: If broker assigns a different name than local guess, there's a window where status checks might use the wrong name. Current Mitigation: The code correctly retries name resolution until it succeeds. ✅ Suggestion: Consider adding a log message when name is resolved: if (resolved !== localName) {
adapterName = resolved
nameResolved = true
onProgress?.(`Adapter name resolved: ${adapterName}`)
}Impact: Very low - current logic is sound, suggestion is just for better observability. [LOW/nit] Error Message Could Be More HelpfulLocation: throw new Error('Adapter deployment failed.')Suggestion: Include more context: throw new Error(`Adapter deployment failed for ${adapterName}. Check broker logs for details.`)Impact: Low - improves debugging experience. [LOW/nit] Comment Could Clarify Adapter Name Resolution LogicLocation: The /**
* Resolves the actual adapter name from the broker.
*
* The broker may use a different base model path than the local naming convention,
* so we query the broker's adapter list to find the correct name by task ID.
* Falls back to locally generated name if broker query fails.
*
* @param providerAddress - Provider address
* @param taskId - Fine-tuning task ID
* @param baseModel - Base model name (for local fallback)
* @param brokerUrlOverride - Optional broker URL override
* @returns Adapter name (broker's name if available, otherwise local guess)
*/
async resolveAdapterName(...) { ... }Impact: Very low - improves maintainability. Security Review✅ No Security Issues Found
Breaking Changes Review✅ No Breaking ChangesPublic API Changes:
CLI Changes:
No version bump required - these are pure additions. Testing ConsiderationsRecommended Test CoverageFrom the author's update comment:
✅ Good manual testing! Additional Recommendations:
Note: Adding tests is not blocking for merge (feature is E2E verified), but recommended for future robustness. Documentation Review✅ CLI Help Text is Gooddeploy-adapter: 'Deploy a downloaded LoRA adapter to the inference GPU (triggers vLLM loading)'chat: 'Send a chat request to a fine-tuned model via the inference broker'✅ Clear and descriptive! 📝 Suggestion: Add SDK DocumentationLocation: The new public methods have minimal JSDoc. Consider adding comprehensive documentation: /**
* Deploys a fine-tuned LoRA adapter to the inference GPU.
*
* After acknowledging a fine-tuned model, you must deploy it to the inference
* GPU before it can be used for inference. This method triggers the deployment
* and optionally waits for it to complete.
*
* @param {string} providerAddress - Address of the inference provider
* @param {string} baseModel - Base model name used in fine-tuning (e.g., "Qwen2.5-0.5B-Instruct")
* @param {string} taskId - Fine-tuning task ID
* @param {DeployAdapterOptions} options - Deployment options
* @param {boolean} options.wait - If true, waits until adapter is active (default: false)
* @param {number} options.timeoutSeconds - Timeout when waiting (default: 120)
* @param {string} options.brokerUrlOverride - Override broker URL (skip contract lookup)
* @param {function} options.onProgress - Callback for progress updates
*
* @returns {Promise<DeployResponse>} Deployment result with message and adapter name
*
* @example
* ```typescript
* // Deploy without waiting
* const result = await broker.inference.deployAdapter(
* providerAddress,
* 'Qwen2.5-0.5B-Instruct',
* taskId
* );
*
* // Deploy and wait for completion
* const result = await broker.inference.deployAdapter(
* providerAddress,
* 'Qwen2.5-0.5B-Instruct',
* taskId,
* {
* wait: true,
* timeoutSeconds: 180,
* onProgress: (state) => console.log(`State: ${state}`)
* }
* );
* ```
*
* @throws {Error} If deployment times out or fails
*/
public deployAdapter = async (...) => { ... }Impact: Medium - improves developer experience for SDK users. Additional Observations✅ Good: Consistent Naming ConventionThe /**
* Build a deterministic LoRA adapter name from base model and task ID.
* Must match the broker-side naming in lora/manager.go MakeAdapterName().
*/✅ Excellent! Ensures client-side and broker-side names match. ✅ Good: Proper CLI Option ValidationLocation: if (options.deploy && !options.model) {
console.error(chalk.red('Error: --model is required when using --deploy'))
process.exit(1)
}✅ Good UX! Clear error message when required option is missing. ✅ Good: Mutual Exclusion ValidationLocation: if (options.adapterName) {
adapterName = options.adapterName
} else if (options.model && options.taskId) {
adapterName = makeAdapterName(options.model, options.taskId)
} else {
console.error(chalk.red('Error: Provide either --adapter-name or both --model and --task-id'))
process.exit(1)
}✅ Excellent validation! Handles both use cases clearly. ✅ Good: Small Change to
|
| Aspect | Previous Review | This Review |
|---|---|---|
| Architecture | ❌ Business logic in CLI | ✅ Properly separated |
| Code Lines | 360+ in CLI | 33 in CLI, 302 in SDK |
| Type Safety | ❌ 5+ any types |
✅ 1 minor issue |
| Duplication | ❌ 3x adapter resolution | ✅ Single method |
| Constants | ❌ Magic numbers | ✅ Named constants |
| Reusability | ❌ CLI-only | ✅ SDK can be used by Web UI |
| Testability | ❌ Difficult | ✅ Easy (dependency injection) |
| Maintainability | ❌ Low | ✅ High |
Result: 🎉 Excellent improvement! The refactoring is a textbook example of how to properly separate concerns in a TypeScript project.
Acknowledgment
@zeyuyuyu - Outstanding work on this refactoring! You've taken the feedback from the previous review and executed a professional, comprehensive solution that:
- ✅ Addresses every issue raised
- ✅ Follows architecture best practices
- ✅ Maintains backward compatibility
- ✅ Improves code quality significantly
- ✅ Sets a good pattern for future features
This is exactly how SDK/CLI separation should be done. The LoRAProcessor is well-designed with proper dependency injection, making it testable and reusable. The CLI is now a true thin wrapper. Well done! 👏
Verdict: ✅ APPROVED - Ready to merge with optional minor improvements suggested above.
|
@claude review this pr |
|
Claude finished @zeyuyuyu's task in 4m 47s —— View job Code Review Complete ✅Reviewed PR #191 follow-up: All major issues from previous review have been successfully addressed! 🎉 SummaryThe refactoring is excellent and addresses all critical architecture violations from the previous review. The business logic has been properly moved to the SDK, TypeScript types are clean, and the CLI is now a thin wrapper as intended. This is a significant improvement and aligns well with the project's architecture standards. Major Improvements ✅[HIGH] Business Logic Successfully Moved to SDK ✅Previously: 360+ lines of business logic in CLI Files:
New Public API Methods on deployAdapter(providerAddress, baseModel, taskId, options?)
getAdapterStatus(providerAddress, adapterName, brokerUrlOverride?)
listAdapters(providerAddress, brokerUrlOverride?)
resolveAdapterName(providerAddress, taskId, baseModel, brokerUrlOverride?)
chatWithFineTunedModel(providerAddress, adapterName, message, options?)Architecture now follows CLAUDE.md principles:
[MEDIUM] TypeScript Types Properly Defined ✅Previously: Multiple New interfaces (all properly typed): AdapterState: Union type with all possible states
AdapterInfo: Complete adapter metadata
AdapterStatusResponse: Status check response
DeployResponse: Deployment result
ChatResponse: OpenAI-compatible chat response
DeployAdapterOptions: Deployment configuration
ChatOptions: Chat configurationRemaining [MEDIUM] Code Duplication Eliminated ✅Previously: Adapter name resolution appeared 3+ times
[LOW] Magic Numbers Extracted ✅Previously: Hardcoded timeouts and intervals throughout const ADAPTER_POLL_INTERVAL_MS = 3000
const DEPLOY_POLL_INTERVAL_MS = 2000
const DEFAULT_DEPLOY_TIMEOUT_SECONDS = 120[MEDIUM]
|
| this.modelProcessor = modelProcessor | ||
|
|
||
| this.loraProcessor = new LoRAProcessor( | ||
| async (providerAddress: string) => { |
There was a problem hiding this comment.
@claude Is it better we have the requestProcessor as LoRAProcessor's dependency instead of passing a func?
There was a problem hiding this comment.
Claude finished @HONGJICAI's task in 2m 27s —— View job
Architecture Discussion: Dependency Injection Pattern
Great question! Let me analyze both approaches:
Current Approach: Function Injection
this.loraProcessor = new LoRAProcessor(
async (providerAddress: string) => { /* getEndpoint */ },
async (providerAddress: string, content?: string) => { /* getHeaders */ }
)Alternative: Object Injection
this.loraProcessor = new LoRAProcessor(this.requestProcessor)
// Then LoRAProcessor would call requestProcessor.getServiceMetadata() directlyAnalysis
I recommend keeping the current function injection approach, but I understand why it might look overly verbose. Here's my reasoning:
✅ Why Current Approach (Function Injection) is Better
-
Loose Coupling
LoRAProcessordoesn't depend on theRequestProcessorclass- It only depends on the specific functionality it needs (endpoint + headers)
- Follows the Dependency Inversion Principle (depend on abstractions, not concrete classes)
-
Testability
- Easy to mock - just pass simple functions in tests:
const mockProcessor = new LoRAProcessor( async () => 'http://test-endpoint', async () => ({ Authorization: 'test-token' }) )
- No need to mock an entire
RequestProcessorobject with all its methods
- Easy to mock - just pass simple functions in tests:
-
Interface Segregation Principle (ISP)
LoRAProcessoronly gets access to what it needs- If you pass the whole
requestProcessor, LoRAProcessor could call methods it shouldn't (likegetService(),topUpAccountIfNeeded(), etc.)
-
Flexibility
- Easy to substitute different implementations
- Not locked into
RequestProcessorclass structure
-
Architectural Consistency
- Looking at the codebase,
LoRAProcessoris intentionally different from other processors - Other processors (
RequestProcessor,ResponseProcessor, etc.) all extendZGServingUserBrokerBaseand needcontract,ledger,metadata,cache - LoRAProcessor doesn't need blockchain/storage access - it only needs HTTP endpoint and auth headers
- This keeps it focused on LoRA deployment logic, not blockchain logic
- Looking at the codebase,
⚠️ Issues with Object Injection
If you pass the whole requestProcessor:
class LoRAProcessor {
constructor(private requestProcessor: RequestProcessor) {}
async deploy(...) {
const meta = await this.requestProcessor.getServiceMetadata(...)
// Now LoRAProcessor can also call:
// - this.requestProcessor.getService()
// - this.requestProcessor.topUpAccountIfNeeded()
// - etc.
// Should it have access to all of these? Probably not.
}
}Problems:
- Tight coupling to
RequestProcessorclass - Harder to test - need to mock entire
RequestProcessorwith all its dependencies - Violates ISP - LoRAProcessor gets access to methods it doesn't need
- Less flexible - can't easily substitute a different implementation
🔍 However, I Notice One Issue
Line 117 has an awkward type cast:
return headers as unknown as Record<string, string>This suggests the types aren't aligned properly. The RequestProcessor.getRequestHeaders() returns ServingRequestHeaders, but LoRAProcessor expects Record<string, string>.
This should be fixed - either:
- Change
LoRAProcessorto acceptServingRequestHeaderstype, or - Change
getRequestHeaders()to returnRecord<string, string>, or - Create a proper conversion function instead of type assertion
💡 Alternative: Interface-Based Injection (Middle Ground)
If you want cleaner code while maintaining loose coupling, consider an interface:
interface LoRADependencies {
getEndpoint(providerAddress: string): Promise<string>
getHeaders(providerAddress: string, content?: string): Promise<Record<string, string>>
}
class LoRAProcessor {
constructor(private deps: LoRADependencies) {}
async deploy(...) {
const endpoint = await this.deps.getEndpoint(...)
const headers = await this.deps.getHeaders(...)
}
}
// In broker.ts:
this.loraProcessor = new LoRAProcessor({
getEndpoint: async (addr) => {
const meta = await this.requestProcessor.getServiceMetadata(addr)
return meta.endpoint
},
getHeaders: async (addr, content) => {
return await this.requestProcessor.getRequestHeaders(addr, content)
}
})Benefits:
- ✅ Cleaner, more organized code
- ✅ Still loosely coupled
- ✅ Easy to test (mock the interface)
- ✅ Type-safe
- ✅ Can add more methods to interface if needed
🎯 Recommendation
Keep the current function injection approach, as it:
- Maintains proper separation of concerns
- Follows SOLID principles (especially DIP and ISP)
- Makes testing easier
- Is consistent with LoRAProcessor's design (it doesn't need blockchain access)
But fix the type cast on line 117 - that's a code smell that should be addressed.
Optional enhancement: If you find the instantiation code too verbose, consider the interface-based approach above as a cleaner middle ground.
Summary Table
| Approach | Coupling | Testability | SOLID Compliance | Code Cleanliness |
|---|---|---|---|---|
| Current (functions) ✅ | Loose | Excellent | ✅ DIP, ISP | Verbose but clear |
| Object injection | Tight | Poor | ❌ Violates DIP, ISP | Clean but wrong |
| Interface-based | Loose | Excellent | ✅ DIP, ISP | Clean and right |
Verdict: Current approach is architecturally sound. The verbosity is a small price to pay for proper decoupling and testability. The interface-based approach would be an improvement if you want cleaner code while maintaining good architecture.
Does this help clarify the design decision? Happy to discuss further if you'd like to explore the interface-based approach or have other concerns!
There was a problem hiding this comment.
Good point. The Claude bot's analysis is correct — function injection gives us looser coupling and better testability. But I agree the current code is a bit verbose.
I'll adopt the interface-based approach as a middle ground:
interface LoRADependencies {
getEndpoint(providerAddress: string): Promise<string>
getHeaders(providerAddress: string, content?: string): Promise<Record<string, string>>
}This keeps the decoupling benefits while making the code cleaner. Will also fix the as unknown as Record<string, string> type cast while at it.
src.ts/cli/fine-tuning.ts
Outdated
| '--broker-url <url>', | ||
| 'Inference broker URL (for --deploy; skips contract lookup)' |
There was a problem hiding this comment.
Is this param necessary?
There was a problem hiding this comment.
Not strictly necessary — the CLI can resolve the broker URL from the on-chain service metadata automatically.
It was added as a dev/debug shortcut to skip the contract lookup (useful when testing against a specific broker endpoint). Happy to remove it if you think it adds unnecessary complexity. The deploy-adapter command has the same option.
- Replace function params with `LoRADependencies` interface for cleaner DI - Fix `as unknown as Record<string, string>` type cast with proper Object.entries conversion - Remove --broker-url CLI option from deploy-adapter and acknowledge-model (broker URL is always resolved from on-chain service metadata) Addresses HONGJICAI's review comments on PR #191.
Update: Review feedback addressed ✅ (commit
|
Summary
fine-tuning deploy-adapterthat triggers adapter deployment to the inference GPU via the broker's/v1/lora/adapters/deployAPI--deployflag onacknowledge-modelto acknowledge + deploy in one step--deploy, acknowledge only downloads the model; users must calldeploy-adapterseparately when they want to go liveCompanion PR
Broker-side changes: 0gfoundation/0g-serving-broker#379
New Commands
Test plan
deploy-adapterwith--waitcorrectly polls until adapter is activeacknowledge-model --deploydoes acknowledge + deploy in one flowacknowledge-modelwithout--deploystill works as before (download only)deploy-adapterreturns proper error when adapter not found or already active