refactor(inference): retire local_ai rpc namespace (#3476)

This commit is contained in:
Steven Enamakel
2026-06-07 03:53:49 -04:00
committed by GitHub
parent 53373234a7
commit 3fb8375b23
31 changed files with 213 additions and 458 deletions
-326
View File
@@ -1218,332 +1218,6 @@
}
]
},
{
"description": "Run one-shot agent chat with optional model overrides.",
"function": "agent_chat",
"inputs": [
{ "comment": "User message.", "name": "message", "required": true, "ty": "String" },
{
"comment": "Optional model override.",
"name": "model_override",
"required": false,
"ty": { "Option": "String" }
},
{
"comment": "Optional temperature override.",
"name": "temperature",
"required": false,
"ty": { "Option": "F64" }
}
],
"method": "openhuman.local_ai_agent_chat",
"namespace": "local_ai",
"outputs": [
{ "comment": "Agent response payload.", "name": "response", "required": true, "ty": "Json" }
]
},
{
"description": "Run one-shot lightweight provider chat.",
"function": "agent_chat_simple",
"inputs": [
{ "comment": "User message.", "name": "message", "required": true, "ty": "String" },
{
"comment": "Optional model override.",
"name": "model_override",
"required": false,
"ty": { "Option": "String" }
},
{
"comment": "Optional temperature override.",
"name": "temperature",
"required": false,
"ty": { "Option": "F64" }
}
],
"method": "openhuman.local_ai_agent_chat_simple",
"namespace": "local_ai",
"outputs": [
{ "comment": "Agent response payload.", "name": "response", "required": true, "ty": "Json" }
]
},
{
"description": "Terminate REPL session.",
"function": "agent_repl_session_end",
"inputs": [
{ "comment": "REPL session id.", "name": "session_id", "required": true, "ty": "String" }
],
"method": "openhuman.local_ai_agent_repl_session_end",
"namespace": "local_ai",
"outputs": [
{ "comment": "Session end result.", "name": "result", "required": true, "ty": "Json" }
]
},
{
"description": "Clear REPL session history.",
"function": "agent_repl_session_reset",
"inputs": [
{ "comment": "REPL session id.", "name": "session_id", "required": true, "ty": "String" }
],
"method": "openhuman.local_ai_agent_repl_session_reset",
"namespace": "local_ai",
"outputs": [
{ "comment": "Session reset result.", "name": "result", "required": true, "ty": "Json" }
]
},
{
"description": "Create a persistent REPL agent session.",
"function": "agent_repl_session_start",
"inputs": [
{
"comment": "Optional session id.",
"name": "session_id",
"required": false,
"ty": { "Option": "String" }
},
{
"comment": "Optional model override.",
"name": "model_override",
"required": false,
"ty": { "Option": "String" }
},
{
"comment": "Optional temperature override.",
"name": "temperature",
"required": false,
"ty": { "Option": "F64" }
}
],
"method": "openhuman.local_ai_agent_repl_session_start",
"namespace": "local_ai",
"outputs": [
{ "comment": "Session creation result.", "name": "result", "required": true, "ty": "Json" }
]
},
{
"description": "Get local AI asset installation status.",
"function": "assets_status",
"inputs": [],
"method": "openhuman.local_ai_assets_status",
"namespace": "local_ai",
"outputs": [
{ "comment": "Assets status payload.", "name": "status", "required": true, "ty": "Json" }
]
},
{
"description": "Trigger local AI model download bootstrap.",
"function": "download",
"inputs": [
{
"comment": "Reset state before download.",
"name": "force",
"required": false,
"ty": { "Option": "Bool" }
}
],
"method": "openhuman.local_ai_download",
"namespace": "local_ai",
"outputs": [
{ "comment": "Local AI status payload.", "name": "status", "required": true, "ty": "Json" }
]
},
{
"description": "Trigger full local AI asset download.",
"function": "download_all_assets",
"inputs": [
{
"comment": "Reset state before download.",
"name": "force",
"required": false,
"ty": { "Option": "Bool" }
}
],
"method": "openhuman.local_ai_download_all_assets",
"namespace": "local_ai",
"outputs": [
{
"comment": "Download progress payload.",
"name": "progress",
"required": true,
"ty": "Json"
}
]
},
{
"description": "Trigger download for one local AI asset capability.",
"function": "download_asset",
"inputs": [
{
"comment": "Asset capability id.",
"name": "capability",
"required": true,
"ty": "String"
}
],
"method": "openhuman.local_ai_download_asset",
"namespace": "local_ai",
"outputs": [
{ "comment": "Assets status payload.", "name": "status", "required": true, "ty": "Json" }
]
},
{
"description": "Get local AI download progress.",
"function": "downloads_progress",
"inputs": [],
"method": "openhuman.local_ai_downloads_progress",
"namespace": "local_ai",
"outputs": [
{
"comment": "Download progress payload.",
"name": "progress",
"required": true,
"ty": "Json"
}
]
},
{
"description": "Generate embeddings for text inputs.",
"function": "embed",
"inputs": [
{
"comment": "Texts to embed.",
"name": "inputs",
"required": true,
"ty": { "Array": "String" }
}
],
"method": "openhuman.local_ai_embed",
"namespace": "local_ai",
"outputs": [
{
"comment": "Embedding result payload.",
"name": "embedding",
"required": true,
"ty": "Json"
}
]
},
{
"description": "Run direct local AI prompt.",
"function": "prompt",
"inputs": [
{ "comment": "Prompt text.", "name": "prompt", "required": true, "ty": "String" },
{
"comment": "Optional max output tokens.",
"name": "max_tokens",
"required": false,
"ty": { "Option": "U64" }
},
{
"comment": "Disable thinking mode.",
"name": "no_think",
"required": false,
"ty": { "Option": "Bool" }
}
],
"method": "openhuman.local_ai_prompt",
"namespace": "local_ai",
"outputs": [
{ "comment": "Prompt output text.", "name": "output", "required": true, "ty": "Json" }
]
},
{
"description": "Read local AI service status.",
"function": "status",
"inputs": [],
"method": "openhuman.local_ai_status",
"namespace": "local_ai",
"outputs": [
{ "comment": "Local AI status payload.", "name": "status", "required": true, "ty": "Json" }
]
},
{
"description": "Summarize text with local AI model.",
"function": "summarize",
"inputs": [
{ "comment": "Input text.", "name": "text", "required": true, "ty": "String" },
{
"comment": "Optional max output tokens.",
"name": "max_tokens",
"required": false,
"ty": { "Option": "U64" }
}
],
"method": "openhuman.local_ai_summarize",
"namespace": "local_ai",
"outputs": [{ "comment": "Summary text.", "name": "summary", "required": true, "ty": "Json" }]
},
{
"description": "Transcribe audio from file path.",
"function": "transcribe",
"inputs": [
{ "comment": "Input audio path.", "name": "audio_path", "required": true, "ty": "String" }
],
"method": "openhuman.local_ai_transcribe",
"namespace": "local_ai",
"outputs": [
{ "comment": "Transcription payload.", "name": "speech", "required": true, "ty": "Json" }
]
},
{
"description": "Transcribe audio from raw bytes.",
"function": "transcribe_bytes",
"inputs": [
{ "comment": "Raw audio bytes.", "name": "audio_bytes", "required": true, "ty": "Bytes" },
{
"comment": "Optional audio extension.",
"name": "extension",
"required": false,
"ty": { "Option": "String" }
}
],
"method": "openhuman.local_ai_transcribe_bytes",
"namespace": "local_ai",
"outputs": [
{ "comment": "Transcription payload.", "name": "speech", "required": true, "ty": "Json" }
]
},
{
"description": "Synthesize speech from text.",
"function": "tts",
"inputs": [
{ "comment": "Input text.", "name": "text", "required": true, "ty": "String" },
{
"comment": "Optional output path.",
"name": "output_path",
"required": false,
"ty": { "Option": "String" }
}
],
"method": "openhuman.local_ai_tts",
"namespace": "local_ai",
"outputs": [
{ "comment": "TTS result payload.", "name": "tts", "required": true, "ty": "Json" }
]
},
{
"description": "Run multimodal local AI prompt with image refs.",
"function": "vision_prompt",
"inputs": [
{ "comment": "Prompt text.", "name": "prompt", "required": true, "ty": "String" },
{
"comment": "Image references to include.",
"name": "image_refs",
"required": true,
"ty": { "Array": "String" }
},
{
"comment": "Optional max output tokens.",
"name": "max_tokens",
"required": false,
"ty": { "Option": "U64" }
}
],
"method": "openhuman.local_ai_vision_prompt",
"namespace": "local_ai",
"outputs": [
{ "comment": "Prompt output text.", "name": "output", "required": true, "ty": "Json" }
]
},
{
"description": "Migrate OpenClaw memory into current workspace.",
"function": "openclaw",
@@ -131,6 +131,10 @@ describe('rpcMethods catalog', () => {
path.resolve(__dirname, '../../../../src/openhuman/inference/schemas.rs'),
'utf8'
),
fs.readFileSync(
path.resolve(__dirname, '../../../../src/openhuman/inference/local/schemas.rs'),
'utf8'
),
fs.readFileSync(
path.resolve(__dirname, '../../../../src/openhuman/embeddings/schemas.rs'),
'utf8'
@@ -33,7 +33,7 @@ describe('voiceInstallApi', () => {
vi.mocked(callCoreRpc).mockResolvedValueOnce(buildStatus({ engine: 'whisper' }));
const result = await installWhisper({ modelSize: 'tiny', force: true });
expect(callCoreRpc).toHaveBeenCalledWith({
method: 'openhuman.local_ai_install_whisper',
method: 'openhuman.inference_install_whisper',
params: { model_size: 'tiny', force: true },
});
expect(result.engine).toBe('whisper');
@@ -45,7 +45,7 @@ describe('voiceInstallApi', () => {
vi.mocked(callCoreRpc).mockResolvedValueOnce(buildStatus());
await installWhisper();
expect(callCoreRpc).toHaveBeenCalledWith({
method: 'openhuman.local_ai_install_whisper',
method: 'openhuman.inference_install_whisper',
params: { model_size: undefined, force: undefined },
});
});
@@ -65,7 +65,7 @@ describe('voiceInstallApi', () => {
);
const result = await installPiper({ voiceId: 'en_US-lessac-medium', force: false });
expect(callCoreRpc).toHaveBeenCalledWith({
method: 'openhuman.local_ai_install_piper',
method: 'openhuman.inference_install_piper',
params: { voice_id: 'en_US-lessac-medium', force: false },
});
expect(result.state).toBe('installing');
@@ -77,7 +77,7 @@ describe('voiceInstallApi', () => {
vi.mocked(callCoreRpc).mockResolvedValueOnce(buildStatus({ engine: 'piper' }));
await installPiper();
expect(callCoreRpc).toHaveBeenCalledWith({
method: 'openhuman.local_ai_install_piper',
method: 'openhuman.inference_install_piper',
params: { voice_id: undefined, force: undefined },
});
});
@@ -91,7 +91,7 @@ describe('voiceInstallApi', () => {
);
const result = await whisperInstallStatus();
expect(callCoreRpc).toHaveBeenCalledWith({
method: 'openhuman.local_ai_whisper_install_status',
method: 'openhuman.inference_whisper_install_status',
params: {},
});
expect(result.state).toBe('missing');
@@ -106,7 +106,7 @@ describe('voiceInstallApi', () => {
);
const result = await piperInstallStatus();
expect(callCoreRpc).toHaveBeenCalledWith({
method: 'openhuman.local_ai_piper_install_status',
method: 'openhuman.inference_piper_install_status',
params: {},
});
expect(result.state).toBe('error');
+6 -6
View File
@@ -1,5 +1,5 @@
/**
* Voice engine installer API — wraps the four new `local_ai.*` RPCs that
* Voice engine installer API — wraps the `inference.*` RPCs that
* orchestrate downloads of the Whisper GGML model + binary and the Piper
* binary + bundled voice into the workspace.
*
@@ -15,7 +15,7 @@ import { callCoreRpc } from '../coreRpcClient';
const log = debug('voiceInstallApi');
/**
* Stable wire shape of [`crate::openhuman::local_ai::voice_install_common::VoiceInstallState`].
* Stable wire shape of [`crate::openhuman::inference::local::voice_install_common::VoiceInstallState`].
*
* The Rust enum serializes via `#[serde(rename_all = "snake_case")]` so
* the TypeScript union mirrors the lowercase variants exactly.
@@ -67,7 +67,7 @@ export async function installWhisper(
): Promise<VoiceInstallStatus> {
log('[voice-install:whisper] kick-off %o', params);
const result = await callCoreRpc<VoiceInstallStatus>({
method: 'openhuman.local_ai_install_whisper',
method: 'openhuman.inference_install_whisper',
params: { model_size: params.modelSize, force: params.force },
});
log('[voice-install:whisper] result state=%s stage=%s', result.state, result.stage ?? '<none>');
@@ -81,7 +81,7 @@ export async function installWhisper(
export async function installPiper(params: InstallPiperParams = {}): Promise<VoiceInstallStatus> {
log('[voice-install:piper] kick-off %o', params);
const result = await callCoreRpc<VoiceInstallStatus>({
method: 'openhuman.local_ai_install_piper',
method: 'openhuman.inference_install_piper',
params: { voice_id: params.voiceId, force: params.force },
});
log('[voice-install:piper] result state=%s stage=%s', result.state, result.stage ?? '<none>');
@@ -96,7 +96,7 @@ export async function installPiper(params: InstallPiperParams = {}): Promise<Voi
*/
export async function whisperInstallStatus(): Promise<VoiceInstallStatus> {
return await callCoreRpc<VoiceInstallStatus>({
method: 'openhuman.local_ai_whisper_install_status',
method: 'openhuman.inference_whisper_install_status',
params: {},
});
}
@@ -107,7 +107,7 @@ export async function whisperInstallStatus(): Promise<VoiceInstallStatus> {
*/
export async function piperInstallStatus(): Promise<VoiceInstallStatus> {
return await callCoreRpc<VoiceInstallStatus>({
method: 'openhuman.local_ai_piper_install_status',
method: 'openhuman.inference_piper_install_status',
params: {},
});
}
+26
View File
@@ -28,14 +28,27 @@ export const CORE_RPC_METHODS = {
configWorkspaceOnboardingFlagExists: 'openhuman.config_workspace_onboarding_flag_exists',
configWorkspaceOnboardingFlagSet: 'openhuman.config_workspace_onboarding_flag_set',
corePing: 'core.ping',
inferenceAgentChat: 'openhuman.inference_agent_chat',
inferenceAgentChatSimple: 'openhuman.inference_agent_chat_simple',
inferenceApplyPreset: 'openhuman.inference_apply_preset',
inferenceAssetsStatus: 'openhuman.inference_assets_status',
inferenceDiagnostics: 'openhuman.inference_diagnostics',
inferenceDeviceProfile: 'openhuman.inference_device_profile',
inferenceDownloadAsset: 'openhuman.inference_download_asset',
inferenceDownloadsProgress: 'openhuman.inference_downloads_progress',
inferenceGetClientConfig: 'openhuman.inference_get_client_config',
inferenceInstallPiper: 'openhuman.inference_install_piper',
inferenceInstallWhisper: 'openhuman.inference_install_whisper',
inferenceListModels: 'openhuman.inference_list_models',
inferencePiperInstallStatus: 'openhuman.inference_piper_install_status',
inferencePresets: 'openhuman.inference_presets',
inferenceTestConnection: 'openhuman.inference_test_connection',
inferenceTranscribe: 'openhuman.inference_transcribe',
inferenceTranscribeBytes: 'openhuman.inference_transcribe_bytes',
inferenceTts: 'openhuman.inference_tts',
inferenceUpdateLocalSettings: 'openhuman.inference_update_local_settings',
inferenceUpdateModelSettings: 'openhuman.inference_update_model_settings',
inferenceWhisperInstallStatus: 'openhuman.inference_whisper_install_status',
providersListModels: 'openhuman.inference_list_models',
screenIntelligenceStatus: 'openhuman.screen_intelligence_status',
embeddingsGetSettings: 'openhuman.embeddings_get_settings',
@@ -81,10 +94,23 @@ export const LEGACY_METHOD_ALIASES: Record<string, CoreRpcMethod> = {
'openhuman.workspace_onboarding_flag_exists':
CORE_RPC_METHODS.configWorkspaceOnboardingFlagExists,
'openhuman.workspace_onboarding_flag_set': CORE_RPC_METHODS.configWorkspaceOnboardingFlagSet,
'openhuman.local_ai_agent_chat': CORE_RPC_METHODS.inferenceAgentChat,
'openhuman.local_ai_agent_chat_simple': CORE_RPC_METHODS.inferenceAgentChatSimple,
'openhuman.local_ai_apply_preset': CORE_RPC_METHODS.inferenceApplyPreset,
'openhuman.local_ai_assets_status': CORE_RPC_METHODS.inferenceAssetsStatus,
'openhuman.local_ai_device_profile': CORE_RPC_METHODS.inferenceDeviceProfile,
'openhuman.local_ai_diagnostics': CORE_RPC_METHODS.inferenceDiagnostics,
'openhuman.local_ai_download_asset': CORE_RPC_METHODS.inferenceDownloadAsset,
'openhuman.local_ai_downloads_progress': CORE_RPC_METHODS.inferenceDownloadsProgress,
'openhuman.local_ai_install_piper': CORE_RPC_METHODS.inferenceInstallPiper,
'openhuman.local_ai_install_whisper': CORE_RPC_METHODS.inferenceInstallWhisper,
'openhuman.local_ai_piper_install_status': CORE_RPC_METHODS.inferencePiperInstallStatus,
'openhuman.local_ai_presets': CORE_RPC_METHODS.inferencePresets,
'openhuman.local_ai_test_connection': CORE_RPC_METHODS.inferenceTestConnection,
'openhuman.local_ai_transcribe': CORE_RPC_METHODS.inferenceTranscribe,
'openhuman.local_ai_transcribe_bytes': CORE_RPC_METHODS.inferenceTranscribeBytes,
'openhuman.local_ai_tts': CORE_RPC_METHODS.inferenceTts,
'openhuman.local_ai_whisper_install_status': CORE_RPC_METHODS.inferenceWhisperInstallStatus,
'openhuman.providers_list_models': CORE_RPC_METHODS.inferenceListModels,
'openhuman.inference_embed': CORE_RPC_METHODS.embeddingsEmbed,
health_snapshot: CORE_RPC_METHODS.healthSnapshot,
+2 -2
View File
@@ -7,7 +7,7 @@ describe('openhumanLocalAiTestConnection', () => {
vi.clearAllMocks();
});
it('calls callCoreRpc with local_ai_test_connection method and url param', async () => {
it('calls callCoreRpc with inference_test_connection method and url param', async () => {
const { callCoreRpc } = await import('../../services/coreRpcClient');
const mockCallCoreRpc = callCoreRpc as ReturnType<typeof vi.fn>;
mockCallCoreRpc.mockResolvedValueOnce({ reachable: true, models_count: 4 });
@@ -16,7 +16,7 @@ describe('openhumanLocalAiTestConnection', () => {
const result = await openhumanLocalAiTestConnection('http://localhost:11434');
expect(mockCallCoreRpc).toHaveBeenCalledWith({
method: 'openhuman.local_ai_test_connection',
method: 'openhuman.inference_test_connection',
params: { url: 'http://localhost:11434' },
});
expect(result).toEqual({ reachable: true, models_count: 4 });
+7 -7
View File
@@ -298,7 +298,7 @@ export async function openhumanLocalAiTranscribe(
audioPath: string
): Promise<CommandResponse<LocalAiSpeechResult>> {
return await callCoreRpc<CommandResponse<LocalAiSpeechResult>>({
method: 'openhuman.local_ai_transcribe',
method: 'openhuman.inference_transcribe',
params: { audio_path: audioPath },
});
}
@@ -308,7 +308,7 @@ export async function openhumanLocalAiTranscribeBytes(
extension?: string
): Promise<CommandResponse<LocalAiSpeechResult>> {
return await callCoreRpc<CommandResponse<LocalAiSpeechResult>>({
method: 'openhuman.local_ai_transcribe_bytes',
method: 'openhuman.inference_transcribe_bytes',
params: { audio_bytes: audioBytes, extension },
});
}
@@ -318,7 +318,7 @@ export async function openhumanLocalAiTts(
outputPath?: string
): Promise<CommandResponse<LocalAiTtsResult>> {
return await callCoreRpc<CommandResponse<LocalAiTtsResult>>({
method: 'openhuman.local_ai_tts',
method: 'openhuman.inference_tts',
params: { text, output_path: outputPath },
});
}
@@ -354,7 +354,7 @@ export async function openhumanLocalAiAssetsStatus(): Promise<
CommandResponse<LocalAiAssetsStatus>
> {
return await callCoreRpc<CommandResponse<LocalAiAssetsStatus>>({
method: 'openhuman.local_ai_assets_status',
method: 'openhuman.inference_assets_status',
});
}
@@ -362,7 +362,7 @@ export async function openhumanLocalAiDownloadsProgress(): Promise<
CommandResponse<LocalAiDownloadsProgress>
> {
return await callCoreRpc<CommandResponse<LocalAiDownloadsProgress>>({
method: 'openhuman.local_ai_downloads_progress',
method: 'openhuman.inference_downloads_progress',
});
}
@@ -370,7 +370,7 @@ export async function openhumanLocalAiDownloadAsset(
capability: 'chat' | 'vision' | 'embedding' | 'stt' | 'tts'
): Promise<CommandResponse<LocalAiAssetsStatus>> {
return await callCoreRpc<CommandResponse<LocalAiAssetsStatus>>({
method: 'openhuman.local_ai_download_asset',
method: 'openhuman.inference_download_asset',
params: { capability },
});
}
@@ -407,7 +407,7 @@ export async function openhumanLocalAiTestConnection(
url: string
): Promise<OllamaConnectionTestResult> {
return await callCoreRpc<OllamaConnectionTestResult>({
method: 'openhuman.local_ai_test_connection',
method: 'openhuman.inference_test_connection',
params: { url },
});
}
+2 -2
View File
@@ -90,7 +90,7 @@ The `local_ai.usage.*` booleans are consulted only during preset application and
Prefer setting the `*_provider` fields directly when editing configuration by hand.
In the desktop app, **Settings → AI & Skills → Local AI** exposes presets, pick one ("embeddings only", "memory + reflection", "everything local") and the right combination of flags is set for you. Status (Ollama reachability, model availability, per-subsystem enablement) is surfaced live via `openhuman.local_ai_status`.
In the desktop app, **Settings → AI & Skills → Local AI** exposes presets, pick one ("embeddings only", "memory + reflection", "everything local") and the right combination of flags is set for you. Status (Ollama reachability, model availability, per-subsystem enablement) is surfaced live via `openhuman.inference_status`.
## When to turn it on
@@ -108,7 +108,7 @@ It is **not** worth turning on if you only have a few sources connected, the clo
- Enough disk for the models (`gemma3:1b-it-qat` \~700 MB, `all-minilm:latest` \~23 MB).
- Enough RAM to keep the model resident (8 GB+ recommended, 16 GB+ ideal).
OpenHuman handles the rest: lifecycle (`src/openhuman/local_ai/service/`), API clients (`ollama_api.rs`, `lm_studio_api.rs`), health checks, and graceful fallback to remote when the local provider disappears.
OpenHuman handles the rest: lifecycle (`src/openhuman/inference/local/service/`), API clients, health checks, and graceful fallback to remote when the local provider disappears.
### LM Studio troubleshooting
@@ -65,7 +65,7 @@ OpenHuman 可以为以下工作负载在你机器上运行本地模型:当本
| `local_ai.usage.learning_reflection` | `false` | 使用本地进行学习通过。 |
| `local_ai.usage.subconscious` | `false` | 使用本地进行潜意识循环。 |
在桌面 app 中,**设置 → AI 与技能 → 本地 AI** 暴露预设,选择一个("仅嵌入向量"、"记忆 + 反思"、"全部本地"),正确的 flag 组合会为你设置。状态(Ollama 可达性、模型可用性、每个子系统启用)通过 `openhuman.local_ai_status` 实时暴露。
在桌面 app 中,**设置 → AI 与技能 → 本地 AI** 暴露预设,选择一个("仅嵌入向量"、"记忆 + 反思"、"全部本地"),正确的 flag 组合会为你设置。状态(Ollama 可达性、模型可用性、每个子系统启用)通过 `openhuman.inference_status` 实时暴露。
## 何时开启
@@ -83,7 +83,7 @@ OpenHuman 可以为以下工作负载在你机器上运行本地模型:当本
* 模型有足够的磁盘(`gemma3:1b-it-qat` \~700 MB`all-minilm:latest` \~23 MB)。
* 有足够的 RAM 保持模型驻留(建议 8 GB+,理想 16 GB+)。
OpenHuman 处理其余:生命周期(`src/openhuman/local_ai/service/`)、API 客户端`ollama_api.rs``lm_studio_api.rs`、健康检查,以及当本地 provider 消失时优雅地回退到远程。
OpenHuman 处理其余:生命周期(`src/openhuman/inference/local/service/`)、API 客户端、健康检查,以及当本地 provider 消失时优雅地回退到远程。
### LM Studio 故障排除
@@ -96,4 +96,4 @@ OpenHuman 处理其余:生命周期(`src/openhuman/local_ai/service/`)、A
* [记忆树](../obsidian-wiki/memory-tree.zh-CN.md)。本地嵌入向量 + 摘要 powering 什么。
* [自动模型路由](README.zh-CN.md)。轻量聊天 hint 如何优先使用本地 provider。
* [隐私与安全](../privacy-and-security.zh-CN.md)。当你 opt-in 时什么移至端侧。
* [隐私与安全](../privacy-and-security.zh-CN.md)。当你 opt-in 时什么移至端侧。
+3 -4
View File
@@ -179,9 +179,9 @@ fn build_registered_controllers() -> Vec<RegisteredController> {
// Background command monitors for agent-scoped event sources
controllers.extend(crate::openhuman::monitor::all_monitor_registered_controllers());
// Unified inference domain: text / vision / local runtime / cloud providers.
// (Formerly split across inference, local_ai, and providers namespaces.)
// (Formerly split across inference, local AI, and providers modules.)
controllers.extend(crate::openhuman::inference::all_inference_registered_controllers());
controllers.extend(crate::openhuman::inference::all_local_ai_registered_controllers());
controllers.extend(crate::openhuman::inference::all_local_inference_registered_controllers());
// Embedding provider configuration and embed RPC.
controllers.extend(crate::openhuman::embeddings::all_embeddings_registered_controllers());
// People resolution and interaction scoring
@@ -351,7 +351,7 @@ fn build_declared_controller_schemas() -> Vec<ControllerSchema> {
schemas.extend(crate::openhuman::model_council::all_model_council_controller_schemas());
schemas.extend(crate::openhuman::monitor::all_monitor_controller_schemas());
schemas.extend(crate::openhuman::inference::all_inference_controller_schemas());
schemas.extend(crate::openhuman::inference::all_local_ai_controller_schemas());
schemas.extend(crate::openhuman::inference::all_local_inference_controller_schemas());
schemas.extend(crate::openhuman::embeddings::all_embeddings_controller_schemas());
schemas.extend(crate::openhuman::people::all_people_controller_schemas());
schemas.extend(
@@ -469,7 +469,6 @@ pub fn namespace_description(namespace: &str) -> Option<&'static str> {
"encrypt" => Some("Encrypt secure values managed by secret storage."),
"health" => Some("Process and component health snapshots."),
"inference" => Some("Connect to configured text, vision, and embedding inference runtimes."),
"local_ai" => Some("Local AI chat, inference, downloads, and media operations."),
"migrate" => Some("Data migration utilities."),
"javascript" => Some("First-class JavaScript runtime bridge for listing and dispatching tools."),
"monitor" => Some("Start, inspect, read, and stop bounded background command monitors."),
-1
View File
@@ -25,7 +25,6 @@ fn grouped_schemas_contains_migrated_namespaces() {
assert!(grouped.contains_key("service"));
assert!(grouped.contains_key("migrate"));
assert!(grouped.contains_key("inference"));
assert!(grouped.contains_key("local_ai"));
}
#[test]
+49
View File
@@ -111,6 +111,18 @@ const LEGACY_ALIASES: &[(&str, &str)] = &[
"openhuman.local_ai_apply_preset",
"openhuman.inference_apply_preset",
),
(
"openhuman.local_ai_agent_chat",
"openhuman.inference_agent_chat",
),
(
"openhuman.local_ai_agent_chat_simple",
"openhuman.inference_agent_chat_simple",
),
(
"openhuman.local_ai_assets_status",
"openhuman.inference_assets_status",
),
(
"openhuman.local_ai_device_profile",
"openhuman.inference_device_profile",
@@ -119,6 +131,26 @@ const LEGACY_ALIASES: &[(&str, &str)] = &[
"openhuman.local_ai_diagnostics",
"openhuman.inference_diagnostics",
),
(
"openhuman.local_ai_download_asset",
"openhuman.inference_download_asset",
),
(
"openhuman.local_ai_downloads_progress",
"openhuman.inference_downloads_progress",
),
(
"openhuman.local_ai_install_piper",
"openhuman.inference_install_piper",
),
(
"openhuman.local_ai_install_whisper",
"openhuman.inference_install_whisper",
),
(
"openhuman.local_ai_piper_install_status",
"openhuman.inference_piper_install_status",
),
// bare `health_snapshot` (no namespace prefix) was used by older clients
// before the canonical `openhuman.health_snapshot` form was established.
("health_snapshot", "openhuman.health_snapshot"),
@@ -128,6 +160,23 @@ const LEGACY_ALIASES: &[(&str, &str)] = &[
("openhuman.system_info", "openhuman.health_system_info"),
("openhuman.inference_embed", "openhuman.embeddings_embed"),
("openhuman.local_ai_presets", "openhuman.inference_presets"),
(
"openhuman.local_ai_test_connection",
"openhuman.inference_test_connection",
),
(
"openhuman.local_ai_transcribe",
"openhuman.inference_transcribe",
),
(
"openhuman.local_ai_transcribe_bytes",
"openhuman.inference_transcribe_bytes",
),
("openhuman.local_ai_tts", "openhuman.inference_tts"),
(
"openhuman.local_ai_whisper_install_status",
"openhuman.inference_whisper_install_status",
),
(
"openhuman.providers_list_models",
"openhuman.inference_list_models",
+2 -2
View File
@@ -1146,10 +1146,10 @@ fn is_provider_user_state_message(lower: &str) -> bool {
/// The two canonical wire shapes today both contain `"for this ram tier"`:
///
/// - `"Vision is disabled for this RAM tier. Switch to the 4-8 GB tier or
/// above to enable it."` — from `local_ai/service/assets.rs::ensure_capability_ready`
/// above to enable it."` — from `inference/local/service/assets.rs::ensure_capability_ready`
/// - `"vision summaries are unavailable for this RAM tier. Use OCR-only
/// summarization or switch to a higher local AI tier."` —
/// from `local_ai/service/vision_embed.rs::summarize`
/// from `inference/local/service/vision_embed.rs::summarize`
///
/// Anchor the classifier to that exact substring so an unrelated message
/// that merely mentions "RAM tier" out of context is not silenced.
+1 -1
View File
@@ -22,7 +22,7 @@ Multi-agent orchestration domain. Owns the LLM tool-calling loop, sub-agent disp
- `src/openhuman/tools/``Tool` / `ToolSpec` execution surface invoked from the tool loop.
- `src/openhuman/memory/` — episodic indexing + memory-loader context injection.
- `src/openhuman/context/` — prompt sections, tool-call format selection.
- `src/openhuman/local_ai/``agent_chat` / `agent_chat_simple` execution backend.
- `src/openhuman/inference/local/``agent_chat` / `agent_chat_simple` execution backend.
- `src/openhuman/config/` — runtime config load via `config::rpc::load_config_with_timeout`.
- `src/core/event_bus/` — emits `DomainEvent::Agent(*)` and `Trigger*` events; subscribers in `agent/bus.rs`.
+1 -1
View File
@@ -628,7 +628,7 @@ fn classify_error(message: String) -> ArmError {
/// `agent.run_turn`).
/// - `src/openhuman/agent/harness/session/runtime.rs` — same strings in the
/// tool-call loop, kept identical so this classifier covers both.
/// - `src/openhuman/local_ai/ops.rs` — user-facing variants with the
/// - `src/openhuman/inference/local/ops.rs` — user-facing variants with the
/// `"Please rephrase clearly."` suffix; we match the leading phrase so
/// either form classifies.
///
+1 -1
View File
@@ -17,7 +17,7 @@ Aggregator that the React shell polls every few seconds to render the OS-level c
- `src/openhuman/config/``config_rpc::*` for `Config` reads and the workspace dir resolver.
- `src/openhuman/autocomplete/``AutocompleteStatus` snapshot.
- `src/openhuman/local_ai/``LocalAiStatus` snapshot.
- `src/openhuman/inference/local/``LocalAiStatus` snapshot.
- `src/openhuman/screen_intelligence/``AccessibilityStatus` snapshot.
- `src/openhuman/service/``ServiceState` / `ServiceStatus` runtime info.
- `src/openhuman/credentials/``session_support::build_session_state` for the auth slice.
+1 -1
View File
@@ -734,7 +734,7 @@ impl AutocompleteEngine {
// Interactive variant — bypasses the scheduler_gate's LLM permit
// so per-keystroke autocomplete doesn't queue behind a memory-tree
// backfill or a triage turn. See `inline_complete_interactive`
// docs in `local_ai/service/public_infer.rs`.
// docs in `inference/local/service/public_infer.rs`.
let generated = match service
.inline_complete_interactive(
&config,
+1 -1
View File
@@ -24,7 +24,7 @@ Authoritative TOML-backed configuration layer. Owns the `Config` schema (every d
## Called by
- ~177 sites across the workspace — every domain pulls `Config` for its slice.
- Hot consumers: `src/openhuman/agent/` (model + autonomy), `src/openhuman/channels/` (provider tokens), `src/openhuman/memory/` (storage paths), `src/openhuman/cron/` (scheduler poll), `src/openhuman/local_ai/` (Ollama / device routing), `src/openhuman/security/` (sandbox backend), `src/openhuman/voice/`, `src/openhuman/notifications/`, `src/openhuman/tools/`, `src/openhuman/encryption/`, `src/openhuman/tree_summarizer/`, `src/openhuman/referral/`.
- Hot consumers: `src/openhuman/agent/` (model + autonomy), `src/openhuman/channels/` (provider tokens), `src/openhuman/memory/` (storage paths), `src/openhuman/cron/` (scheduler poll), `src/openhuman/inference/local/` (Ollama / device routing), `src/openhuman/security/` (sandbox backend), `src/openhuman/voice/`, `src/openhuman/notifications/`, `src/openhuman/tools/`, `src/openhuman/encryption/`, `src/openhuman/tree_summarizer/`, `src/openhuman/referral/`.
- `src/core/all.rs` — registers `all_config_*`.
## Tests
+1 -1
View File
@@ -24,7 +24,7 @@ AES-256-GCM at-rest crypto for AI memory storage and the encrypt/decrypt RPC sur
- `src/openhuman/credentials/` — uses the same `EncryptedPayload` / `EncryptionKey` primitives directly when storing per-channel secrets.
- `src/core/all.rs` — registers `all_encryption_*` controllers so the shell + CLI can encrypt configuration secrets.
- Indirect: `src/openhuman/memory/`, `src/openhuman/channels/`, and `src/openhuman/local_ai/` rely on the credentials domain (which in turn uses this layer) for secrets at rest.
- Indirect: `src/openhuman/memory/`, `src/openhuman/channels/`, and `src/openhuman/inference/local/` rely on the credentials domain (which in turn uses this layer) for secrets at rest.
## Tests
+6 -6
View File
@@ -1,6 +1,6 @@
# inference
Unified inference domain: the canonical home for everything LLM/STT/TTS/embedding-related. It owns the local-runtime manager (Ollama / LM Studio / Whisper / Piper), the unified cloud + local provider abstraction (trait, factory, router, reliability/retry wrapper), voice transcription and TTS inference, OpenAI/Codex subscription OAuth, and an OpenAI-compatible `/v1/chat/completions` HTTP endpoint. It consolidates the previously separate `local_ai/`, `providers/`, and inference parts of `voice/` under one domain root. The RPC surface is still split across the `inference.*` and `local_ai.*` namespaces for backwards compatibility.
Unified inference domain: the canonical home for everything LLM/STT/TTS/embedding-related. It owns the local-runtime manager (Ollama / LM Studio / Whisper / Piper), the unified cloud + local provider abstraction (trait, factory, router, reliability/retry wrapper), voice transcription and TTS inference, OpenAI/Codex subscription OAuth, and an OpenAI-compatible `/v1/chat/completions` HTTP endpoint. It consolidates the previously separate `local_ai/`, `providers/`, and inference parts of `voice/` under one domain root. The RPC surface is `inference.*`; older `local_ai_*` method names are compatibility aliases in `src/core/legacy_aliases.rs`.
## Responsibilities
@@ -33,7 +33,7 @@ Unified inference domain: the canonical home for everything LLM/STT/TTS/embeddin
| `local/` | Local runtime manager (was `local_ai/`). |
| `local/core.rs` | `LocalAiService` singleton (`global`/`try_global`), `model_artifact_path`. |
| `local/ops.rs` | Local RPC entrypoints (`local_ai_status/prompt/summarize/vision_prompt/embed/should_react`, `ReactionDecision`); re-exported as `local::rpc`. |
| `local/schemas.rs` | `local_ai.*` controller schemas + handlers. |
| `local/schemas.rs` | Local-runtime `inference.*` controller schemas + handlers. |
| `local/ollama.rs`, `local/lm_studio.rs` | Provider-specific runtime drivers; base-url resolution. |
| `local/install*.rs`, `local/voice_install_common.rs` | Whisper/Piper install + shared download logic. |
| `local/model_requirements.rs` | `MIN_CONTEXT_TOKENS`, `evaluate_context`, `ContextEligibility`. |
@@ -65,18 +65,18 @@ From `mod.rs` re-exports:
- `presets::{ModelPreset, ModelTier, VisionMode}`
- `sentiment::SentimentResult`
- `types::{LocalAiStatus, LocalAiAssetStatus, LocalAiAssetsStatus, LocalAiDownloadProgressItem, LocalAiDownloadsProgress, LocalAiEmbeddingResult, LocalAiSpeechResult, LocalAiTtsResult}`
- `local::all_local_ai_controller_schemas` / `local::all_local_ai_registered_controllers`
- `local::all_local_inference_controller_schemas` / `local::all_local_inference_registered_controllers` (legacy export names; registered schemas are in the `inference` namespace)
- `rpc` (alias for `ops`) and `all_inference_controller_schemas` / `all_inference_registered_controllers`
Provider-layer (via `provider::`): `Provider`, `ChatMessage`, `ChatRequest`, `ChatResponse`, `create_chat_provider`, `provider_for_role`, `BYOK_INCOMPLETE_SENTINEL`, plus error classifiers. Local runtime: `local::{global, try_global}``Arc<LocalAiService>`.
## RPC / controllers
Two namespaces, both wired into the controller registry (`src/core/all.rs`).
One namespace is wired into the controller registry (`src/core/all.rs`).
`inference.*` (`schemas.rs`): `status`, `get_client_config`, `update_model_settings`, `update_local_settings`, `list_models`, `device_profile`, `presets`, `apply_preset`, `diagnostics`, `openai_oauth_start`, `openai_oauth_complete`, `openai_oauth_status`, `openai_oauth_disconnect`, `summarize`, `prompt`, `vision_prompt`, `test_provider_model`, `should_react`, `analyze_sentiment`.
`inference.*` (`schemas.rs`, `local/schemas.rs`): `status`, `get_client_config`, `update_model_settings`, `update_local_settings`, `list_models`, `device_profile`, `presets`, `apply_preset`, `diagnostics`, `openai_oauth_start`, `openai_oauth_complete`, `openai_oauth_status`, `openai_oauth_disconnect`, `summarize`, `prompt`, `vision_prompt`, `test_provider_model`, `should_react`, `analyze_sentiment`, `agent_chat`, `agent_chat_simple`, `transcribe`, `transcribe_bytes`, `tts`, `assets_status`, `downloads_progress`, `download_asset`, `install_whisper`, `install_piper`, `whisper_install_status`, `piper_install_status`, `test_connection`.
`local_ai.*` (`local/schemas.rs`): `agent_chat`, `agent_chat_simple`, `transcribe`, `transcribe_bytes`, `tts`, `assets_status`, `downloads_progress`, `download_asset`, `install_whisper`, `install_piper`, `whisper_install_status`, `piper_install_status`, `test_connection`.
Legacy `openhuman.local_ai_*` and `openhuman.update_local_ai_settings` method names are rewritten to canonical `openhuman.inference_*` methods by `src/core/legacy_aliases.rs` and `app/src/services/rpcMethods.ts`.
Also exposes a non-RPC HTTP router (`http::router()`) nested at `/v1` by `src/core/jsonrpc.rs` (`/v1/chat/completions`, `/v1/models`), accepting either the core bearer or a stable external API key.
+2 -2
View File
@@ -49,8 +49,8 @@ pub use core::*;
pub use ops as rpc;
pub use ops::*;
pub use schemas::{
all_controller_schemas as all_local_ai_controller_schemas,
all_registered_controllers as all_local_ai_registered_controllers,
all_controller_schemas as all_local_inference_controller_schemas,
all_registered_controllers as all_local_inference_registered_controllers,
};
pub(crate) use service::whisper_engine;
pub use service::LocalAiService;
+48 -48
View File
@@ -67,17 +67,17 @@ pub fn all_controller_schemas() -> Vec<ControllerSchema> {
vec![
schemas("agent_chat"),
schemas("agent_chat_simple"),
schemas("local_ai_transcribe"),
schemas("local_ai_transcribe_bytes"),
schemas("local_ai_tts"),
schemas("local_ai_assets_status"),
schemas("local_ai_downloads_progress"),
schemas("local_ai_download_asset"),
schemas("local_ai_install_whisper"),
schemas("local_ai_install_piper"),
schemas("local_ai_whisper_install_status"),
schemas("local_ai_piper_install_status"),
schemas("local_ai_test_connection"),
schemas("transcribe"),
schemas("transcribe_bytes"),
schemas("tts"),
schemas("assets_status"),
schemas("downloads_progress"),
schemas("download_asset"),
schemas("install_whisper"),
schemas("install_piper"),
schemas("whisper_install_status"),
schemas("piper_install_status"),
schemas("test_connection"),
]
}
@@ -92,47 +92,47 @@ pub fn all_registered_controllers() -> Vec<RegisteredController> {
handler: handle_agent_chat_simple,
},
RegisteredController {
schema: schemas("local_ai_transcribe"),
schema: schemas("transcribe"),
handler: handle_local_ai_transcribe,
},
RegisteredController {
schema: schemas("local_ai_transcribe_bytes"),
schema: schemas("transcribe_bytes"),
handler: handle_local_ai_transcribe_bytes,
},
RegisteredController {
schema: schemas("local_ai_tts"),
schema: schemas("tts"),
handler: handle_local_ai_tts,
},
RegisteredController {
schema: schemas("local_ai_assets_status"),
schema: schemas("assets_status"),
handler: handle_local_ai_assets_status,
},
RegisteredController {
schema: schemas("local_ai_downloads_progress"),
schema: schemas("downloads_progress"),
handler: handle_local_ai_downloads_progress,
},
RegisteredController {
schema: schemas("local_ai_download_asset"),
schema: schemas("download_asset"),
handler: handle_local_ai_download_asset,
},
RegisteredController {
schema: schemas("local_ai_install_whisper"),
schema: schemas("install_whisper"),
handler: handle_local_ai_install_whisper,
},
RegisteredController {
schema: schemas("local_ai_install_piper"),
schema: schemas("install_piper"),
handler: handle_local_ai_install_piper,
},
RegisteredController {
schema: schemas("local_ai_whisper_install_status"),
schema: schemas("whisper_install_status"),
handler: handle_local_ai_whisper_install_status,
},
RegisteredController {
schema: schemas("local_ai_piper_install_status"),
schema: schemas("piper_install_status"),
handler: handle_local_ai_piper_install_status,
},
RegisteredController {
schema: schemas("local_ai_test_connection"),
schema: schemas("test_connection"),
handler: handle_local_ai_test_connection,
},
]
@@ -141,7 +141,7 @@ pub fn all_registered_controllers() -> Vec<RegisteredController> {
pub fn schemas(function: &str) -> ControllerSchema {
match function {
"agent_chat" => ControllerSchema {
namespace: "local_ai",
namespace: "inference",
function: "agent_chat",
description: "Run one-shot agent chat with optional model overrides.",
inputs: vec![
@@ -152,7 +152,7 @@ pub fn schemas(function: &str) -> ControllerSchema {
outputs: vec![json_output("response", "Agent response payload.")],
},
"agent_chat_simple" => ControllerSchema {
namespace: "local_ai",
namespace: "inference",
function: "agent_chat_simple",
description: "Run one-shot lightweight provider chat.",
inputs: vec![
@@ -162,15 +162,15 @@ pub fn schemas(function: &str) -> ControllerSchema {
],
outputs: vec![json_output("response", "Agent response payload.")],
},
"local_ai_transcribe" => ControllerSchema {
namespace: "local_ai",
"transcribe" => ControllerSchema {
namespace: "inference",
function: "transcribe",
description: "Transcribe audio from file path.",
inputs: vec![required_string("audio_path", "Input audio path.")],
outputs: vec![json_output("speech", "Transcription payload.")],
},
"local_ai_transcribe_bytes" => ControllerSchema {
namespace: "local_ai",
"transcribe_bytes" => ControllerSchema {
namespace: "inference",
function: "transcribe_bytes",
description: "Transcribe audio from raw bytes.",
inputs: vec![
@@ -184,8 +184,8 @@ pub fn schemas(function: &str) -> ControllerSchema {
],
outputs: vec![json_output("speech", "Transcription payload.")],
},
"local_ai_tts" => ControllerSchema {
namespace: "local_ai",
"tts" => ControllerSchema {
namespace: "inference",
function: "tts",
description: "Synthesize speech from text.",
inputs: vec![
@@ -194,29 +194,29 @@ pub fn schemas(function: &str) -> ControllerSchema {
],
outputs: vec![json_output("tts", "TTS result payload.")],
},
"local_ai_assets_status" => ControllerSchema {
namespace: "local_ai",
"assets_status" => ControllerSchema {
namespace: "inference",
function: "assets_status",
description: "Get local AI asset installation status.",
inputs: vec![],
outputs: vec![json_output("status", "Assets status payload.")],
},
"local_ai_downloads_progress" => ControllerSchema {
namespace: "local_ai",
"downloads_progress" => ControllerSchema {
namespace: "inference",
function: "downloads_progress",
description: "Get local AI download progress.",
inputs: vec![],
outputs: vec![json_output("progress", "Download progress payload.")],
},
"local_ai_download_asset" => ControllerSchema {
namespace: "local_ai",
"download_asset" => ControllerSchema {
namespace: "inference",
function: "download_asset",
description: "Trigger download for one local AI asset capability.",
inputs: vec![required_string("capability", "Asset capability id.")],
outputs: vec![json_output("status", "Assets status payload.")],
},
"local_ai_install_whisper" => ControllerSchema {
namespace: "local_ai",
"install_whisper" => ControllerSchema {
namespace: "inference",
function: "install_whisper",
description: "Download whisper.cpp's GGML model (and on Windows the whisper-cli binary) into the workspace so the local STT factory has everything it needs to run.",
inputs: vec![
@@ -231,8 +231,8 @@ pub fn schemas(function: &str) -> ControllerSchema {
],
outputs: vec![json_output("status", "Whisper install status payload.")],
},
"local_ai_install_piper" => ControllerSchema {
namespace: "local_ai",
"install_piper" => ControllerSchema {
namespace: "inference",
function: "install_piper",
description: "Download the Piper binary archive and the bundled en_US-lessac-medium voice files into the workspace.",
inputs: vec![
@@ -247,31 +247,31 @@ pub fn schemas(function: &str) -> ControllerSchema {
],
outputs: vec![json_output("status", "Piper install status payload.")],
},
"local_ai_whisper_install_status" => ControllerSchema {
namespace: "local_ai",
"whisper_install_status" => ControllerSchema {
namespace: "inference",
function: "whisper_install_status",
description: "Query the Whisper install state (missing / installing / installed / broken / error) plus per-stage download progress.",
inputs: vec![],
outputs: vec![json_output("status", "Whisper install status payload.")],
},
"local_ai_piper_install_status" => ControllerSchema {
namespace: "local_ai",
"piper_install_status" => ControllerSchema {
namespace: "inference",
function: "piper_install_status",
description: "Query the Piper install state (missing / installing / installed / broken / error) plus per-stage download progress.",
inputs: vec![],
outputs: vec![json_output("status", "Piper install status payload.")],
},
"local_ai_test_connection" => ControllerSchema {
namespace: "local_ai",
"test_connection" => ControllerSchema {
namespace: "inference",
function: "test_connection",
description: "Test connectivity to an Ollama server URL. Returns reachable status and model count.",
inputs: vec![required_string("url", "Ollama server URL to test.")],
outputs: vec![json_output("result", "Connection test result.")],
},
_ => ControllerSchema {
namespace: "local_ai",
namespace: "inference",
function: "unknown",
description: "Unknown local_ai controller function.",
description: "Unknown local inference controller function.",
inputs: vec![],
outputs: vec![FieldSchema {
name: "error",
+21 -18
View File
@@ -5,13 +5,16 @@ fn catalog_counts_match_and_nonempty() {
let s = all_controller_schemas();
let h = all_registered_controllers();
assert_eq!(s.len(), h.len());
assert!(s.len() >= 12, "local_ai should expose >=12 controller fns");
assert!(
s.len() >= 12,
"local inference should expose >=12 controller fns"
);
}
#[test]
fn all_schemas_use_local_ai_namespace_and_have_descriptions() {
fn all_schemas_use_inference_namespace_and_have_descriptions() {
for s in all_controller_schemas() {
assert_eq!(s.namespace, "local_ai", "function {}", s.function);
assert_eq!(s.namespace, "inference", "function {}", s.function);
assert!(!s.description.is_empty(), "function {} desc", s.function);
assert!(!s.outputs.is_empty(), "function {} outputs", s.function);
}
@@ -21,7 +24,7 @@ fn all_schemas_use_local_ai_namespace_and_have_descriptions() {
fn unknown_function_returns_unknown_schema() {
let s = schemas("no_such_fn");
assert_eq!(s.function, "unknown");
assert_eq!(s.namespace, "local_ai");
assert_eq!(s.namespace, "inference");
}
#[test]
@@ -29,29 +32,29 @@ fn every_registered_key_resolves_to_non_unknown_schema() {
let keys = [
"agent_chat",
"agent_chat_simple",
"local_ai_transcribe",
"local_ai_transcribe_bytes",
"local_ai_tts",
"local_ai_assets_status",
"local_ai_downloads_progress",
"local_ai_download_asset",
"local_ai_install_whisper",
"local_ai_install_piper",
"local_ai_whisper_install_status",
"local_ai_piper_install_status",
"local_ai_test_connection",
"transcribe",
"transcribe_bytes",
"tts",
"assets_status",
"downloads_progress",
"download_asset",
"install_whisper",
"install_piper",
"whisper_install_status",
"piper_install_status",
"test_connection",
];
for k in keys {
let s = schemas(k);
assert_eq!(s.namespace, "local_ai");
assert_eq!(s.namespace, "inference");
assert_ne!(s.function, "unknown", "key `{k}` fell through");
}
}
#[test]
fn registered_controllers_all_in_local_ai_namespace() {
fn registered_controllers_all_in_inference_namespace() {
for h in all_registered_controllers() {
assert_eq!(h.schema.namespace, "local_ai");
assert_eq!(h.schema.namespace, "inference");
assert!(!h.schema.function.is_empty());
}
}
+4 -4
View File
@@ -9,8 +9,8 @@
//! (moved from `src/openhuman/voice/`)
//! - `http/` — OpenAI-compatible `/v1/chat/completions` endpoint
//!
//! The RPC surface remains under the `inference.*` and `local_ai.*` namespaces
//! for backwards compatibility.
//! The RPC surface is `inference.*`; old `local_ai_*` RPC names are resolved
//! by the legacy alias layer for backwards compatibility.
pub mod device;
pub mod http;
@@ -36,8 +36,8 @@ pub use schemas::{
// Re-export the types that external callers (voice, agent, etc.) import from inference
pub use device::DeviceProfile;
pub use local::all_local_ai_controller_schemas;
pub use local::all_local_ai_registered_controllers;
pub use local::all_local_inference_controller_schemas;
pub use local::all_local_inference_registered_controllers;
pub use model_context::context_window_for_model;
pub use presets::{ModelPreset, ModelTier, VisionMode};
pub use sentiment::SentimentResult;
@@ -39,7 +39,7 @@
//! into `~/.openhuman/bin/piper/`, extracts it, and stages the bundled
//! `en_US-lessac-medium` voice (`.onnx` + `.onnx.json`) alongside via a
//! `.part` file + atomic rename. After install the `resolve_piper_binary`
//! helper in `local_ai/paths.rs` picks it up automatically.
//! helper in `inference/paths.rs` picks it up automatically.
//!
//! **Advanced path:** download Piper from
//! [rhasspy/piper](https://github.com/rhasspy/piper) releases (one
@@ -20,7 +20,7 @@
//! `~/.openhuman/bin/whisper/` via a `.part` file + atomic rename, plus
//! the `whisper-cli` binary on Windows where upstream ships a release
//! archive. After install the `resolve_whisper_binary` helper in
//! `local_ai/paths.rs` picks it up automatically — no env var to set.
//! `inference/paths.rs` picks it up automatically — no env var to set.
//!
//! **Advanced path:** install whisper.cpp's `whisper-cli` from a package
//! manager (`brew install whisper-cpp`, `pacman -S whisper.cpp`, …) or
+5 -4
View File
@@ -172,7 +172,8 @@ use openhuman_core::openhuman::inference::voice::local_speech::{synthesize_piper
use openhuman_core::openhuman::inference::voice::postprocess::cleanup_transcription;
use openhuman_core::openhuman::inference::{
all_inference_controller_schemas, all_inference_registered_controllers,
all_local_ai_controller_schemas, all_local_ai_registered_controllers, DeviceProfile,
all_local_inference_controller_schemas, all_local_inference_registered_controllers,
DeviceProfile,
};
use openhuman_core::openhuman::memory::{Memory, MemoryCategory, MemoryEntry, RecallOpts};
use openhuman_core::openhuman::security::SecurityPolicy;
@@ -3018,13 +3019,13 @@ async fn inference_local_controllers_and_presets_cover_public_paths() {
let _ollama_bin_guard = EnvVarGuard::set("OLLAMA_BIN", &mock_ollama);
let _ollama_base_guard = EnvVarGuard::set("OPENHUMAN_OLLAMA_BASE_URL", &provider_base);
let local_schemas = all_local_ai_controller_schemas();
let local_registered = all_local_ai_registered_controllers();
let local_schemas = all_local_inference_controller_schemas();
let local_registered = all_local_inference_registered_controllers();
assert_eq!(local_schemas.len(), local_registered.len());
assert!(local_registered.iter().all(|controller| {
controller
.rpc_method_name()
.starts_with("openhuman.local_ai_")
.starts_with("openhuman.inference_")
}));
let reachable = call(
@@ -20,7 +20,7 @@ use openhuman_core::openhuman::config::schema::cloud_providers::{
};
use openhuman_core::openhuman::config::Config;
use openhuman_core::openhuman::credentials::{AuthService, DEFAULT_AUTH_PROFILE_NAME};
use openhuman_core::openhuman::inference::local::all_local_ai_registered_controllers;
use openhuman_core::openhuman::inference::local::all_local_inference_registered_controllers;
use openhuman_core::openhuman::inference::ops::inference_test_provider_model;
use openhuman_core::openhuman::inference::provider::compatible::{
AuthStyle as CompatibleAuthStyle, OpenAiCompatibleProvider,
@@ -226,7 +226,7 @@ async fn provider_admin_cold_paths_cover_model_errors_local_factory_and_connecti
.expect_err("empty lmstudio model");
assert!(empty_lmstudio.contains("empty model"));
let controllers = all_local_ai_registered_controllers();
let controllers = all_local_inference_registered_controllers();
let test_connection = controller(&controllers, "test_connection");
let reachable = call(test_connection, json!({"url": base}))
.await
@@ -18,7 +18,7 @@ use flate2::Compression;
use openhuman_core::core::all::RegisteredController;
use openhuman_core::openhuman::config::Config;
use openhuman_core::openhuman::inference::local::{
all_local_ai_registered_controllers, local_ai_transcribe_bytes,
all_local_inference_registered_controllers, local_ai_transcribe_bytes,
};
use serde_json::{json, Value};
use tempfile::{tempdir, TempDir};
@@ -107,7 +107,7 @@ async fn piper_controller_installs_skips_existing_and_records_failures_from_mock
let _piper_bin = EnvVarGuard::unset("PIPER_BIN");
let _whisper_bin = EnvVarGuard::unset("WHISPER_BIN");
let controllers = all_local_ai_registered_controllers();
let controllers = all_local_inference_registered_controllers();
let install = controller(&controllers, "install_piper");
let status = controller(&controllers, "piper_install_status");
@@ -21,7 +21,7 @@ use openhuman_core::openhuman::inference::local::ops::{
LocalAiChatMessage,
};
use openhuman_core::openhuman::inference::local::{
all_local_ai_registered_controllers, LocalAiService,
all_local_inference_registered_controllers, LocalAiService,
};
use serde_json::{json, Value};
use tempfile::{tempdir, TempDir};
@@ -295,7 +295,7 @@ async fn local_services_cover_mocked_inference_assets_speech_and_whisper_install
"ready"
);
let controllers = all_local_ai_registered_controllers();
let controllers = all_local_inference_registered_controllers();
let install = controller(&controllers, "install_whisper");
let status = controller(&controllers, "whisper_install_status");
+3 -3
View File
@@ -1764,7 +1764,7 @@ async fn json_rpc_protocol_auth_and_agent_hello_inner() {
let chat = post_json_rpc(
&rpc_base,
5,
"openhuman.local_ai_agent_chat",
"openhuman.inference_agent_chat",
json!({
"message": "Hello",
}),
@@ -2207,14 +2207,14 @@ async fn json_rpc_prompt_injection_is_rejected_before_model_call() {
let blocked_agent = post_json_rpc(
&rpc_base,
4003,
"openhuman.local_ai_agent_chat",
"openhuman.inference_agent_chat",
json!({
"message": payload,
"model_override": "e2e-mock-model",
}),
)
.await;
let agent_err = assert_jsonrpc_error(&blocked_agent, "local_ai_agent_chat blocked");
let agent_err = assert_jsonrpc_error(&blocked_agent, "inference_agent_chat blocked");
let agent_msg = agent_err
.get("message")
.and_then(Value::as_str)