12 KiB
Arabica Sprint 3 开发指南
1. 模块概览与架构设计
Sprint 3 的核心是完成系统的“智能升维”:不再采用向客户端 UI 暴露的 Prompts 原语,将大模型升级为拥有完整上下文的高级智能体(Agent)。通过意图识别,系统自动路由至对应的工具(Tools),并在底层彻底解决大模型长文本转义崩溃与工具死循环问题。
整体架构精简为高度内聚的三层模型:
- 接入层:
app.ts负责初始化 MCP 服务器,注册 5 大核心工具,并放宽参数类型(z.any())以承接畸形负载。 - 控制层(核心大脑):
toolsController.ts充当路由网关与防火墙,处理参数兜底序列化、JSON 框架外壳剥离及强指令注入。 - 服务层:
arxivService.ts(学术网关)与resourceService.ts(本地存储网关)处理具体的外部/内部 I/O。
下图展示了模块间的静态关系:
graph TD
A[app.ts 接入层] --> B[toolsController 核心路由]
A --> C[resource 资源注册]
B --> D[arxivService 学术检索]
B --> E[resourceService 本地文件操作]
B --> F[本地 frameworks JSON]
D --> G[arXiv API]
E --> H[本地 Markdown 知识库]
C --> E
2. 函数列表和调用逻辑关系
2.1 完成函数列表
以下是 Sprint 3 项目中所有显式定义的核心函数,按文件分组。(注:Sprint 2 中的 intentService.ts、promptsController.ts 和 promptService.ts 已在本次迭代中删除)
1. app.ts(入口文件)
| 函数名 | 参数 | 描述 | 异步 |
|---|---|---|---|
start |
无 | 初始化 MCP 服务器,连接 STDIO 传输层 | ✅ |
2. toolsController.ts(工具控制器 & 路由大脑)
| 函数名 | 参数 | 描述 | 异步 |
|---|---|---|---|
handleToolCall |
toolName: string, params: any |
统一入口,根据工具名分发到具体处理函数 | ✅ |
handleSearchArxiv |
params: any |
处理 search_arxiv,调用外部 API 并格式化结果 |
✅ |
handleFetchFramework |
params: any |
处理 fetch_framework_template,剥离 JSON 注入刹车指令 |
✅ |
handleSaveNote |
params: any |
处理 save_note,执行 JSON 对象容错与序列化 |
✅ |
handleListLocalNotes |
无 | 处理 list_local_notes,返回本地笔记清单 |
✅ |
handleReadLocalNote |
params: any |
处理 read_local_note,读取具体笔记 |
✅ |
3. arxivService.ts(学术检索服务)
| 函数名 | 参数 | 描述 | 异步 |
|---|---|---|---|
searchArxiv |
query: string, maxResults: number |
发起 HTTP 请求,解析 arXiv XML 返回标准化文献数组 | ✅ |
4. resourceService.ts(资源服务 - 维持 S2 核心)
| 函数名 | 参数 | 描述 | 异步 |
|---|---|---|---|
listObsidianNotes |
无 | 列出知识库目录下所有 .md 文件 |
✅ |
readObsidianNote |
filename: string |
读取指定笔记文件内容,含路径防穿越校验 | ✅ |
saveNote |
filename: string, content: string |
将内容安全写入本地知识库 | ✅ |
2.2 整体函数调用关系图
代码段
graph TD
subgraph "接入层 app.ts"
A1["server.tool (search_arxiv)"] -->|分发| B1[handleToolCall]
A2["server.tool (fetch_framework)"] -->|分发| B1
A3["server.tool (save_note)"] -->|分发| B1
A4["server.resource (local-notes)"] -->|list/read 回调| C1[resourceService]
end
subgraph "控制器 (防火墙与路由) toolsController"
B1 -->|匹配意图 1| D1[handleSearchArxiv]
B1 -->|匹配意图 2| D2[handleFetchFramework]
B1 -->|匹配意图 3| D3[handleSaveNote]
end
subgraph "服务层"
D1 -->|发起网络请求| E1[arxivService.searchArxiv]
D2 -->|读取本地配置| E2[(frameworks/*.json)]
D3 -->|容错兜底后写盘| E3[resourceService.saveNote]
end
3. 核心业务代码实现
3.1 接入层 (app.ts)
3.1.1 模块职责与设计目标
-
类型收敛与编译修复:通过显式断言
type: "text" as const,解决 TypeScript 无法推断 MCP 严格字面量类型的编译报错。 -
宽容参数输入:将容易引发大模型转义崩溃的长文本参数(如
user_input、content)从z.string()放宽至z.any(),将容错权下放给 Controller 层。
3.1.2 核心代码解析
// ==========================================
// 意图 3: 保存笔记工具 (高容错版)
// ==========================================
server.tool(
'save_note',
{
filename: z.string().describe('生成的文件名,必须以 .md 结尾'),
// 💡 关键容错:放宽校验,接纳大模型误传的 JSON Object
content: z.any().describe('需要保存的完整内容文本,支持传入JSON对象。')
},
async (args) => {
const result = await handleToolCall('save_note', args);
return {
isError: result.isError,
// 💡 显式字面量断言,修复 TS 报错
content: result.content.map((item: any) => ({ type: "text" as const, text: item.text }))
};
}
);
关键解析:
-
放宽类型校验 (z.any()):大语言模型在生成包含大量 Markdown 语法的长篇报告时,极易因未闭合的引号或换行符导致底层的 JSON RPC 解析崩溃(AI_JSONParseError)。将其由严格的 z.string() 放宽为 z.any(),使得系统允许大模型即使错误地传入了一个嵌套 JSON 对象,也能顺利进入 Controller 层。
-
字面量断言 (as const):MCP SDK 在强类型下强制要求返回的 content 数组内元素 type 必须为特指的字面量 "text",使用 as const 断言完美消除了 TypeScript 宽泛类型推断带来的编译时报错。
3.2 控制层 (toolsController.ts)
3.2.1 模块职责与设计目标
控制层在 Sprint 3 中化身为**“智能体的大脑与护栏”**。
-
抗死循环(Anti-Loop):处理
fetch_framework_template时,拦截原始 JSON 结构,提取纯文本,并强行拼接业务指令阻断模型乱调工具。 -
长文本容错(Fallback Serialization):处理
save_note时,拦截畸形的 JSON 入参并自动序列化,确保平滑写盘。
3.2.2 核心逻辑流程与代码
3.2.2.1 handleFetchFramework (抗死循环剥离器)
- 调用序列图
sequenceDiagram
participant LLM as 大模型
participant Ctrl as toolsController
participant FS as 本地文件系统
LLM->>Ctrl: 请求工具 fetch_framework (SWOT)
Ctrl->>FS: 读取 swot.json
FS-->>Ctrl: 原始 JSON 字符串
Ctrl->>Ctrl: JSON.parse() 提取 system / user prompt
Ctrl->>Ctrl: 组装纯文本,并在头部拼接【🛑立即停止调用工具】
Ctrl-->>LLM: 返回纯文本强指令
LLM->>LLM: 遵守指令,停止工具调用,开始输出分析文本
- 核心代码
async function handleFetchFramework(params: any) {
const name = params.framework_name?.toLowerCase();
// ... 校验框架有效性并读取 JSON 文件 ...
const parsedData = JSON.parse(fileContent);
// 提取出真正的 Prompt 文本,绝对不给大模型看 JSON 壳子
const sysMsg = parsedData.messages?.find((m: any) => m.role === 'system')?.content?.text || '';
const userMsg = parsedData.messages?.filter((m: any) => m.role === 'user').pop()?.content?.text || '';
// 组装极强约束的系统指令,强制大模型刹车
const returnText = `【🛑 最高优先级的底层执行指令 🛑】
你已经成功获取了 ${name.toUpperCase()} 框架的模板。现在,请你**立即停止调用任何工具**!绝对不要重复调用 fetch_framework_template!
请直接在聊天框中为用户输出最终的分析报告。
【系统设定】:\n${sysMsg}\n
【分析结构要求】:\n${userMsg}\n
⚠️ 最终要求:报告输出完毕后,你必须向用户提问:“分析完毕,是否需要将此分析结果保存成笔记?”`;
return { content: [{ type: 'text', text: returnText }] };
}
关键解析:
-
JSON 外壳剥离:如果直接将带有 {{variable}} 等变量占位符和嵌套结构的原始 JSON 返回给大模型,模型极大概率会产生“幻觉”,认为这是需要它进一步填空后重新发起请求的数据结构。提取内部纯文本能够大幅降低模型的理解成本。
-
强制刹车指令 (🛑 立即停止...):这是解决大模型陷入“工具无限死循环”的核心护城河。通过在返回文本首部注入最高优先级的底层指令,能够强制打断其内部的 Tool-Call 冲动,迫使其状态机切回文本生成模式。
3.2.2.2 handleSaveNote (兜底序列化)
核心代码
async function handleSaveNote(params: any) {
if (!params.filename || !params.content) {
throw new Error("保存失败:缺少 filename 或 content 参数");
}
// 💡 核心容错逻辑:帮大模型擦屁股处理奇葩格式
let contentToSave = '';
if (typeof params.content === 'string') {
contentToSave = params.content;
} else {
// 若大模型传了深度嵌套的对象,在底层帮它转成带缩进的字符串
contentToSave = JSON.stringify(params.content, null, 2);
}
const message = await saveNote(params.filename, contentToSave);
return { content: [{ type: 'text', text: message }] };
}
关键解析:
-
隐式类型转换(兜底机制):完美承接了接入层中放开的 z.any() 校验。当大模型发生幻觉,自作主张地将整篇分析报告包装成了类似 { "title": "...", "data": "..." } 的对象传入时,这段底层逻辑会悄无声息地帮它执行 JSON.stringify(..., null, 2),完成序列化。
-
业务平滑流转:通过这种代码层的宽容处理,避免了仅仅因为一个参数类型错误就让模型前序耗费大量 Token 思考和生成的长篇内容全部作废,极大提升了工程鲁棒性。
3.3 服务层 (arxivService.ts)
3.3.1 模块职责
专注于与 arXiv 官方 API 通信。其核心使命是将原本极其复杂的学术 XML 标签(包含海量对大模型无用的元数据)清洗、降维为大模型能够轻松理解的极简 Markdown 列表。
3.3.2 核心逻辑流
sequenceDiagram
participant Ctrl as toolsController
participant Svc as arxivService
participant API as arXiv API
Ctrl->>Svc: searchArxiv('quantum', 5)
Svc->>API: fetch('[http://export.arxiv.org/api/query?search_query=all:quantum&max_results=5](http://export.arxiv.org/api/query?search_query=all:quantum&max_results=5)')
API-->>Svc: XML 格式的学术数据
Svc->>Svc: 解析 XML,提取 entry (title, id, summary)
Svc->>Svc: 清洗换行符,映射为统一的 Literature Object
Svc-->>Ctrl: 返回极简对象数组
4. 测试与质量保障 (Testing & QA)
Sprint 3 引入了 Jest 自动化测试套件,重点关注工具控制层的路由与容错能力:
-
网络拦截与 Mock:在
arxivService.test.ts中,必须通过global.fetch = jest.fn()拦截真实网络请求,确保测试在离线环境下秒级通过。 -
容错机制断言:在
toolsController.test.ts中,编写专门针对save_note传入 JSON Object 的断言(Assertion),验证resourceService.saveNote被调用时接收到的是否为序列化后的字符串。 -
刹车指令探针:测试
fetch_framework_template时,断言返回的content.text必须toContain('立即停止调用任何工具'),确保防死锁底座生效。
许可声明
本文档采用 知识共享署名-相同方式共享 4.0 国际许可协议 (CC BY-SA 4.0) 进行许可,© 2025-2026 Gitconomy Research.