文章目录
- 前言
-
- 一、系统概述
- 二、系统架构全景
- 三、类型系统深度分析(types.ts)
-
- [3.1 Provider 类型系统 --- 适配器模式](#3.1 Provider 类型系统 — 适配器模式)
- [3.2 AgentStreamChunk --- 7 种类型的判别联合](#3.2 AgentStreamChunk — 7 种类型的判别联合)
- [3.3 ChatMessage 与 MessageStep --- 渲染层的数据结构](#3.3 ChatMessage 与 MessageStep — 渲染层的数据结构)
- [四、Agent 引擎深度分析(agent.ts)](#四、Agent 引擎深度分析(agent.ts))
-
- [4.1 System Prompt 设计哲学](#4.1 System Prompt 设计哲学)
- [4.2 createGraphRAGAgent --- Agent 工厂](#4.2 createGraphRAGAgent — Agent 工厂)
- [4.3 双模式流引擎 --- streamAgentResponse](#4.3 双模式流引擎 — streamAgentResponse)
- [五、7 个 Graph RAG 工具深度分析(tools.ts)](#五、7 个 Graph RAG 工具深度分析(tools.ts))
-
- [5.1 工具概览](#5.1 工具概览)
- [5.2 工具 1: search --- 混合搜索](#5.2 工具 1: search — 混合搜索)
- [5.3 工具 2: cypher --- 原始图查询](#5.3 工具 2: cypher — 原始图查询)
- [5.4 工具 3: grep --- 正则搜索](#5.4 工具 3: grep — 正则搜索)
- [5.5 工具 5: overview --- 全局代码地图](#5.5 工具 5: overview — 全局代码地图)
- [5.6 工具 6: explore --- 深度探测](#5.6 工具 6: explore — 深度探测)
- [5.7 工具 7: impact --- 爆炸半径分析(最复杂的工具)](#5.7 工具 7: impact — 爆炸半径分析(最复杂的工具))
- 六、动态上下文注入(context-builder.ts)
- [七、Agent 消息序列化(历史构建)](#七、Agent 消息序列化(历史构建))
-
- [`buildLangChainMessages` --- 前端消息 → LangChain 消息](#
buildLangChainMessages— 前端消息 → LangChain 消息) - [`serializeAgentHistoryMessages` --- LangChain 消息 → 前端可存储格式](#
serializeAgentHistoryMessages— LangChain 消息 → 前端可存储格式)
- [`buildLangChainMessages` --- 前端消息 → LangChain 消息](#
- [八、LLM 设置持久化(settings-service.ts)](#八、LLM 设置持久化(settings-service.ts))
- 九、关键设计决策总结
- 十、安全与错误处理
- 十一、总结
前言
继续 gitnexus-web
一、系统概述
Graph RAG Agent 是 gitnexus-web 的核心智能层 ,它是一个运行在浏览器端的 LangGraph React Agent,通过 7 个专用图数据库工具与知识图谱交互,实现对代码库的深度分析和理解。
二、系统架构全景
js
┌─────────────────────────────────────────────────────────────────────────┐
│ 系统框图 │
│ │
│ ┌─────────────┐ ┌──────────────────┐ ┌──────────────────────┐ │
│ │ 用户配置层 │ │ Agent 引擎 │ │ 工具执行层 │ │
│ │ │ │ │ │ │ │
│ │ settings- │───►│ agent.ts │───►│ tools.ts │ │
│ │ service.ts │ │ (LangGraph) │ │ (7 个工具) │ │
│ │ │ │ │ │ │ │
│ │ localStorage │ │ BASE_SYSTEM_ │ │ search cypher │ │
│ │ 持久化 │ │ PROMPT │ │ grep read │ │
│ └─────────────┘ │ + dynamic_context │ │ overview explore │ │
│ │ (context-builder) │ │ impact │ │
│ └──────────────────┘ └──────────┬───────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────┐ ┌──────────────────────┐ │
│ │ 流式输出层 │ │ 后端 API │ │
│ │ │ │ │ │
│ │ streamAgent- │ │ /api/search │ │
│ │ Response() │───►│ /api/query (Cypher) │ │
│ │ │ │ /api/grep │ │
│ │ 7 种 chunk 类型 │ │ /api/file │ │
│ │ reasoning │ │ /api/clusters │ │
│ │ tool_call │ │ /api/processes │ │
│ │ tool_result │ └──────────────────────┘ │
│ │ content │ │
│ │ done/error/ │ │
│ │ cancelled │ │
│ └──────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ UI 渲染层 │ │
│ │ RightPanel.tsx │ │
│ │ ToolCallCard │ │
│ │ MarkdownRenderer │ │
│ └──────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
三、类型系统深度分析(types.ts)
3.1 Provider 类型系统 --- 适配器模式
typescript
// 基类
interface BaseProviderConfig {
provider: LLMProvider; // 9 种枚举值
model: string;
temperature?: number;
maxTokens?: number;
}
// 具体实现 × 9
interface OpenAIConfig extends BaseProviderConfig { provider: 'openai'; apiKey: string; ... }
interface AzureOpenAIConfig extends BaseProviderConfig { provider: 'azure-openai'; ... }
interface GeminiConfig extends BaseProviderConfig { provider: 'gemini'; ... }
interface AnthropicConfig extends BaseProviderConfig { ... }
interface OllamaConfig extends BaseProviderConfig { ... }
interface OpenRouterConfig extends BaseProviderConfig { ... }
interface MiniMaxConfig extends BaseProviderConfig { ... }
interface GLMConfig extends BaseProviderConfig { ... }
interface DeepSeekConfig extends BaseProviderConfig { ... }
// 联合类型
type ProviderConfig = OpenAIConfig | AzureOpenAIConfig | ... | DeepSeekConfig;
设计亮点:
- 使用 TypeScript 可识别联合类型 (discriminated union),通过
provider字段做类型收窄 - 每种供应商的特殊配置(如 Azure 需要
endpoint+deploymentName)都有独立字段 - 相同的字段(如 DeepSeek 使用 OpenAI 兼容 API)通过
extends共享基类
3.2 AgentStreamChunk --- 7 种类型的判别联合
typescript
type AgentStreamChunk =
| { type: 'reasoning'; reasoning: string } // LLM 推理过程
| { type: 'tool_call'; toolCall: ToolCallInfo } // 工具调用发起
| { type: 'tool_result'; toolCall: ToolCallInfo } // 工具执行结果
| { type: 'content'; content: string } // 最终输出内容
| { type: 'error'; error: string } // 错误
| { type: 'done'; historyMessages?: ... } // 完成
| { type: 'cancelled' }; // 用户取消
这是整个 Agent 流式输出的协议层。每种 chunk 类型携带最小必需信息:
reasoning--- 只带文本(不需要工具信息)tool_call/tool_result--- 带完整ToolCallInfo(id, name, args, status, result)content--- 只带文本(最终答案)done--- 可选带historyMessages(用于重建下一轮对话的完整历史)
3.3 ChatMessage 与 MessageStep --- 渲染层的数据结构
typescript
interface ChatMessage {
id: string;
role: 'user' | 'assistant' | 'tool';
content: string;
historyMessages?: AgentHistoryMessage[]; // 隐藏的完整对话转录
toolCalls?: ToolCallInfo[]; // @deprecated
steps?: MessageStep[]; // 有序执行步骤
toolCallId?: string;
timestamp: number;
}
interface MessageStep {
id: string;
type: 'reasoning' | 'tool_call' | 'content'; // 三种步骤类型
content?: string; // 推理/内容文本
toolCall?: ToolCallInfo; // 工具调用信息
}
关键设计决策:
steps[]替代了旧的toolCalls[](已弃用)historyMessages是隐藏字段,不渲染但保留完整对话 JSON,用于 DeepSeek 等需要精确重放对话历史的提供商- 两种格式的向后兼容:新消息用
steps,旧消息降级使用content+toolCalls
四、Agent 引擎深度分析(agent.ts)
4.1 System Prompt 设计哲学
文件 : agent.ts(file:///workspace/gitnexus-web/src/core/llm/agent.ts#L64-L120) 的 BASE_SYSTEM_PROMPT
这是一份精心设计的 Agent 行为约束,采用分层指令结构:
js
1. IDENTITY + GROUNDING 强制指令(最高优先级)
"Every factual claim MUST include a citation."
"NO citation = NO claim."
2. VALIDATION 强制指令
"Every output MUST be validated."
"NO validation = NO claim."
"Do not blindly trust readme or single source of truth."
3. CORE PROTOCOL - 5 步工作法
Search → Read → Trace → Cite → Validate
4. TOOLS 参考 --- 每个工具的简短说明 + Cypher 示例
5. GRAPH SCHEMA --- 节点/边类型 + 语义含义
6. CRITICAL RULES --- 边界约束
"impact output is trusted. Do NOT re-validate."
"Cite or retract." "Read before concluding." "Retry on failure."
"Prefer tables and mermaid diagrams."
7. [Dynamic Context] --- 运行时代码库信息(由 context-builder 注入)
设计理念:
- 最短、最直接的指令优于长解释(参考 Aider/Cline 研究)
- 不提供模板示例 --- 让 LLM 自己推理如何执行
- "反懒惰"指令:明确禁止只读 README 就下结论
- 明确区分"信任层级":
impact工具的输出被标记为 "trusted",不需要重新验证
4.2 createGraphRAGAgent --- Agent 工厂
typescript
export const createGraphRAGAgent = (
config: ProviderConfig, // 用户的 LLM 配置
backend: GraphRAGBackend, // 后端 API 接口集合
codebaseContext?: CodebaseContext, // 可选的动态上下文
) => {
// 1. 创建聊天模型
const model = createChatModel(config);
// 2. 创建 7 个工具
const tools = createGraphRAGTools(backend);
// 3. 构建动态系统提示
const systemPrompt = codebaseContext
? buildDynamicSystemPrompt(BASE_SYSTEM_PROMPT, codebaseContext)
: BASE_SYSTEM_PROMPT;
// 4. 创建 LangGraph React Agent
const agent = createReactAgent({
llm: model as any,
tools: tools as any,
messageModifier: new SystemMessage(systemPrompt) as any,
});
return agent;
};
createChatModel --- 支持 9 家提供商的适配器工厂
这是典型的工厂方法模式。switch 每个 provider 类型,实例化对应的 LangChain 模型:
typescript
export const createChatModel = (config: ProviderConfig): BaseChatModel => {
switch (config.provider) {
case 'openai': return new ChatOpenAI({ apiKey, modelName, temperature, streaming: true });
case 'gemini': return new ChatGoogleGenerativeAI({ apiKey, model, streaming: true });
case 'anthropic': return new ChatAnthropic({ apiKey, model, streaming: true });
case 'ollama': return new ChatOllama({ baseUrl, model, numPredict: 30000, numCtx: 32768 });
case 'openrouter': return new ChatOpenAI({ baseURL: DEFAULT_OPENROUTER_BASE_URL, ... });
case 'minimax': return new ChatAnthropic({ baseURL: 'https://api.minimax.io/anthropic', ... });
case 'glm': return new ChatOpenAI({ baseURL: 'https://api.z.ai/api/coding/paas/v4', ... });
case 'deepseek': return new DeepSeekChatOpenAI({ baseURL: 'https://api.deepseek.com', ... });
// ...
}
};
值得注意的细节:
- Ollama 显式设置了
numCtx: 32768(默认 2048)和numPredict: 30000(默认 128-2048),这是 Agent 工作流的关键调优 - MiniMax 使用 Anthropic 兼容 API,但走的是
https://api.minimax.io/anthropic - DeepSeek 有自定义的
DeepSeekChatOpenAI封装,处理其特殊的消息格式 - 所有模型都设置
streaming: true,这是流式输出的基础
4.3 双模式流引擎 --- streamAgentResponse
这是整个系统最核心、最复杂的模块(约 240 行)。它解决了 Agent 输出流的两个核心问题:
问题 1: 既要逐 token 实时性,又要完整状态的结构性
typescript
const stream = await agent.stream(
{ messages: formattedMessages },
{
streamMode: ['values', 'messages'], // 并发双模式!
recursionLimit: 50, // 最大 50 次工具循环
signal: options.signal, // 取消信号
}
);
| 模式 | 数据 | 频率 | 用途 |
|---|---|---|---|
messages |
AIMessageChunk(逐 token) | 每 token | → 实时显示推理/内容文本 |
values |
完整 AgentState 快照 | 每步完成 | → 检测工具调用结果、状态变化 |
问题 2: 区分"推理过程"和"最终答案"
Agent 的输出模式可能是:
js
推理: "让我先搜索鉴权相关的代码..."
[tool_call: search] → 执行中
[tool_result: 结果1] → 完成
推理: "好的,我找到了关于鉴权的信息..."
[tool_call: cypher] → 执行中
[tool_result: 结果2] → 完成
内容: "**鉴权系统分析**\n\n鉴权系统主要分布在 `src/auth/` 目录..."
系统需要智能地区分"思考过程"(推理)和"最终总结"(内容):
typescript
let hasSeenToolCallThisTurn = false;
let pendingToolCalls = 0;
const isReasoning = !hasSeenToolCallThisTurn // 首次工具调用前 = 推理
|| toolCalls.length > 0 // 工具调用中的文本 = 推理
|| pendingToolCalls > 0; // 待处理工具完成前 = 推理
if (isReasoning) {
yield { type: 'reasoning', reasoning: content };
} else {
yield { type: 'content', content };
}
去重机制 --- 防止重复渲染
typescript
const yieldedToolCalls = new Set<string>(); // 已 yield 的 tool_call id
const yieldedToolResults = new Set<string>(); // 已 yield 的 tool_result id
// messages 模式可能和 values 模式产生重复的 tool_call/tool_result
if (!yieldedToolCalls.has(toolId)) {
yieldedToolCalls.add(toolId);
yield { type: 'tool_call', toolCall: { id: toolId, ... } };
}
五、7 个 Graph RAG 工具深度分析(tools.ts)
文件: tools.ts(file:///workspace/gitnexus-web/src/core/llm/tools.ts) --- 约 1500 行
5.1 工具概览
| # | 工具名 | 后端 API | 核心能力 | 输出格式 | 关键参数 |
|---|---|---|---|---|---|
| 1 | search | GET /api/search |
混合搜索(BM25 + 语义 + RRF 融合) | 按流程分组的结构化文本 | query, groupByProcess, limit |
| 2 | cypher | POST /api/query |
任意 Cypher 查询,支持矢量占位符 | Markdown 表格(最多 50 行) | cypher, query(可选矢量) |
| 3 | grep | GET /api/grep |
正则文件内容搜索 | filePath:line: text 格式 |
pattern, fileFilter, caseSensitive |
| 4 | read | GET /api/file |
读取完整文件内容 | File: path (N lines)\n\ncontent |
filePath |
| 5 | overview | 4 条并行 Cypher | 代码全局地图(集群+流程+依赖+关键路径) | 4 段 Markdown 表格 | 无参 |
| 6 | explore | 组合 Cypher | 对 symbol/cluster/process 深度探测 | 按类型不同结构 | target, type(可选自动检测) |
| 7 | impact | 1~3 层 Cypher | 爆炸半径分析 + 风险评级 | 分层表格 + 风险摘要 | target, direction, maxDepth |
5.2 工具 1: search --- 混合搜索
typescript
const searchTool = tool(async ({ query, groupByProcess, limit }) => {
const searchResults = await backendSearch(query, { limit: limit ?? 10, mode: 'hybrid' });
// 结果格式化为:
// [idx] Type: Name (score)
// ID: nodeId
// File: filePath:startLine
// Cluster: name
// Connections: -[CALLS 100%]-> name, <-[CALLS 95%]- name
// Processes: (按流程分组)
});
关键设计:
- 支持按
Process(执行流)分组,结果按流程分块显示 - 每个结果附带连接信息(前 3 个出边 + 前 3 个入边),附带置信度百分比
- 集群标签帮助 LLM 理解结果归属的功能模块
Zod Schema 验证:
typescript
schema: z.object({
query: z.string().describe('What you are looking for'),
groupByProcess: z.boolean().optional().nullable().describe('Group by process'),
limit: z.number().optional().nullable(),
})
5.3 工具 2: cypher --- 原始图查询
typescript
const cypherTool = tool(async ({ query, cypher }) => {
// 特殊处理:{{QUERY_VECTOR}} 占位符
if (cypher.includes('{{QUERY_VECTOR}}')) {
// 路由到后端语义搜索
const semanticResults = await backendSearch(query, { mode: 'semantic' });
return `Semantic search for "${query}" (${semanticResults.length} results):...`;
}
// 普通 Cypher 查询
const results = await executeQuery(cypher);
// 格式化为 Markdown 表格
});
设计亮点:
- 矢量占位符 :
{``{QUERY_VECTOR}}--- LLM 不需要知道如何生成嵌入向量,只需在 Cypher 中放一个占位符 - 结果自动格式化为 Markdown 表格(比 JSON 更 token 高效)
- 错误恢复:Cypher 语法错误时,返回错误消息 + 有效的示例查询供 LLM 参考重试
5.4 工具 3: grep --- 正则搜索
typescript
const grepTool = tool(async ({ pattern, fileFilter, caseSensitive, maxResults }) => {
// 前端验证正则有效性
try { new RegExp(pattern, caseSensitive ? 'g' : 'gi'); } catch (e) {
return `Invalid regex: ${pattern}...`;
}
const results = await backendGrep(fullPattern, limit);
// 格式化:filePath:line: text
});
设计决策:
- 前端先验证正则有效性再发送到后端,避免无效网络请求
fileFilter通过前瞻断言(?=)实现,在单个正则中完成路径过滤和内容搜索- 默认最多 100 条结果,超出时截断并提示
5.5 工具 5: overview --- 全局代码地图
typescript
const overviewTool = tool(async () => {
// 4 条并行 Cypher 查询
const [clusters, processes, deps, critical] = await Promise.all([
executeQuery(clustersQuery), // 集群 TOP 200
executeQuery(processesQuery), // 流程 TOP 200
executeQuery(depsQuery), // 跨集群依赖 TOP 15
executeQuery(criticalQuery), // 关键路径 TOP 10
]);
// 组合为 4 段 Markdown 表格:
return `CLUSTERS (N total):
| Cluster | Symbols | Cohesion | Description |
PROCESSES (M total):
| Process | Steps | Type | Clusters |
CLUSTER DEPENDENCIES:
- ClusterA -> ClusterB (42 calls)
CRITICAL PATHS:
- ProcessName (5 steps)`;
});
设计决策:
- 4 个查询并行执行,减少总延迟
Cypher查询结果兼容两种格式(Array.isArray(row)表示旧格式,row.field表示新格式)
5.6 工具 6: explore --- 深度探测
typescript
const exploreTool = tool(async ({ target, type }) => {
// 自动检测类型(未指定时依次尝试)
if (!resolvedType || resolvedType === 'process') { /* 查询 Process */ }
if (!resolvedType || resolvedType === 'cluster') { /* 查询 Community */ }
if (!resolvedType || resolvedType === 'symbol') { /* 查询各种节点类型 */ }
// 按类型返回不同结构
if (resolvedType === 'process') {
return `PROCESS: label\nType: ...\nSteps: ...
STEPS:
- step. name (filePath)
CLUSTERS TOUCHED: ...`;
}
// cluster 和 symbol 同理
});
关键设计:
- 3 次尝试的自动类型检测:先试 process → 再试 community → 最后试 symbol
- Cypher 查询使用
Cypher参数化('${safeTarget}')防注入 - 每种类型返回的结构不同,为 LLM 提供针对性的信息
5.7 工具 7: impact --- 爆炸半径分析(最复杂的工具)
这是 tools.ts 中最复杂的工具,约 660 行。它实现了从目标符号出发的多跳依赖分析。
核心流程:
js
impact("validate", "upstream", maxDepth=3)
│
├── 1. 查找目标节点 (Find Target)
│ ├── 按 name 搜索("validate")
│ ├── 按 filePath 搜索(含 "/" 时)
│ └── 多匹配消歧(返回选项请求用户指定)
│
├── 2. 多跳 Cypher 查询 (Traversal)
│ ├── depth=1: 直接依赖 (LIMIT 300)
│ ├── depth=2: 间接依赖 (LIMIT 200)
│ └── depth=3: 传递依赖 (LIMIT 100)
│
├── 3. 后处理 (Post-processing)
│ ├── 去重(按 nodeId)
│ ├── 测试文件过滤 (includeTests=false)
│ └── 置信度过滤 (minConfidence=0.7)
│
├── 4. 上下文分析 (Context)
│ ├── 受影响流程查询 (STEP_IN_PROCESS)
│ ├── 受影响集群查询 (MEMBER_OF)
│ └── 直接 vs 间接集群分类
│
├── 5. 风险评级 (Risk Assessment)
│ ├── directCount + affectedProcesses + affectedClusters
│ └── LOW / MEDIUM / HIGH / CRITICAL
│
└── 6. 格式输出 (Formatting)
├── d=1: Type|Name|File:Line|EdgeType|Confidence% + 代码片段
├── d=2: 同上(不含代码片段)
├── d=3: 同上(最多 5 条)
└── 风险摘要
消歧逻辑 --- 处理同名符号:
typescript
// 多个同名文件时的消歧
if (targetResults.length > 1 && !target.includes('/')) {
return `⚠️ AMBIGUOUS TARGET: Multiple files named "${target}" found:
1. src/auth/validate.ts
2. src/utils/validate.ts
Please specify which file you mean by using a more specific path...`;
}
回退机制 --- 当图分析无结果时:
typescript
if (isFileTarget && totalAffected === 0) {
// 尝试全文 grep 查找文本引用
const hints = await backendGrep(`\\b${escapeRegex(baseName)}\\b`, 15);
// ...
return `No upstream dependencies found, but textual references were detected (graph may be incomplete):...`
}
getCallSiteSnippet --- d=1 结果的上下文代码片段:
typescript
const getCallSiteSnippet = async (n: NodeInfo): Promise<string | null> => {
const content = await readFile(n.filePath);
const lines = content.split('\n');
let snippet = lines[n.startLine - 1].trim();
if (snippet.length > 80) snippet = snippet.slice(0, 77) + '...';
return snippet; // ↳ "validate(userInput);"
};
六、动态上下文注入(context-builder.ts)
文件: context-builder.ts(file:///workspace/gitnexus-web/src/core/llm/context-builder.ts)
Agent 初始化时,可以注入当前代码库的运行时统计信息,让 LLM 在回答前就了解项目规模:
typescript
export async function buildCodebaseContext(
executeQuery: (cypher: string) => Promise<any[]>,
projectName: string,
): Promise<CodebaseContext> {
const [stats, hotspots, folderTree] = await Promise.all([
getCodebaseStats(executeQuery, projectName),
getHotspots(executeQuery),
getFolderTree(executeQuery),
]);
return { stats, hotspots, folderTree };
}
注入到 System Prompt 的格式:
js
📊 CODEBASE: projectName
Files: 120 | Functions: 450 | Classes: 30 | Interfaces: 25
Hotspots (most connected):
- `validate()` (Function) --- 84 edges
- `UserService` (Class) --- 56 edges
📁 STRUCTURE
三个数据源:
| 数据 | Cypher 查询 | 用途 |
|---|---|---|
| CodebaseStats | 5 条 count 查询(File, Function, Class, Interface, Method) | 让 LLM 知道项目规模 |
| Hotspots | MATCH (n)-[r:CodeRelation]-(m) WITH n, COUNT(r) AS connections ORDER BY connections DESC LIMIT 8 |
告诉 LLM 高连接度的关键节点(潜在入口点) |
| FolderTree | MATCH (f:File) RETURN f.filePath ORDER BY path |
展示项目目录结构(智能折叠 >10 层的目录) |
注意 :getCodebaseStats 的 5 条计数查询是串行执行 的(for 循环逐个 await),这是可以优化的地方。但 buildCodebaseContext 本身将 3 个数据源查询并行化(Promise.all)。
七、Agent 消息序列化(历史构建)
为了支持 DeepSeek 等需要精确对话上下文复现的提供商,系统实现了完整的消息序列化:
buildLangChainMessages --- 前端消息 → LangChain 消息
typescript
export const buildLangChainMessages = (messages: AgentMessage[]): BaseMessage[] =>
messages.map((message) => {
if (message.role === 'user') {
return new HumanMessage(message.content);
}
if (message.role === 'tool') {
return new ToolMessage({ content: message.content, tool_call_id: message.toolCallId, ... });
}
return new AIMessage({
content: message.content,
additional_kwargs: { reasoning_content: message.reasoningContent },
tool_calls: message.toolCalls,
});
});
serializeAgentHistoryMessages --- LangChain 消息 → 前端可存储格式
typescript
export const serializeAgentHistoryMessages = (
messages: unknown[], startIndex = 0
): AgentHistoryMessage[] => {
// 从 LangChain 消息中提取:
// - AI 消息的 content + reasoning_content + tool_calls
// - Tool 消息的 content + tool_call_id + name
};
用途 :done chunk 中的 historyMessages 被存储在 ChatMessage.historyMessages 中,下一轮对话时重建完整历史,确保提供商如 DeepSeek 能正确理解上下文。
八、LLM 设置持久化(settings-service.ts)
设计模式 :Provider Configuration Strategy Pattern
typescript
export interface LLMSettings {
activeProvider: LLMProvider; // 当前激活的提供商
intelligentClustering: boolean; // 智能集群命名
hasSeenClusteringPrompt: boolean; // 集群提示已显示
useSameModelForClustering: boolean; // 使用同一模型做集群
openai?: Partial<...>;
gemini?: Partial<...>;
// ... 全部 9 家
}
存储位置 :localStorage(浏览器本地存储)
关键函数:
loadSettings()/saveSettings()--- 读写 localStoragegetActiveProviderConfig()--- 获取当前激活的完整配置isProviderConfigured(provider)--- 检查提供商是否已配置 API Key
数据流:
js
用户填写 API Key + Model
│
▼
saveSettings() → localStorage.setItem('gitnexus-llm-settings', JSON.stringify(settings))
│
▼
getActiveProviderConfig() → validate required fields → ProviderConfig
│
▼
createChatModel(config) → BaseChatModel (LangChain 实例)
│
▼
createGraphRAGAgent(config, backend) → LangGraph Agent
九、关键设计决策总结
| 决策 | 实现 | 优点 | 代价 |
|---|---|---|---|
| LangGraph React Agent | createReactAgent |
现成的 ReAct 循环实现,减少自研工作量 | 依赖 LangChain 生态,bundle 体积大 |
| 双模式流 | streamMode: ['values', 'messages'] |
兼顾 token 实时性和状态完整性 | 需要去重逻辑(yieldedToolCalls Set) |
| 推理/内容智能区分 | hasSeenToolCallThisTurn + pendingToolCalls |
用户看到 Cursor 式的思考→操作→回答循环 | 状态跟踪复杂,边界情况可能误分类 |
| 工具函数式 | 纯 async 函数 + Zod 校验 |
无 class 继承,测试简单,类型安全 | 缺少中间件/AOP 扩展点 |
| Cypher 矢量占位符 | {``{QUERY_VECTOR}} → 后端语义搜索 |
LLM 不需要理解嵌入向量 | 后端必须实现矢量化接口 |
| Prompt 动态注入 | context-builder → buildDynamicSystemPrompt |
LLM 提前了解代码库规模,减少不必要的工具调用 | 注入上下文增加 prompt tokens |
| multi-format 兼容 | Array.isArray(row) 兼容旧/新 Cypher 结果格式 |
平滑迁移,后端升级不影响前端 | 冗余判断代码 |
| 完整历史重建 | AgentHistoryMessage[] + captureHistory |
支持 DeepSeek 等需要精确回放对话的提供商 | 存储更多数据,localStorage 可能超出限制 |
十、安全与错误处理
| 机制 | 在哪 | 说明 |
|---|---|---|
| API Key 安全 | settings-service.ts |
存储在 localStorage,不发送到任何非 LLM 服务端 |
| 正则有效性验证 | tools.ts (grep) |
前端先验证正则再发送请求 |
| Cypher 注入防护 | tools.ts (cypher) |
使用 replace(/'/g, "''") 转义单引号 |
| 错误恢复超时 | useAppState.tsx |
Agent 错误 3 秒后自动重置 |
| 取消支持 | agent.ts |
AbortSignal 支持用户中途停止 |
| 递归限制 | agent.ts |
recursionLimit: 50 防止无限工具循环 |
| 输出截断 | tools.ts (read) |
大文件截断到 50000 字符 |
| 结果限制 | 所有工具 | 搜索结果默认 10 条,grep 100 条,Cypher 50 行 |
十一、总结
GitNexus 的 Graph RAG Agent 是一个纯浏览器端运行的 LangGraph React Agent 系统,它巧妙地解决了"让 LLM 理解代码结构"这个核心问题,其设计哲学可以概括为:
让工具做重活,让 LLM 做推理。
- 7 个 Graph RAG 工具封装了所有图数据库操作,LLM 不需要理解 Cypher 或图遍历
- 系统提示词采用"约束优先"设计,强制执行引用和验证以避免幻觉
- 双模式流式引擎实现了 Cursor 式的思考→操作→回答的流畅体验
- 支持 9 家 LLM 提供商,通过适配器模式统一接口
- 动态上下文注入让 LLM 在首次回答前就了解代码库规模