LLM-driven 设计,添加学习审计和会话历史存储至目录的功能

This commit is contained in:
2026-05-15 11:50:20 +08:00
parent f150c602e5
commit eebf802e31
15 changed files with 1557 additions and 133 deletions
+33 -4
View File
@@ -2,6 +2,7 @@ import type { Event as OpencodeEvent, Part } from "@opencode-ai/sdk/v2";
import { Router } from "express";
import { z } from "zod";
import { type LearningOrchestrator } from "../learning/orchestrator.js";
import { logger } from "../logger.js";
import { MemoryStore } from "../memory/store.js";
import { type OpencodeRuntimeAdapter } from "../runtime/opencode.js";
@@ -34,6 +35,7 @@ export const buildChatRouter = (
sessionBridge: ChatSessionBridge,
runtime: OpencodeRuntimeAdapter,
memoryStore: MemoryStore,
learningOrchestrator: LearningOrchestrator,
) => {
const chatRouter = Router();
@@ -229,6 +231,11 @@ export const buildChatRouter = (
});
if (!streamResult.aborted && !streamResult.failed) {
const messages = await runtime.messages(binding.sessionId, 60);
const assistantMessage = [...messages]
.reverse()
.find((message) => message.info.role === "assistant");
const assistantText = collectTextContent(assistantMessage?.parts ?? []);
const existingSessionTitle = sessionBridge.getSessionTitle(binding.sessionId);
let sessionTitle = existingSessionTitle;
const shouldGenerateTitle =
@@ -251,6 +258,21 @@ export const buildChatRouter = (
);
}
}
if (assistantText) {
void learningOrchestrator.onTurnCompleted({
assistantMessage: assistantText,
model: parsed.data.model,
requestContext,
sessionId: binding.sessionId,
toolCallCount: streamResult.toolCallCount,
userMessage: parsed.data.message,
}).catch((error) => {
logger.warn(
{ err: error, sessionId: binding.sessionId },
"post-turn learning failed",
);
});
}
}
} finally {
streamClosed = true;
@@ -385,7 +407,11 @@ const streamPromptResponse = async ({
projectId,
signal,
write,
}: StreamPromptOptions): Promise<{ aborted: boolean; failed: boolean }> => {
}: StreamPromptOptions): Promise<{
aborted: boolean;
failed: boolean;
toolCallCount: number;
}> => {
const eventStream = await runtime.subscribeEvents();
const iterator = eventStream[Symbol.asyncIterator]();
const requestStartedAt = Date.now();
@@ -396,6 +422,7 @@ const streamPromptResponse = async ({
const pendingPartTextDeltas = new Map<string, string[]>();
const reasoningDeltas = new Map<string, string[]>();
let emittedText = false;
let toolCallCount = 0;
let done = false;
let promptSettled = false;
let aborted = signal?.aborted ?? false;
@@ -624,6 +651,7 @@ const streamPromptResponse = async ({
(hasToolParams(toolParams) || isToolFinalState)
) {
emittedToolParts.add(part.id);
toolCallCount += 1;
if (!reason) {
logger.warn(
{
@@ -704,11 +732,11 @@ const streamPromptResponse = async ({
await runtime.abortSession(opencodeSessionId).catch((error) => {
logger.warn({ sessionId: opencodeSessionId, err: error }, "failed to abort opencode session");
});
return { aborted: true, failed: false };
return { aborted: true, failed: false, toolCallCount };
}
if (failed) {
return { aborted: false, failed: true };
return { aborted: false, failed: true, toolCallCount };
}
await promptPromise;
@@ -735,7 +763,7 @@ const streamPromptResponse = async ({
session_id: clientSessionId,
total_duration_ms: Math.max(0, Date.now() - requestStartedAt),
});
return { aborted: false, failed: false };
return { aborted: false, failed: false, toolCallCount };
} finally {
await iterator.return?.(undefined);
if (!promptSettled) {
@@ -1003,6 +1031,7 @@ const toolLabels: Record<string, string> = {
dynamic_http_call: "后端数据查询",
fetch_result_ref: "结果引用回读",
memory_manager: "记忆写入",
session_search: "历史会话检索",
skill_manager: "流程沉淀",
locate_features: "地图定位",
view_history: "历史数据面板",