@gt-fe/eap-runtime 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +323 -0
- package/dist/api/handlers/index.d.ts +3 -0
- package/dist/api/handlers/index.js +4 -0
- package/dist/api/handlers/invoke.handler.d.ts +77 -0
- package/dist/api/handlers/invoke.handler.js +411 -0
- package/dist/api/handlers/messages.handler.d.ts +53 -0
- package/dist/api/handlers/messages.handler.js +80 -0
- package/dist/api/handlers/stream.handler.d.ts +92 -0
- package/dist/api/handlers/stream.handler.js +432 -0
- package/dist/api/index.d.ts +3 -0
- package/dist/api/index.js +4 -0
- package/dist/api/middleware/auth.middleware.d.ts +96 -0
- package/dist/api/middleware/auth.middleware.js +158 -0
- package/dist/api/middleware/index.d.ts +1 -0
- package/dist/api/middleware/index.js +2 -0
- package/dist/api/routes/agent.routes.d.ts +44 -0
- package/dist/api/routes/agent.routes.js +204 -0
- package/dist/api/routes/index.d.ts +3 -0
- package/dist/api/routes/index.js +4 -0
- package/dist/api/routes/session.routes.d.ts +82 -0
- package/dist/api/routes/session.routes.js +169 -0
- package/dist/api/routes/task.routes.d.ts +75 -0
- package/dist/api/routes/task.routes.js +108 -0
- package/dist/checkpoint/checkpoint-serializer.d.ts +58 -0
- package/dist/checkpoint/checkpoint-serializer.js +81 -0
- package/dist/checkpoint/composite-checkpoint-saver.d.ts +42 -0
- package/dist/checkpoint/composite-checkpoint-saver.js +118 -0
- package/dist/checkpoint/contracts/checkpoint-saver.contract.d.ts +65 -0
- package/dist/checkpoint/contracts/checkpoint-saver.contract.js +12 -0
- package/dist/checkpoint/contracts/index.d.ts +1 -0
- package/dist/checkpoint/contracts/index.js +1 -0
- package/dist/checkpoint/file-checkpoint-saver.d.ts +19 -0
- package/dist/checkpoint/file-checkpoint-saver.js +98 -0
- package/dist/checkpoint/hybrid-checkpoint-saver.d.ts +130 -0
- package/dist/checkpoint/hybrid-checkpoint-saver.js +211 -0
- package/dist/checkpoint/in-memory-checkpoint-saver.d.ts +14 -0
- package/dist/checkpoint/in-memory-checkpoint-saver.js +46 -0
- package/dist/checkpoint/index.d.ts +16 -0
- package/dist/checkpoint/index.js +13 -0
- package/dist/checkpoint/mongo-checkpoint-saver.d.ts +38 -0
- package/dist/checkpoint/mongo-checkpoint-saver.js +56 -0
- package/dist/checkpoint/pg-checkpoint-saver.d.ts +59 -0
- package/dist/checkpoint/pg-checkpoint-saver.js +140 -0
- package/dist/checkpoint/redis-checkpoint-saver.d.ts +68 -0
- package/dist/checkpoint/redis-checkpoint-saver.js +173 -0
- package/dist/checkpoint/restore-checkpoint.d.ts +33 -0
- package/dist/checkpoint/restore-checkpoint.js +33 -0
- package/dist/common/agent-code.d.ts +10 -0
- package/dist/common/agent-code.js +12 -0
- package/dist/common/concurrency.d.ts +79 -0
- package/dist/common/concurrency.js +121 -0
- package/dist/common/health.d.ts +40 -0
- package/dist/common/health.js +37 -0
- package/dist/common/index.d.ts +5 -0
- package/dist/common/index.js +5 -0
- package/dist/common/logger.d.ts +34 -0
- package/dist/common/logger.js +38 -0
- package/dist/common/queue-consumer.d.ts +125 -0
- package/dist/common/queue-consumer.js +159 -0
- package/dist/common/trace-id.d.ts +2 -0
- package/dist/common/trace-id.js +8 -0
- package/dist/common/types.d.ts +104 -0
- package/dist/common/types.js +14 -0
- package/dist/contracts/index.d.ts +11 -0
- package/dist/contracts/index.js +10 -0
- package/dist/contracts/permission-checker.d.ts +10 -0
- package/dist/contracts/permission-checker.js +4 -0
- package/dist/contracts/trace-collector.d.ts +211 -0
- package/dist/contracts/trace-collector.js +13 -0
- package/dist/governance/governance-client.d.ts +38 -0
- package/dist/governance/governance-client.js +95 -0
- package/dist/governance/index.d.ts +7 -0
- package/dist/governance/index.js +5 -0
- package/dist/governance/permission-guard.d.ts +19 -0
- package/dist/governance/permission-guard.js +59 -0
- package/dist/graph/adapters/index.d.ts +3 -0
- package/dist/graph/adapters/index.js +3 -0
- package/dist/graph/adapters/langgraph-checkpointer.d.ts +40 -0
- package/dist/graph/adapters/langgraph-checkpointer.js +211 -0
- package/dist/graph/adapters/langgraph.adapter.d.ts +190 -0
- package/dist/graph/adapters/langgraph.adapter.js +1303 -0
- package/dist/graph/adapters/subgraph-compiler.d.ts +39 -0
- package/dist/graph/adapters/subgraph-compiler.js +248 -0
- package/dist/graph/condition-evaluator.d.ts +57 -0
- package/dist/graph/condition-evaluator.js +162 -0
- package/dist/graph/contracts/executable-graph.d.ts +340 -0
- package/dist/graph/contracts/executable-graph.js +10 -0
- package/dist/graph/contracts/index.d.ts +2 -0
- package/dist/graph/contracts/index.js +2 -0
- package/dist/graph/contracts/state-graph.contract.d.ts +76 -0
- package/dist/graph/contracts/state-graph.contract.js +11 -0
- package/dist/graph/hitl/hitl-handler.d.ts +199 -0
- package/dist/graph/hitl/hitl-handler.js +480 -0
- package/dist/graph/hitl/index.d.ts +1 -0
- package/dist/graph/hitl/index.js +1 -0
- package/dist/graph/index.d.ts +7 -0
- package/dist/graph/index.js +7 -0
- package/dist/graph/nodes/index.d.ts +2 -0
- package/dist/graph/nodes/index.js +2 -0
- package/dist/graph/nodes/llm-node.d.ts +182 -0
- package/dist/graph/nodes/llm-node.js +281 -0
- package/dist/graph/nodes/skill-bind-node.d.ts +32 -0
- package/dist/graph/nodes/skill-bind-node.js +116 -0
- package/dist/graph/nodes/tool-node.d.ts +75 -0
- package/dist/graph/nodes/tool-node.js +324 -0
- package/dist/graph/resolve-graph-prompt.d.ts +5 -0
- package/dist/graph/resolve-graph-prompt.js +75 -0
- package/dist/graph/retry/budget-aware-retry.d.ts +91 -0
- package/dist/graph/retry/budget-aware-retry.js +126 -0
- package/dist/graph/retry/index.d.ts +9 -0
- package/dist/graph/retry/index.js +8 -0
- package/dist/graph/retry/node-retry.d.ts +123 -0
- package/dist/graph/retry/node-retry.js +235 -0
- package/dist/graph/skill-audit-recorder.d.ts +126 -0
- package/dist/graph/skill-audit-recorder.js +114 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +32 -0
- package/dist/memory/compression/compression-stats.d.ts +109 -0
- package/dist/memory/compression/compression-stats.js +144 -0
- package/dist/memory/compression/compression.repository.d.ts +62 -0
- package/dist/memory/compression/compression.repository.js +71 -0
- package/dist/memory/compression/index.d.ts +10 -0
- package/dist/memory/compression/index.js +15 -0
- package/dist/memory/compression/llm-summarize.d.ts +58 -0
- package/dist/memory/compression/llm-summarize.js +192 -0
- package/dist/memory/compression/sliding-window.compression.d.ts +60 -0
- package/dist/memory/compression/sliding-window.compression.js +175 -0
- package/dist/memory/index.d.ts +4 -0
- package/dist/memory/index.js +7 -0
- package/dist/memory/long-term-memory.service.d.ts +94 -0
- package/dist/memory/long-term-memory.service.js +120 -0
- package/dist/memory/message-compression.service.d.ts +128 -0
- package/dist/memory/message-compression.service.js +69 -0
- package/dist/memory/pg-memory.repository.d.ts +44 -0
- package/dist/memory/pg-memory.repository.js +108 -0
- package/dist/memory/semantic-retrieval.service.d.ts +148 -0
- package/dist/memory/semantic-retrieval.service.js +238 -0
- package/dist/providers/echo-llm-provider.d.ts +10 -0
- package/dist/providers/echo-llm-provider.js +87 -0
- package/dist/providers/http-llm-provider.d.ts +35 -0
- package/dist/providers/http-llm-provider.js +404 -0
- package/dist/providers/index.d.ts +7 -0
- package/dist/providers/index.js +5 -0
- package/dist/providers/local-llm-provider.d.ts +27 -0
- package/dist/providers/local-llm-provider.js +105 -0
- package/dist/providers/resolve-llm-model.d.ts +13 -0
- package/dist/providers/resolve-llm-model.js +30 -0
- package/dist/providers/routing-llm-provider.d.ts +28 -0
- package/dist/providers/routing-llm-provider.js +79 -0
- package/dist/registry/agent-models.d.ts +1 -0
- package/dist/registry/agent-models.js +1 -0
- package/dist/registry/agent-registry-client.d.ts +56 -0
- package/dist/registry/agent-registry-client.js +36 -0
- package/dist/registry/definition-cache.d.ts +37 -0
- package/dist/registry/definition-cache.js +82 -0
- package/dist/registry/deployment-snapshot.d.ts +99 -0
- package/dist/registry/deployment-snapshot.js +542 -0
- package/dist/registry/graph-loader.d.ts +134 -0
- package/dist/registry/graph-loader.js +985 -0
- package/dist/registry/index.d.ts +9 -0
- package/dist/registry/index.js +9 -0
- package/dist/registry/preload-errors.d.ts +13 -0
- package/dist/registry/preload-errors.js +35 -0
- package/dist/registry/preload-flags.d.ts +22 -0
- package/dist/registry/preload-flags.js +44 -0
- package/dist/registry/skill-io-guard.d.ts +25 -0
- package/dist/registry/skill-io-guard.js +98 -0
- package/dist/registry/skill-tree.d.ts +27 -0
- package/dist/registry/skill-tree.js +183 -0
- package/dist/runtime/agent-runtime.d.ts +106 -0
- package/dist/runtime/agent-runtime.js +777 -0
- package/dist/runtime/dev-runtime.d.ts +69 -0
- package/dist/runtime/dev-runtime.js +199 -0
- package/dist/runtime/index.d.ts +4 -0
- package/dist/runtime/index.js +3 -0
- package/dist/runtime/storage-profile.d.ts +50 -0
- package/dist/runtime/storage-profile.js +138 -0
- package/dist/runtime/types.d.ts +120 -0
- package/dist/runtime/types.js +9 -0
- package/dist/server.d.ts +12 -0
- package/dist/server.js +161 -0
- package/dist/session/compression-middleware.d.ts +126 -0
- package/dist/session/compression-middleware.js +230 -0
- package/dist/session/file-message.repository.d.ts +25 -0
- package/dist/session/file-message.repository.js +78 -0
- package/dist/session/file-session.repository.d.ts +72 -0
- package/dist/session/file-session.repository.js +212 -0
- package/dist/session/index.d.ts +13 -0
- package/dist/session/index.js +12 -0
- package/dist/session/message.service.d.ts +80 -0
- package/dist/session/message.service.js +128 -0
- package/dist/session/persist-turn-messages.d.ts +145 -0
- package/dist/session/persist-turn-messages.js +325 -0
- package/dist/session/pg-message.repository.d.ts +49 -0
- package/dist/session/pg-message.repository.js +149 -0
- package/dist/session/pg-session.repository.d.ts +46 -0
- package/dist/session/pg-session.repository.js +117 -0
- package/dist/session/session-context.service.d.ts +49 -0
- package/dist/session/session-context.service.js +66 -0
- package/dist/session/session.service.d.ts +194 -0
- package/dist/session/session.service.js +166 -0
- package/dist/sse/connection-event-logger.d.ts +21 -0
- package/dist/sse/connection-event-logger.js +33 -0
- package/dist/sse/contracts/index.d.ts +1 -0
- package/dist/sse/contracts/index.js +1 -0
- package/dist/sse/contracts/sse-emitter.contract.d.ts +74 -0
- package/dist/sse/contracts/sse-emitter.contract.js +11 -0
- package/dist/sse/event-buffer.d.ts +20 -0
- package/dist/sse/event-buffer.js +45 -0
- package/dist/sse/events/code-events.d.ts +19 -0
- package/dist/sse/events/code-events.js +9 -0
- package/dist/sse/events/collaboration-events.d.ts +37 -0
- package/dist/sse/events/collaboration-events.js +11 -0
- package/dist/sse/events/connection-events.d.ts +13 -0
- package/dist/sse/events/connection-events.js +9 -0
- package/dist/sse/events/file-events.d.ts +29 -0
- package/dist/sse/events/file-events.js +10 -0
- package/dist/sse/events/hitl-events.d.ts +33 -0
- package/dist/sse/events/hitl-events.js +10 -0
- package/dist/sse/events/index.d.ts +107 -0
- package/dist/sse/events/index.js +96 -0
- package/dist/sse/events/plan-events.d.ts +38 -0
- package/dist/sse/events/plan-events.js +11 -0
- package/dist/sse/events/reasoning-events.d.ts +35 -0
- package/dist/sse/events/reasoning-events.js +13 -0
- package/dist/sse/events/response-events.d.ts +25 -0
- package/dist/sse/events/response-events.js +12 -0
- package/dist/sse/events/session-events.d.ts +43 -0
- package/dist/sse/events/session-events.js +12 -0
- package/dist/sse/events/skill-events.d.ts +48 -0
- package/dist/sse/events/skill-events.js +11 -0
- package/dist/sse/events/stats-events.d.ts +44 -0
- package/dist/sse/events/stats-events.js +12 -0
- package/dist/sse/events/tool-events.d.ts +68 -0
- package/dist/sse/events/tool-events.js +14 -0
- package/dist/sse/index.d.ts +8 -0
- package/dist/sse/index.js +8 -0
- package/dist/sse/sse-connection-manager.d.ts +116 -0
- package/dist/sse/sse-connection-manager.js +185 -0
- package/dist/sse/sse-emitter.d.ts +108 -0
- package/dist/sse/sse-emitter.js +210 -0
- package/dist/sse/sse-heartbeat.d.ts +58 -0
- package/dist/sse/sse-heartbeat.js +76 -0
- package/dist/sse/trace-collector.d.ts +60 -0
- package/dist/sse/trace-collector.js +107 -0
- package/dist/task/contracts/index.d.ts +2 -0
- package/dist/task/contracts/index.js +1 -0
- package/dist/task/contracts/task-manager.contract.d.ts +160 -0
- package/dist/task/contracts/task-manager.contract.js +10 -0
- package/dist/task/index.d.ts +7 -0
- package/dist/task/index.js +5 -0
- package/dist/task/pg-task.repository.d.ts +47 -0
- package/dist/task/pg-task.repository.js +131 -0
- package/dist/task/task-progress-tracker.d.ts +67 -0
- package/dist/task/task-progress-tracker.js +80 -0
- package/dist/task/task-timeout.d.ts +70 -0
- package/dist/task/task-timeout.js +116 -0
- package/dist/task/task.service.d.ts +84 -0
- package/dist/task/task.service.js +233 -0
- package/dist/token/compression-budget-integration.d.ts +145 -0
- package/dist/token/compression-budget-integration.js +185 -0
- package/dist/token/context-window-budget.d.ts +113 -0
- package/dist/token/context-window-budget.js +134 -0
- package/dist/token/contracts/index.d.ts +1 -0
- package/dist/token/contracts/index.js +1 -0
- package/dist/token/contracts/token-budget.contract.d.ts +77 -0
- package/dist/token/contracts/token-budget.contract.js +11 -0
- package/dist/token/index.d.ts +12 -0
- package/dist/token/index.js +10 -0
- package/dist/token/pg-token-budget.repository.d.ts +39 -0
- package/dist/token/pg-token-budget.repository.js +116 -0
- package/dist/token/session-budget-checker.d.ts +54 -0
- package/dist/token/session-budget-checker.js +72 -0
- package/dist/token/token-budget-guard.d.ts +66 -0
- package/dist/token/token-budget-guard.js +97 -0
- package/dist/token/token-budget.service.d.ts +86 -0
- package/dist/token/token-budget.service.js +130 -0
- package/dist/token/token-detail-mode.d.ts +73 -0
- package/dist/token/token-detail-mode.js +107 -0
- package/dist/token/token-estimator.d.ts +80 -0
- package/dist/token/token-estimator.js +134 -0
- package/dist/token/token-stats.service.d.ts +88 -0
- package/dist/token/token-stats.service.js +94 -0
- package/dist/tool/create-tool-executor.d.ts +33 -0
- package/dist/tool/create-tool-executor.js +60 -0
- package/dist/tool/dev-http-executor.d.ts +18 -0
- package/dist/tool/dev-http-executor.js +96 -0
- package/dist/tool/dev-mcp-executor.d.ts +29 -0
- package/dist/tool/dev-mcp-executor.js +84 -0
- package/dist/tool/dev-tool-executor.d.ts +23 -0
- package/dist/tool/dev-tool-executor.js +55 -0
- package/dist/tool/file-artifact.d.ts +11 -0
- package/dist/tool/file-artifact.js +90 -0
- package/dist/tool/http-tool-executor.d.ts +17 -0
- package/dist/tool/http-tool-executor.js +158 -0
- package/dist/tool/index.d.ts +18 -0
- package/dist/tool/index.js +17 -0
- package/dist/tool/loaded-tool-definition.d.ts +35 -0
- package/dist/tool/loaded-tool-definition.js +9 -0
- package/dist/tool/local-mcp-client.d.ts +23 -0
- package/dist/tool/local-mcp-client.js +225 -0
- package/dist/tool/local-tool-executor.d.ts +37 -0
- package/dist/tool/local-tool-executor.js +249 -0
- package/dist/tool/mcp-tool-executor.d.ts +23 -0
- package/dist/tool/mcp-tool-executor.js +160 -0
- package/dist/tool/mock-tool-executor.d.ts +28 -0
- package/dist/tool/mock-tool-executor.js +65 -0
- package/dist/tool/system-prompt-builder.d.ts +114 -0
- package/dist/tool/system-prompt-builder.js +213 -0
- package/dist/tool/tool-confirmation.d.ts +29 -0
- package/dist/tool/tool-confirmation.js +81 -0
- package/dist/tool/tool-event-emitter.d.ts +58 -0
- package/dist/tool/tool-event-emitter.js +154 -0
- package/dist/tool/tool-execution-router.d.ts +25 -0
- package/dist/tool/tool-execution-router.js +50 -0
- package/dist/tool/tool-executor.interface.d.ts +85 -0
- package/dist/tool/tool-executor.interface.js +7 -0
- package/dist/tool/tool-orchestrator.d.ts +22 -0
- package/dist/tool/tool-orchestrator.js +104 -0
- package/dist/tool/tool-result-injector.d.ts +64 -0
- package/dist/tool/tool-result-injector.js +65 -0
- package/dist/utils/format-error.d.ts +4 -0
- package/dist/utils/format-error.js +4 -0
- package/dist/utils/http-error.d.ts +11 -0
- package/dist/utils/http-error.js +24 -0
- package/package.json +56 -0
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP LLM Provider — OpenAI 兼容 API
|
|
3
|
+
*
|
|
4
|
+
* 支持:
|
|
5
|
+
* - 真流式输出(stream: true + SSE chunk 解析)
|
|
6
|
+
* - function calling(tools / tool_choice 参数)
|
|
7
|
+
* - tool_calls 解析(同步 & 流式)
|
|
8
|
+
*/
|
|
9
|
+
import { resolveLlmModelName } from './resolve-llm-model.js';
|
|
10
|
+
export class HttpLLMProvider {
|
|
11
|
+
apiKey;
|
|
12
|
+
baseUrl;
|
|
13
|
+
defaultModel;
|
|
14
|
+
timeoutMs;
|
|
15
|
+
constructor(config) {
|
|
16
|
+
this.apiKey = config.apiKey;
|
|
17
|
+
this.baseUrl = (config.baseUrl ?? 'https://api.openai.com/v1').replace(/\/$/, '');
|
|
18
|
+
this.defaultModel = config.defaultModel ?? 'DeepSeek-V4-Pro';
|
|
19
|
+
this.timeoutMs = config.timeoutMs ?? 60000;
|
|
20
|
+
}
|
|
21
|
+
async invoke(messages, config) {
|
|
22
|
+
const body = this.buildRequestBody(messages, config, false);
|
|
23
|
+
// [DEBUG] 打印真实发给 LLM 的请求(tools/system 是否齐全,定位 XML 伪 tool_call 问题)
|
|
24
|
+
if (process.env.EAP_LLM_DEBUG === '1') {
|
|
25
|
+
const firstMsg = Array.isArray(body.messages) && body.messages.length > 0
|
|
26
|
+
? body.messages[0]
|
|
27
|
+
: null;
|
|
28
|
+
const systemHead = firstMsg && typeof firstMsg.content === 'string'
|
|
29
|
+
? firstMsg.content.slice(0, 300)
|
|
30
|
+
: '';
|
|
31
|
+
console.log('[LLM-DEBUG] invoke request:', JSON.stringify({
|
|
32
|
+
model: body.model,
|
|
33
|
+
toolsCount: Array.isArray(body.tools) ? body.tools.length : 0,
|
|
34
|
+
toolNames: Array.isArray(body.tools)
|
|
35
|
+
? body.tools.map(t => t?.function?.name)
|
|
36
|
+
: [],
|
|
37
|
+
messages: body.messages.map((m) => ({
|
|
38
|
+
role: m.role,
|
|
39
|
+
contentLen: String(m.content ?? '').length,
|
|
40
|
+
hasToolCalls: Boolean(m.tool_calls || m.toolCalls),
|
|
41
|
+
toolCallId: m.tool_call_id ?? m.toolCallId,
|
|
42
|
+
hasReasoning: typeof m.reasoning_content === 'string' || typeof m.reasoningContent === 'string',
|
|
43
|
+
reasoningLen: typeof m.reasoning_content === 'string'
|
|
44
|
+
? m.reasoning_content.length
|
|
45
|
+
: typeof m.reasoningContent === 'string'
|
|
46
|
+
? m.reasoningContent.length
|
|
47
|
+
: 0,
|
|
48
|
+
})),
|
|
49
|
+
systemPromptHead: systemHead,
|
|
50
|
+
}, null, 2));
|
|
51
|
+
}
|
|
52
|
+
const controller = new AbortController();
|
|
53
|
+
const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
54
|
+
const url = `${this.baseUrl}/chat/completions`;
|
|
55
|
+
try {
|
|
56
|
+
let response;
|
|
57
|
+
try {
|
|
58
|
+
response = await fetch(url, {
|
|
59
|
+
method: 'POST',
|
|
60
|
+
headers: {
|
|
61
|
+
'Content-Type': 'application/json',
|
|
62
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
63
|
+
},
|
|
64
|
+
body: JSON.stringify(body),
|
|
65
|
+
signal: controller.signal,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
throw new Error(this.formatNetworkError(url, err));
|
|
70
|
+
}
|
|
71
|
+
if (!response.ok) {
|
|
72
|
+
const errText = await response.text();
|
|
73
|
+
throw new Error(`LLM API error ${response.status}: ${errText}`);
|
|
74
|
+
}
|
|
75
|
+
const data = (await response.json());
|
|
76
|
+
const choice = data.choices?.[0];
|
|
77
|
+
const content = choice?.message?.content ?? '';
|
|
78
|
+
const reasoningContent = typeof choice?.message?.reasoning_content === 'string'
|
|
79
|
+
? choice.message.reasoning_content
|
|
80
|
+
: undefined;
|
|
81
|
+
const toolCalls = this.parseToolCalls(choice?.message?.tool_calls);
|
|
82
|
+
// [DEBUG] 打印 LLM 返回的原始 tool_calls 与 finish_reason
|
|
83
|
+
if (process.env.EAP_LLM_DEBUG === '1') {
|
|
84
|
+
console.log('[LLM-DEBUG] invoke response:', JSON.stringify({
|
|
85
|
+
finishReason: choice?.finish_reason,
|
|
86
|
+
toolCallsCount: toolCalls.length,
|
|
87
|
+
toolCalls: toolCalls.map(tc => ({ id: tc.id, name: tc.name })),
|
|
88
|
+
contentHead: content.slice(0, 300),
|
|
89
|
+
reasoningLen: reasoningContent?.length ?? 0,
|
|
90
|
+
usage: data.usage,
|
|
91
|
+
}, null, 2));
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
content,
|
|
95
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
96
|
+
...(reasoningContent ? { reasoningContent } : {}),
|
|
97
|
+
inputTokens: data.usage?.prompt_tokens ?? 0,
|
|
98
|
+
outputTokens: data.usage?.completion_tokens ?? 0,
|
|
99
|
+
// model 字段优先级:config(用户显式配置) > data.model(网关返回,可能是路由别名如 "/mnt/fast") > defaultModel
|
|
100
|
+
model: config.model ?? (typeof data.model === 'string' && !data.model.includes('/') ? data.model : undefined) ?? this.defaultModel,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
finally {
|
|
104
|
+
clearTimeout(timeout);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async *stream(messages, config) {
|
|
108
|
+
const body = this.buildRequestBody(messages, config, true);
|
|
109
|
+
// [DEBUG] 打印流式请求(与 invoke 一致)
|
|
110
|
+
if (process.env.EAP_LLM_DEBUG === '1') {
|
|
111
|
+
const firstMsg = Array.isArray(body.messages) && body.messages.length > 0
|
|
112
|
+
? body.messages[0]
|
|
113
|
+
: null;
|
|
114
|
+
const systemHead = firstMsg && typeof firstMsg.content === 'string'
|
|
115
|
+
? firstMsg.content.slice(0, 300)
|
|
116
|
+
: '';
|
|
117
|
+
console.log('[LLM-DEBUG] stream request:', JSON.stringify({
|
|
118
|
+
model: body.model,
|
|
119
|
+
toolsCount: Array.isArray(body.tools) ? body.tools.length : 0,
|
|
120
|
+
toolNames: Array.isArray(body.tools)
|
|
121
|
+
? body.tools.map(t => t?.function?.name)
|
|
122
|
+
: [],
|
|
123
|
+
messagesCount: Array.isArray(body.messages) ? body.messages.length : 0,
|
|
124
|
+
messages: Array.isArray(body.messages)
|
|
125
|
+
? body.messages.map((m) => ({
|
|
126
|
+
role: m.role,
|
|
127
|
+
contentLen: String(m.content ?? '').length,
|
|
128
|
+
hasToolCalls: Boolean(m.tool_calls || m.toolCalls),
|
|
129
|
+
hasReasoning: typeof m.reasoning_content === 'string',
|
|
130
|
+
reasoningLen: typeof m.reasoning_content === 'string' ? m.reasoning_content.length : 0,
|
|
131
|
+
}))
|
|
132
|
+
: [],
|
|
133
|
+
systemPromptHead: systemHead,
|
|
134
|
+
}, null, 2));
|
|
135
|
+
}
|
|
136
|
+
const controller = new AbortController();
|
|
137
|
+
const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
138
|
+
const url = `${this.baseUrl}/chat/completions`;
|
|
139
|
+
try {
|
|
140
|
+
let response;
|
|
141
|
+
try {
|
|
142
|
+
response = await fetch(url, {
|
|
143
|
+
method: 'POST',
|
|
144
|
+
headers: {
|
|
145
|
+
'Content-Type': 'application/json',
|
|
146
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
147
|
+
},
|
|
148
|
+
body: JSON.stringify(body),
|
|
149
|
+
signal: controller.signal,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
throw new Error(this.formatNetworkError(url, err));
|
|
154
|
+
}
|
|
155
|
+
if (!response.ok) {
|
|
156
|
+
const errText = await response.text();
|
|
157
|
+
throw new Error(`LLM API error ${response.status}: ${errText}`);
|
|
158
|
+
}
|
|
159
|
+
if (!response.body) {
|
|
160
|
+
throw new Error('LLM API returned no response body for streaming');
|
|
161
|
+
}
|
|
162
|
+
// 真流式:逐 SSE chunk 解析
|
|
163
|
+
const reader = response.body.getReader();
|
|
164
|
+
const decoder = new TextDecoder();
|
|
165
|
+
let buffer = '';
|
|
166
|
+
const toolCallAccumulators = new Map();
|
|
167
|
+
let inputTokens = 0;
|
|
168
|
+
let outputTokens = 0;
|
|
169
|
+
let usageEmitted = false;
|
|
170
|
+
let model = config.model ?? this.defaultModel;
|
|
171
|
+
while (true) {
|
|
172
|
+
const { done, value } = await reader.read();
|
|
173
|
+
if (done)
|
|
174
|
+
break;
|
|
175
|
+
buffer += decoder.decode(value, { stream: true });
|
|
176
|
+
const lines = buffer.split('\n');
|
|
177
|
+
// 保留最后一个可能不完整的行
|
|
178
|
+
buffer = lines.pop() ?? '';
|
|
179
|
+
for (const line of lines) {
|
|
180
|
+
const trimmed = line.trim();
|
|
181
|
+
if (!trimmed || !trimmed.startsWith('data: '))
|
|
182
|
+
continue;
|
|
183
|
+
const payload = trimmed.slice(6);
|
|
184
|
+
if (payload === '[DONE]')
|
|
185
|
+
continue;
|
|
186
|
+
let chunk;
|
|
187
|
+
try {
|
|
188
|
+
chunk = JSON.parse(payload);
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
continue; // 跳过无法解析的 chunk
|
|
192
|
+
}
|
|
193
|
+
// 仅当 chunk.model 是合法模型名(不含 '/')时覆盖,过滤掉网关返回的路由别名(如 "/mnt/fast")
|
|
194
|
+
if (chunk.model && typeof chunk.model === 'string' && !chunk.model.includes('/')) {
|
|
195
|
+
model = chunk.model;
|
|
196
|
+
}
|
|
197
|
+
if (chunk.usage) {
|
|
198
|
+
inputTokens = chunk.usage.prompt_tokens ?? 0;
|
|
199
|
+
outputTokens = chunk.usage.completion_tokens ?? 0;
|
|
200
|
+
}
|
|
201
|
+
const delta = chunk.choices?.[0]?.delta;
|
|
202
|
+
const finishReason = chunk.choices?.[0]?.finish_reason;
|
|
203
|
+
// 累积 tool_calls delta
|
|
204
|
+
if (delta?.tool_calls) {
|
|
205
|
+
for (const tc of delta.tool_calls) {
|
|
206
|
+
const idx = tc.index;
|
|
207
|
+
if (!toolCallAccumulators.has(idx)) {
|
|
208
|
+
toolCallAccumulators.set(idx, {
|
|
209
|
+
id: tc.id ?? '',
|
|
210
|
+
name: tc.function?.name ?? '',
|
|
211
|
+
arguments: '',
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
const acc = toolCallAccumulators.get(idx);
|
|
215
|
+
if (tc.id)
|
|
216
|
+
acc.id = tc.id;
|
|
217
|
+
if (tc.function?.name)
|
|
218
|
+
acc.name = tc.function.name;
|
|
219
|
+
if (tc.function?.arguments)
|
|
220
|
+
acc.arguments += tc.function.arguments;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// 产出内容 / reasoning chunk
|
|
224
|
+
const content = delta?.content ?? '';
|
|
225
|
+
const reasoningContent = typeof delta?.reasoning_content === 'string' ? delta.reasoning_content : '';
|
|
226
|
+
const isEnd = finishReason === 'stop' || finishReason === 'tool_calls';
|
|
227
|
+
const hasUsage = Boolean(chunk.usage);
|
|
228
|
+
if (content || reasoningContent || isEnd) {
|
|
229
|
+
const result = {
|
|
230
|
+
content,
|
|
231
|
+
isEnd,
|
|
232
|
+
...(reasoningContent ? { reasoningContent } : {}),
|
|
233
|
+
};
|
|
234
|
+
if (isEnd) {
|
|
235
|
+
// usage 常在 finish 之后单独 chunk 到达;此处有则带上,无则留给收尾 chunk
|
|
236
|
+
if (hasUsage || inputTokens > 0 || outputTokens > 0) {
|
|
237
|
+
result.inputTokens = inputTokens;
|
|
238
|
+
result.outputTokens = outputTokens;
|
|
239
|
+
if (hasUsage)
|
|
240
|
+
usageEmitted = true;
|
|
241
|
+
}
|
|
242
|
+
if (toolCallAccumulators.size > 0) {
|
|
243
|
+
result.toolCalls = this.finalizeToolCalls(toolCallAccumulators);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
yield result;
|
|
247
|
+
}
|
|
248
|
+
else if (hasUsage) {
|
|
249
|
+
// OpenAI/兼容网关:stream_options.include_usage 的 usage 独立 chunk(choices 为空)
|
|
250
|
+
usageEmitted = true;
|
|
251
|
+
yield {
|
|
252
|
+
content: '',
|
|
253
|
+
isEnd: true,
|
|
254
|
+
inputTokens,
|
|
255
|
+
outputTokens,
|
|
256
|
+
toolCalls: toolCallAccumulators.size > 0
|
|
257
|
+
? this.finalizeToolCalls(toolCallAccumulators)
|
|
258
|
+
: undefined,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
// 流结束仍未产出 usage 时,若有累计值再补一发(避免调用方锁死在 0)
|
|
264
|
+
if (!usageEmitted && (inputTokens > 0 || outputTokens > 0)) {
|
|
265
|
+
yield {
|
|
266
|
+
content: '',
|
|
267
|
+
isEnd: true,
|
|
268
|
+
inputTokens,
|
|
269
|
+
outputTokens,
|
|
270
|
+
toolCalls: toolCallAccumulators.size > 0
|
|
271
|
+
? this.finalizeToolCalls(toolCallAccumulators)
|
|
272
|
+
: undefined,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
// [DEBUG] 流结束后打印累积 tool_calls
|
|
276
|
+
if (process.env.EAP_LLM_DEBUG === '1') {
|
|
277
|
+
console.log('[LLM-DEBUG] stream response summary:', JSON.stringify({
|
|
278
|
+
toolCallsCount: toolCallAccumulators.size,
|
|
279
|
+
toolNames: Array.from(toolCallAccumulators.values()).map(a => a.name),
|
|
280
|
+
inputTokens,
|
|
281
|
+
outputTokens,
|
|
282
|
+
}, null, 2));
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
finally {
|
|
286
|
+
clearTimeout(timeout);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
// ─────────────────────────────────────────────
|
|
290
|
+
// Private helpers
|
|
291
|
+
// ─────────────────────────────────────────────
|
|
292
|
+
buildRequestBody(messages, config, stream) {
|
|
293
|
+
const apiMessages = [];
|
|
294
|
+
if (config.systemPrompt) {
|
|
295
|
+
apiMessages.push({ role: 'system', content: config.systemPrompt });
|
|
296
|
+
}
|
|
297
|
+
for (const m of messages) {
|
|
298
|
+
const msg = { role: m.role, content: m.content };
|
|
299
|
+
// DeepSeek thinking:assistant 须回传 reasoning_content(有则带上)
|
|
300
|
+
if (m.role === 'assistant' && typeof m.reasoningContent === 'string') {
|
|
301
|
+
msg.reasoning_content = m.reasoningContent;
|
|
302
|
+
}
|
|
303
|
+
// 如果消息来自 assistant 且包含 toolCalls,附带 tool_calls 字段
|
|
304
|
+
if (m.role === 'assistant' && m.toolCalls?.length) {
|
|
305
|
+
msg.tool_calls = m.toolCalls.map((tc) => ({
|
|
306
|
+
id: tc.id,
|
|
307
|
+
type: 'function',
|
|
308
|
+
function: { name: tc.name, arguments: JSON.stringify(tc.arguments) },
|
|
309
|
+
}));
|
|
310
|
+
}
|
|
311
|
+
// 如果消息是 tool 角色,附带 tool_call_id
|
|
312
|
+
if (m.role === 'tool') {
|
|
313
|
+
msg.tool_call_id = m.toolCallId ?? '';
|
|
314
|
+
}
|
|
315
|
+
apiMessages.push(msg);
|
|
316
|
+
}
|
|
317
|
+
const body = {
|
|
318
|
+
model: resolveLlmModelName(config.model, this.defaultModel),
|
|
319
|
+
messages: apiMessages,
|
|
320
|
+
temperature: config.temperature ?? 0.7,
|
|
321
|
+
max_tokens: config.maxTokens,
|
|
322
|
+
stream,
|
|
323
|
+
};
|
|
324
|
+
// 注入 tools 定义(function-calling)
|
|
325
|
+
if (config.tools?.length) {
|
|
326
|
+
body.tools = config.tools;
|
|
327
|
+
if (config.toolChoice !== undefined) {
|
|
328
|
+
body.tool_choice = config.toolChoice;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
// 流式模式请求 usage 统计(OpenAI 兼容)
|
|
332
|
+
if (stream) {
|
|
333
|
+
body.stream_options = { include_usage: true };
|
|
334
|
+
}
|
|
335
|
+
return body;
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* 解析同步响应中的 tool_calls
|
|
339
|
+
*/
|
|
340
|
+
parseToolCalls(rawCalls) {
|
|
341
|
+
if (!rawCalls?.length)
|
|
342
|
+
return [];
|
|
343
|
+
return rawCalls.map((tc) => {
|
|
344
|
+
let parsedArgs = {};
|
|
345
|
+
try {
|
|
346
|
+
parsedArgs = JSON.parse(tc.function.arguments);
|
|
347
|
+
}
|
|
348
|
+
catch {
|
|
349
|
+
parsedArgs = {};
|
|
350
|
+
}
|
|
351
|
+
return {
|
|
352
|
+
id: tc.id,
|
|
353
|
+
name: tc.function.name,
|
|
354
|
+
arguments: parsedArgs,
|
|
355
|
+
};
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* 将流式累积的 tool_calls 转换为最终结果
|
|
360
|
+
*/
|
|
361
|
+
finalizeToolCalls(accumulators) {
|
|
362
|
+
const result = [];
|
|
363
|
+
for (const [, acc] of accumulators) {
|
|
364
|
+
let parsedArgs = {};
|
|
365
|
+
try {
|
|
366
|
+
parsedArgs = JSON.parse(acc.arguments);
|
|
367
|
+
}
|
|
368
|
+
catch {
|
|
369
|
+
parsedArgs = {};
|
|
370
|
+
}
|
|
371
|
+
result.push({
|
|
372
|
+
id: acc.id,
|
|
373
|
+
name: acc.name,
|
|
374
|
+
arguments: parsedArgs,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
return result;
|
|
378
|
+
}
|
|
379
|
+
formatNetworkError(url, err) {
|
|
380
|
+
const detail = formatNetworkDetail(err);
|
|
381
|
+
return `LLM network error calling ${url}: ${detail}`;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
function formatNetworkDetail(error, depth = 0) {
|
|
385
|
+
if (depth > 5)
|
|
386
|
+
return '...';
|
|
387
|
+
if (typeof AggregateError !== 'undefined' && error instanceof AggregateError) {
|
|
388
|
+
const nested = (error.errors ?? []).map((e) => formatNetworkDetail(e, depth + 1)).filter(Boolean);
|
|
389
|
+
return nested.length > 0 ? `AggregateError(${nested.join('; ')})` : 'AggregateError';
|
|
390
|
+
}
|
|
391
|
+
if (error instanceof Error) {
|
|
392
|
+
const cause = error.cause;
|
|
393
|
+
const base = error.message?.trim() || error.name || 'Error';
|
|
394
|
+
return cause !== undefined ? `${base} (cause: ${formatNetworkDetail(cause, depth + 1)})` : base;
|
|
395
|
+
}
|
|
396
|
+
if (typeof error === 'string')
|
|
397
|
+
return error;
|
|
398
|
+
try {
|
|
399
|
+
return JSON.stringify(error);
|
|
400
|
+
}
|
|
401
|
+
catch {
|
|
402
|
+
return 'Unknown error';
|
|
403
|
+
}
|
|
404
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { EchoLLMProvider, createDefaultLLMProvider } from './echo-llm-provider.js';
|
|
2
|
+
export { HttpLLMProvider, type HttpLLMProviderConfig } from './http-llm-provider.js';
|
|
3
|
+
export { RoutingLLMProvider, type RoutingLLMProviderConfig } from './routing-llm-provider.js';
|
|
4
|
+
export { resolveLlmModelName, canOverrideGraphModel } from './resolve-llm-model.js';
|
|
5
|
+
export { createLocalLlmProvider, requireLocalLlmConnections, resolveLocalLlmConnection, parseLocalLlmConnectionRefs, hydrateLocalLlmConnections, LLM_CONNECTIONS_ENV, } from './local-llm-provider.js';
|
|
6
|
+
export type { LocalLlmConnection, LocalLlmConnectionRef, LocalLlmConnectionRefs, LocalLlmConnections, } from './local-llm-provider.js';
|
|
7
|
+
export type { LLMToolDefinition } from '../graph/nodes/llm-node.js';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { EchoLLMProvider, createDefaultLLMProvider } from './echo-llm-provider.js';
|
|
2
|
+
export { HttpLLMProvider } from './http-llm-provider.js';
|
|
3
|
+
export { RoutingLLMProvider } from './routing-llm-provider.js';
|
|
4
|
+
export { resolveLlmModelName, canOverrideGraphModel } from './resolve-llm-model.js';
|
|
5
|
+
export { createLocalLlmProvider, requireLocalLlmConnections, resolveLocalLlmConnection, parseLocalLlmConnectionRefs, hydrateLocalLlmConnections, LLM_CONNECTIONS_ENV, } from './local-llm-provider.js';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 本地多模型 LLM:按节点 model 选网关。仅 dev。
|
|
3
|
+
* 连接表里只带 apiKeyEnv,密钥从 process.env 读取,不进 JSON。
|
|
4
|
+
*/
|
|
5
|
+
import type { ILLMProvider } from '../graph/nodes/llm-node.js';
|
|
6
|
+
export interface LocalLlmConnection {
|
|
7
|
+
baseUrl: string;
|
|
8
|
+
apiKey: string;
|
|
9
|
+
}
|
|
10
|
+
export interface LocalLlmConnectionRef {
|
|
11
|
+
baseUrl: string;
|
|
12
|
+
apiKeyEnv: string;
|
|
13
|
+
}
|
|
14
|
+
export interface LocalLlmConnectionRefs {
|
|
15
|
+
default?: LocalLlmConnectionRef;
|
|
16
|
+
byModel?: Record<string, LocalLlmConnectionRef>;
|
|
17
|
+
}
|
|
18
|
+
export interface LocalLlmConnections {
|
|
19
|
+
default?: LocalLlmConnection;
|
|
20
|
+
byModel?: Record<string, LocalLlmConnection>;
|
|
21
|
+
}
|
|
22
|
+
export declare const LLM_CONNECTIONS_ENV = "EAP_LLM_CONNECTIONS";
|
|
23
|
+
export declare function parseLocalLlmConnectionRefs(env?: NodeJS.ProcessEnv): LocalLlmConnectionRefs;
|
|
24
|
+
export declare function hydrateLocalLlmConnections(refs: LocalLlmConnectionRefs, env?: NodeJS.ProcessEnv): LocalLlmConnections;
|
|
25
|
+
export declare function requireLocalLlmConnections(env?: NodeJS.ProcessEnv): LocalLlmConnections;
|
|
26
|
+
export declare function resolveLocalLlmConnection(connections: LocalLlmConnections, model?: string): LocalLlmConnection;
|
|
27
|
+
export declare function createLocalLlmProvider(connections?: LocalLlmConnections): ILLMProvider;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 本地多模型 LLM:按节点 model 选网关。仅 dev。
|
|
3
|
+
* 连接表里只带 apiKeyEnv,密钥从 process.env 读取,不进 JSON。
|
|
4
|
+
*/
|
|
5
|
+
import { HttpLLMProvider } from './http-llm-provider.js';
|
|
6
|
+
export const LLM_CONNECTIONS_ENV = 'EAP_LLM_CONNECTIONS';
|
|
7
|
+
export function parseLocalLlmConnectionRefs(env = process.env) {
|
|
8
|
+
const raw = env[LLM_CONNECTIONS_ENV]?.trim();
|
|
9
|
+
if (raw) {
|
|
10
|
+
let parsed;
|
|
11
|
+
try {
|
|
12
|
+
parsed = JSON.parse(raw);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
throw new Error(`${LLM_CONNECTIONS_ENV} is not valid JSON.`);
|
|
16
|
+
}
|
|
17
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
18
|
+
throw new Error(`${LLM_CONNECTIONS_ENV} must be a JSON object.`);
|
|
19
|
+
}
|
|
20
|
+
const refs = parsed;
|
|
21
|
+
return {
|
|
22
|
+
default: refs.default,
|
|
23
|
+
byModel: refs.byModel ?? {},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
if (env.OPENAI_API_KEY) {
|
|
27
|
+
return {
|
|
28
|
+
default: {
|
|
29
|
+
baseUrl: env.OPENAI_BASE_URL || 'https://api.deepseek.com/v1',
|
|
30
|
+
apiKeyEnv: 'OPENAI_API_KEY',
|
|
31
|
+
},
|
|
32
|
+
byModel: {},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
return { byModel: {} };
|
|
36
|
+
}
|
|
37
|
+
export function hydrateLocalLlmConnections(refs, env = process.env) {
|
|
38
|
+
const hydrate = (ref) => {
|
|
39
|
+
if (!ref?.apiKeyEnv)
|
|
40
|
+
return undefined;
|
|
41
|
+
const apiKey = env[ref.apiKeyEnv]?.trim();
|
|
42
|
+
if (!apiKey)
|
|
43
|
+
return undefined;
|
|
44
|
+
return { baseUrl: ref.baseUrl, apiKey };
|
|
45
|
+
};
|
|
46
|
+
const byModel = {};
|
|
47
|
+
for (const [name, ref] of Object.entries(refs.byModel ?? {})) {
|
|
48
|
+
const connection = hydrate(ref);
|
|
49
|
+
if (connection)
|
|
50
|
+
byModel[name] = connection;
|
|
51
|
+
}
|
|
52
|
+
return { default: hydrate(refs.default), byModel };
|
|
53
|
+
}
|
|
54
|
+
export function requireLocalLlmConnections(env = process.env) {
|
|
55
|
+
const refs = parseLocalLlmConnectionRefs(env);
|
|
56
|
+
const connections = hydrateLocalLlmConnections(refs, env);
|
|
57
|
+
if (!connections.default && Object.keys(connections.byModel ?? {}).length === 0) {
|
|
58
|
+
const wanted = [
|
|
59
|
+
refs.default?.apiKeyEnv,
|
|
60
|
+
...Object.values(refs.byModel ?? {}).map((ref) => ref.apiKeyEnv),
|
|
61
|
+
].filter((name, index, all) => Boolean(name) && all.indexOf(name) === index);
|
|
62
|
+
throw new Error(`Local LLM is configured but no API key resolved. Set ${wanted.join(' or ') || 'OPENAI_API_KEY'}.`);
|
|
63
|
+
}
|
|
64
|
+
return connections;
|
|
65
|
+
}
|
|
66
|
+
export function resolveLocalLlmConnection(connections, model) {
|
|
67
|
+
const named = model ? connections.byModel?.[model] : undefined;
|
|
68
|
+
const connection = named ?? connections.default;
|
|
69
|
+
if (!connection?.apiKey) {
|
|
70
|
+
throw new Error(model
|
|
71
|
+
? `No local LLM gateway for model ${model}. Set runtime.llm.models["${model}"] in eap.config.json.`
|
|
72
|
+
: 'No local LLM gateway. Set runtime.llm in eap.config.json.');
|
|
73
|
+
}
|
|
74
|
+
return connection;
|
|
75
|
+
}
|
|
76
|
+
export function createLocalLlmProvider(connections = requireLocalLlmConnections()) {
|
|
77
|
+
return new LocalLlmProvider(connections);
|
|
78
|
+
}
|
|
79
|
+
class LocalLlmProvider {
|
|
80
|
+
connections;
|
|
81
|
+
cache = new Map();
|
|
82
|
+
constructor(connections) {
|
|
83
|
+
this.connections = connections;
|
|
84
|
+
}
|
|
85
|
+
invoke(messages, config) {
|
|
86
|
+
return this.providerFor(config.model).invoke(messages, config);
|
|
87
|
+
}
|
|
88
|
+
stream(messages, config) {
|
|
89
|
+
return this.providerFor(config.model).stream(messages, config);
|
|
90
|
+
}
|
|
91
|
+
providerFor(model) {
|
|
92
|
+
const connection = resolveLocalLlmConnection(this.connections, model);
|
|
93
|
+
const cacheKey = `${connection.baseUrl}\0${connection.apiKey}\0${model ?? ''}`;
|
|
94
|
+
const cached = this.cache.get(cacheKey);
|
|
95
|
+
if (cached)
|
|
96
|
+
return cached;
|
|
97
|
+
const provider = new HttpLLMProvider({
|
|
98
|
+
apiKey: connection.apiKey,
|
|
99
|
+
baseUrl: connection.baseUrl,
|
|
100
|
+
defaultModel: model,
|
|
101
|
+
});
|
|
102
|
+
this.cache.set(cacheKey, provider);
|
|
103
|
+
return provider;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 解析实际发给上游 LLM 的 model 名。
|
|
3
|
+
*
|
|
4
|
+
* 优先级:
|
|
5
|
+
* 1. EAP_DEFAULT_MODEL — 仅 EAP_MODEL_FORCE=1 时压过 graph.model
|
|
6
|
+
* 2. graph 节点 config.model
|
|
7
|
+
* 3. Provider 构造时的 defaultModel / EAP_DEFAULT_MODEL(作兜底,不压 graph)
|
|
8
|
+
* 4. 内置兜底 DeepSeek-V4-Pro
|
|
9
|
+
*
|
|
10
|
+
* 生产与普通 dev:图内模型名优先。不要靠 EAP_DEPLOYMENT_CONTEXT=dev 盖掉多节点模型。
|
|
11
|
+
*/
|
|
12
|
+
export declare function canOverrideGraphModel(env?: NodeJS.ProcessEnv): boolean;
|
|
13
|
+
export declare function resolveLlmModelName(graphModel?: string | null, defaultModel?: string | null, env?: NodeJS.ProcessEnv): string;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 解析实际发给上游 LLM 的 model 名。
|
|
3
|
+
*
|
|
4
|
+
* 优先级:
|
|
5
|
+
* 1. EAP_DEFAULT_MODEL — 仅 EAP_MODEL_FORCE=1 时压过 graph.model
|
|
6
|
+
* 2. graph 节点 config.model
|
|
7
|
+
* 3. Provider 构造时的 defaultModel / EAP_DEFAULT_MODEL(作兜底,不压 graph)
|
|
8
|
+
* 4. 内置兜底 DeepSeek-V4-Pro
|
|
9
|
+
*
|
|
10
|
+
* 生产与普通 dev:图内模型名优先。不要靠 EAP_DEPLOYMENT_CONTEXT=dev 盖掉多节点模型。
|
|
11
|
+
*/
|
|
12
|
+
export function canOverrideGraphModel(env = process.env) {
|
|
13
|
+
return env.EAP_MODEL_FORCE === '1';
|
|
14
|
+
}
|
|
15
|
+
export function resolveLlmModelName(graphModel, defaultModel, env = process.env) {
|
|
16
|
+
const fromEnv = env.EAP_DEFAULT_MODEL?.trim();
|
|
17
|
+
if (fromEnv && canOverrideGraphModel(env)) {
|
|
18
|
+
return fromEnv;
|
|
19
|
+
}
|
|
20
|
+
return (specifiedModel(graphModel) ||
|
|
21
|
+
specifiedModel(defaultModel) ||
|
|
22
|
+
fromEnv ||
|
|
23
|
+
'DeepSeek-V4-Pro');
|
|
24
|
+
}
|
|
25
|
+
function specifiedModel(value) {
|
|
26
|
+
const trimmed = value?.trim();
|
|
27
|
+
if (!trimmed || trimmed.toLowerCase() === 'default')
|
|
28
|
+
return undefined;
|
|
29
|
+
return trimmed;
|
|
30
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RoutingLLMProvider — 基于 Foundation Service 模型路由的 LLM Provider
|
|
3
|
+
*
|
|
4
|
+
* 工作流程:
|
|
5
|
+
* 1. 请求 Foundation `/models/select` 选择最佳模型
|
|
6
|
+
* 2. Foundation 返回模型名称 + Provider 连接配置(baseUrl/apiKey/headers)
|
|
7
|
+
* 3. 用返回的连接配置创建 HttpLLMProvider,发送请求
|
|
8
|
+
*
|
|
9
|
+
* 设计原则:
|
|
10
|
+
* - Runtime 不存任何 LLM 凭证,全部从 Foundation 获取
|
|
11
|
+
* - 支持任意 OpenAI 兼容 API(OpenAI / DeepSeek / 本地 Ollama 等)
|
|
12
|
+
* - 连接配置由 Foundation 统一管理,Runtime 无感知
|
|
13
|
+
*/
|
|
14
|
+
import type { GraphMessage } from '../graph/contracts/executable-graph.js';
|
|
15
|
+
import type { ILLMProvider, LLMNodeConfig, LLMResponse, LLMChunk } from '../graph/nodes/llm-node.js';
|
|
16
|
+
export interface RoutingLLMProviderConfig {
|
|
17
|
+
foundationUrl: string;
|
|
18
|
+
fallbackProvider?: ILLMProvider;
|
|
19
|
+
}
|
|
20
|
+
export declare class RoutingLLMProvider implements ILLMProvider {
|
|
21
|
+
private readonly foundationUrl;
|
|
22
|
+
private readonly fallback;
|
|
23
|
+
constructor(config: RoutingLLMProviderConfig);
|
|
24
|
+
invoke(messages: GraphMessage[], config: LLMNodeConfig): Promise<LLMResponse>;
|
|
25
|
+
stream(messages: GraphMessage[], config: LLMNodeConfig): AsyncIterableIterator<LLMChunk>;
|
|
26
|
+
private selectModel;
|
|
27
|
+
private createProviderForModel;
|
|
28
|
+
}
|