Route AiClient through local AI Broker instead of direct provider calls #15
Labels
No labels
prio_critical
prio_low
type_bug
type_contact
type_issue
type_lead
type_question
type_story
type_task
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lhumina_code/hero_biz#15
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
The current
AiClientincrates/hero_biz_ui/src/ai/client.rsis an OpenAI-compatible HTTP client that calls external AI providers directly viabase_url+api_keyonAiProvider. This breaks the intended Hero stack architecture.Required Architecture
All AI calls must flow through the local AI Broker:
The broker already exists (
hero_aibroker) and exposes an OpenAI-compatible REST surface at~/hero/var/sockets/hero_aibroker/rest.sock.SDK Gap — Blocker
hero_biz uses non-streaming chat exclusively (
analyze_intent,generate_suggestions, all assistant calls go throughAiClient.chat()). The currenthero_aibroker_sdkonly provides:AIBrokerAdminAPIClient— admin RPC only (health, models, info), no chatStreamingClient— forcesstream: true, streaming onlyAIBrokerRawClient— raw JSON-RPC; chat is not exposed over the RPC socketThere is no non-streaming chat client in the SDK. This must be resolved first.
Path Forward
Step 1 (prerequisite — work in
hero_aibroker): Add a non-streaming REST client tohero_aibroker_sdkthat POSTs torest.sockat/v1/chat/completionswithout forcing streaming. Something like aRestClientalongside the existingStreamingClient.Step 2 (this repo): Replace hero_biz's
AiClientwith the new SDK client:AiProvider,AiConfig, and all provider/API key config from hero_biz — that becomes the broker's concerntry_chatfallback loop — failover is handled inside the brokerchat(),transcribe(), andtts()calls to the broker REST socket via the SDKReferences
crates/hero_biz_ui/src/ai/client.rs—AiClient,try_chat,try_transcribe,try_ttscrates/hero_biz_ui/src/ai/config.rs—AiProvider,AiConfig,AiModelhero_aibroker/crates/hero_aibroker_sdk/src/streaming.rs— existingStreamingClient(streaming only)hero_aibroker/crates/hero_aibroker_sdk/src/lib.rs—AIBrokerAdminAPIClient,AIBrokerRawClientherolib_aiintohero_aibroker_sdk(single AI client)Blocked by lhumina_code/hero_aibroker#63
This issue cannot start until
hero_aibroker_sdkexposes a non-streaming REST chat client. The SDK currently only hasStreamingClientandAIBrokerAdminAPIClient— neither supports a plainPOST /v1/chat/completionswithout forcing streaming.The prerequisite work is tracked in hero_aibroker#63 — Consolidate herolib_ai into hero_aibroker_sdk. Specifically, step 1 of that issue (add
from_default_socket()+ non-streaming chat toHeroAibrokerClient) must land before this can proceed.herolib_aiintohero_aibroker_sdk(single AI client) #63Implementation Spec for Issue #15
Objective
Replace
hero_biz_admin's direct-providerAiClientwith calls routed through the localhero_aibrokerREST socket, eliminating all provider/API-key configuration from hero_biz. The public surface ofAiClient(the methods used byassistant.rsandserver.rs) is preserved so callers need only minimal changes.Key Findings
Correction vs. issue text: The files live in
crates/hero_biz_admin/, notcrates/hero_biz_ui/. The issue refers to an older layout.SDK state: The
hero_aibroker_sdkStreamingClienttalks torest.sockover raw Unix HTTP. The same pattern must be used for non-streaming calls — there is no pre-built non-streaming client in the SDK, so a thin helper is written directly inclient.rs.Broker REST socket path:
$HERO_SOCKET_DIR/hero_aibroker/rest.sockCallers of
AiClient:crates/hero_biz_admin/src/ai/assistant.rs— calls.chat(ModelPurpose::*, …)crates/hero_biz_admin/src/web/server.rs— constructsAiClientfromAiConfig; holdsOption<AiClient>inAppStatecrates/hero_biz_admin/src/lib.rs— passesOption<AiConfig>intocreate_web_routerRequirements
rest.sock), never directly to external providersAiProvider,AiConfig,AiModelfrom hero_biz — no provider URLs, no API keys, no model selectiontry_chat,try_transcribe,try_ttsfallback loops — the broker handles routing/fallbackAiClientconstructable fromHERO_SOCKET_DIRenv var onlychat(),transcribe(),text_to_speech(),text_chat(),morph()signatures for existing callersChatMessage,MessageRole,TranscriptionRequestpublic (used by handlers)reqwestfromhero_biz_admindeps (only used inai/client.rs)create_web_routerno longer acceptsai_configparameterFiles to Modify
Cargo.toml(workspace root)hero_aibroker_sdkto[workspace.dependencies]crates/hero_biz_admin/Cargo.tomlhero_aibroker_sdk; removereqwestcrates/hero_biz_admin/src/ai/client.rscrates/hero_biz_admin/src/ai/config.rsAiProvider,AiConfig,AiModel; keepModelPurposecrates/hero_biz_admin/src/ai/mod.rscrates/hero_biz_admin/src/web/server.rsAiConfig-based construction withAiClient::from_env()crates/hero_biz_admin/src/lib.rsai_configparameter fromcreate_web_routerImplementation Plan
Step 1 — Update Cargo.toml files
Files:
Cargo.toml,crates/hero_biz_admin/Cargo.tomlhero_aibroker_sdkgit dependency to workspacehero_biz_admindepsreqwestfromhero_biz_admin(verify no other usage first)Dependencies: none
Step 2 — Rewrite
crates/hero_biz_admin/src/ai/client.rsFiles:
crates/hero_biz_admin/src/ai/client.rsunix_post_json(path, body) -> Result<serde_json::Value>helper usingtokio::net::UnixStreamraw HTTP/1.1unix_post_multiparthelper for STTAiClient { socket_path: PathBuf }withfrom_env()constructorchat()(sendsmodel: "auto", ignoresModelPurpose),transcribe(),text_to_speech(),text_chat(),morph()ChatMessage,MessageRole,TranscriptionRequestpublic; remove all private OpenAI-client structsDependencies: none (Step 1 for compile)
Step 3 — Update
config.rsandmod.rsFiles:
crates/hero_biz_admin/src/ai/config.rs,crates/hero_biz_admin/src/ai/mod.rsAiProvider,AiConfig,AiModelfromconfig.rsModelPurposeenum and its trait implsmod.rsto not re-export removed typesDependencies: Step 2
Step 4 — Update
server.rsandlib.rsFiles:
crates/hero_biz_admin/src/web/server.rs,crates/hero_biz_admin/src/lib.rsAiConfig-based construction withAiClient::from_env()ai_configparam fromcreate_web_routerandcreate_routerOption<AiClient>inAppStatebut alwaysSome(AiClient::from_env())ai_client(grep for.ai_client)Dependencies: Step 3
Step 5 — Verify build and fix compile errors
cargo build -p hero_biz_admincargo test -p hero_biz_adminDependencies: Step 4
Acceptance Criteria
cargo build -p hero_biz_adminsucceedsAiProvider,AiConfig,AiModelno longer exist in hero_bizreqwestremoved fromhero_biz_admin/Cargo.tomlunix://.../hero_aibroker/rest.sockbase_urlorapi_keyremainscreate_web_routerno longer acceptsai_configcargo test -p hero_biz_adminpassesNotes
ModelPurposeis kept inconfig.rsfor API compatibility withassistant.rs; the implementation ignores it and sendsmodel: "auto"to the brokerUnixStream(same raw-socket approach as chat)Test Results
Implementation Complete
Changes made
crates/hero_biz_admin/src/ai/client.rs— Rewritten from scratch. Removed allreqwest-based direct provider calls (try_chat,try_transcribe,try_tts, fallback loops, API key handling). NewAiClientholds only asocket_path: PathBufresolved fromHERO_SOCKET_DIR. Communicates with the broker via rawtokio::net::UnixStreamHTTP/1.1 (same pattern ashero_aibroker_sdk'sStreamingClient). Kept public typesChatMessage,MessageRole,TranscriptionRequestunchanged — callers need no updates.crates/hero_biz_admin/src/ai/config.rs— RemovedAiProvider,AiConfig,AiModel. Kept onlyModelPurposeenum (still passed toAiClient::chat()for API compatibility; ignored by the broker since it auto-routes).crates/hero_biz_admin/src/ai/mod.rs— Narrowedconfigre-export toModelPurposeonly (waspub use config::*).crates/hero_biz_admin/src/web/server.rs—create_routerno longer acceptsai_config: Option<AiConfig>. ConstructsAiClient::from_env()unconditionally at startup.ai_configuredis alwaystrue.crates/hero_biz_admin/src/lib.rs—create_web_routersignature simplified: removedai_config: Option<AiConfig>parameter.crates/hero_biz_admin/src/main.rs— Updated call site to match new signature.crates/hero_biz_admin/Cargo.toml— Removedreqwestfrom both[dependencies]and[dev-dependencies].Broker routing
All AI calls now route through
$HERO_SOCKET_DIR/hero_aibroker/rest.sock:POST /v1/chat/completionswithmodel: "auto"POST /v1/audio/transcriptions(multipart/form-data over Unix socket)POST /v1/audio/speechTest results
cargo test -p hero_biz_admin: 7 passed, 0 failed.Implemented in commit
387341c.AiClientnow routes all calls (chat, STT, TTS, morph) through the localhero_aibrokerREST socket at$HERO_SOCKET_DIR/hero_aibroker/rest.sock. Direct provider calls removed.herolib_aiintohero_aibroker_sdk(single AI client)