Compare commits
108 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
f12230c9ec | |
|
|
afbde1931f | |
|
|
a5095dcb40 | |
|
|
1f09296e12 | |
|
|
34c531f48f | |
|
|
80f8a4500d | |
|
|
c8ea0fde85 | |
|
|
1592c1550b | |
|
|
81b4449aa7 | |
|
|
ba9ef8e9b4 | |
|
|
436f1b690f | |
|
|
8253826118 | |
|
|
05e110c328 | |
|
|
f5c4222b31 | |
|
|
e0891bec49 | |
|
|
3c9878e84d | |
|
|
ae636203af | |
|
|
c8ad7c1950 | |
|
|
a5cb36245e | |
|
|
ea3b3e6d8f | |
|
|
d60587fd88 | |
|
|
587d4ae967 | |
|
|
2a6ac629de | |
|
|
d2268f4930 | |
|
|
95ece61847 | |
|
|
4227ea8aff | |
|
|
7043da7165 | |
|
|
96573c173a | |
|
|
a8c28bf7ba | |
|
|
b310bc85e5 | |
|
|
9a4621ddb9 | |
|
|
7ca6e342ad | |
|
|
47fda0dc01 | |
|
|
43fc7faced | |
|
|
3a437eb739 | |
|
|
7b664bb836 | |
|
|
634b54f8f6 | |
|
|
1decb41fe9 | |
|
|
6e318f8844 | |
|
|
9d1a7ab005 | |
|
|
98413b372c | |
|
|
99e7a2f772 | |
|
|
da9277c084 | |
|
|
94f37630cd | |
|
|
f85261ff2e | |
|
|
f11560b639 | |
|
|
6917812cc9 | |
|
|
a256bd2885 | |
|
|
52267a223b | |
|
|
1a4909bbca | |
|
|
98568b30de | |
|
|
659193f40b | |
|
|
167c4f30cc | |
|
|
5058d8dc54 | |
|
|
6c655020af | |
|
|
7f3ef73e6c | |
|
|
a2d4d145d0 | |
|
|
8b9d56b902 | |
|
|
ef7ce9186d | |
|
|
9ccc32ed83 | |
|
|
6998f91caa | |
|
|
443ba981ad | |
|
|
8d127c8ee4 | |
|
|
23c0147b3b | |
|
|
de31311126 | |
|
|
b8cd6925e7 | |
|
|
74a46c2102 | |
|
|
d2f1663620 | |
|
|
56f391b860 | |
|
|
c8b269f48c | |
|
|
41009ddce8 | |
|
|
623bf36a4c | |
|
|
8001da9906 | |
|
|
321c439990 | |
|
|
d27d4f84c9 | |
|
|
49f2cabd1a | |
|
|
e904c1d140 | |
|
|
0c0b2d88bb | |
|
|
d08b7ca6bd | |
|
|
8f3ca7f9cc | |
|
|
3643814616 | |
|
|
b0d28b630f | |
|
|
74072b63c3 | |
|
|
145212d55e | |
|
|
bb32d370ae | |
|
|
c4d4f4859b | |
|
|
8b6c74954a | |
|
|
97ad2d648e | |
|
|
e42632e56b | |
|
|
568ee82e54 | |
|
|
3d547131e6 | |
|
|
8d20eef8e9 | |
|
|
3470f2d4b3 | |
|
|
e9a68b83a3 | |
|
|
0fa70fa6c7 | |
|
|
87bcf71d79 | |
|
|
47e1789eb8 | |
|
|
7b08124fdf | |
|
|
1f38a310af | |
|
|
c425dc47b6 | |
|
|
1b834bb404 | |
|
|
2bb057b0de | |
|
|
950590f384 | |
|
|
bdeda110d2 | |
|
|
c48f7cfa23 | |
|
|
431d2a8e56 | |
|
|
38aa45fbb0 | |
|
|
04feef1562 |
|
|
@ -0,0 +1,127 @@
|
|||
<!--
|
||||
---
|
||||
title: Project Caffeine Changelog (更新日志)
|
||||
description: 记录 Project Caffeine 项目的所有显著更改、版本迭代与发布历史,遵循 Keep a Changelog 与语义化版本规范。
|
||||
type: Changelog
|
||||
version: v0.0.3 (Arabica) - Sprint 3
|
||||
file: CHANGELOG.md
|
||||
author: Gitconomy Research-郭晧
|
||||
date: 2026-03-11
|
||||
tags:
|
||||
- Project Caffeine
|
||||
- Changelog
|
||||
- Release Notes
|
||||
- Version Control
|
||||
license: CC BY-SA 4.0
|
||||
status: Active
|
||||
---
|
||||
-->
|
||||
# Changelog (更新日志)
|
||||
|
||||
本项目的所有显著更改都将记录在此文件中。
|
||||
|
||||
本项目遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/) 规范,并且项目版本号采用 [Semantic Versioning (语义化版本)](https://semver.org/lang/zh-CN/)。
|
||||
|
||||
---
|
||||
|
||||
## [0.0.3] - 2026-03-10
|
||||
|
||||
### Added (新增)
|
||||
|
||||
- **意图驱动路由引擎**:全面重构系统提示词,确立四大核心意图映射(文献查询、框架分析、保存内容、本地笔记分析),使大模型能够根据自然语言智能调度专属工具。
|
||||
- **学术文献检索接入**:新增 `search_arxiv` 原子工具与 `arxivService.ts`,支持基于提取的核心关键字调用 Arxiv API,实时获取学术论文并结构化输出。
|
||||
- **结果落盘持久化**:优化 `save_note` 工具,允许大模型在获取用户明确授权后,将生成的长篇分析报告或检索结果一键保存为本地 Markdown 文件。
|
||||
- **内部模板获取工具**:新增 `fetch_framework_template` 后台工具,专供大模型内部读取 SWOT、SCQA 等 JSON 框架配置,不再向客户端 UI 暴露。
|
||||
|
||||
### Changed (变更)
|
||||
|
||||
- **系统架构降维升级**:系统架构从“UI 按钮/提示词驱动(Prompt-driven)”彻底转向“自然语言/工具驱动(Tool-driven)”。
|
||||
- **参数宽容度放宽 (Parameter Tolerance)**:将 `save_note` 等工具的核心负载参数类型校验由严格的 `z.string()` 放宽至 `z.any()`,并在底层 `toolsController.ts` 中引入自动序列化机制(`JSON.stringify`),以承接大模型输出的畸形对象。
|
||||
|
||||
### Deprecated (废弃)
|
||||
|
||||
- **MCP Prompts 原语暴露**:废弃了向客户端 UI 直接暴露的 `prompts/list` 和 `prompts/get` 接口,改由大模型在后台自主调用工具获取模板。
|
||||
|
||||
### Removed (移除)
|
||||
|
||||
- 移除了 Sprint 2 时期遗留的 5 个硬编码 `server.prompt` 注册代码。
|
||||
|
||||
### Fixed (修复)
|
||||
|
||||
- **大模型 JSON 转义崩溃**:彻底修复了因大模型未能正确转义超长 Markdown 文本和嵌套引号,导致底层触发 `AI_JSONParseError` 并中断工具调用的核心故障。
|
||||
- **无限循环调用死锁 (Infinite Tool Call Loop)**:修复了大模型在读取原始 JSON 框架文件后由于“困惑”而导致的工具循环调用崩溃;通过在 `toolsController.ts` 中剥离 JSON 外壳,并向大模型强制注入“🛑立即停止调用任何工具”的底层防呆指令解决。
|
||||
- **TypeScript 强类型推断错误**:修复了 MCP SDK 中因 `type` 字段推断为泛用 `string` 而引发的编译报错,通过显式声明 `type: "text" as const` 解决。
|
||||
|
||||
### Security (安全)
|
||||
|
||||
- **强指令防越权写入**:在系统提示词与底层逻辑中双重加固防线,设定【绝对红线】,严禁大模型在未经主动提问并获取用户明确(如“是”、“保存”)授权前,私自调用 `save_note` 执行写盘操作。
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
## [0.0.2] - 2026-03-06
|
||||
|
||||
### Added (新增)
|
||||
|
||||
- **接入 MCP Prompts 原语**:新增 `prompts/list` 和 `prompts/get` 接口,向大模型暴露静态思维框架模板,支持降低前置上下文长度。
|
||||
- **多维静态思维框架库**:在 `src/models/frameworks/` 目录下新增 `5W3H`、`SCQA`、`SWOT`、`PESTLE` 等基于 JSON 格式的静态思维框架模板。
|
||||
- **新增意图拆解工具**:开发 `generate_search_queries` 工具,支持将用户模糊的自然语言查询自动拆解为 3-5 个专业检索词,为后续文献检索提供广度解析。
|
||||
- **输入参数严格校验**:在 `schemas.ts` 中基于 Zod 新增针对 `generate_search_queries` 工具及 Prompts 原语的强类型参数校验规则。
|
||||
- **底层角色矩阵与输出规范**:建立多智能体角色矩阵 (Persona Matrix) 雏形,通过系统消息 (System Prompt) 及 Few-Shot 示例,**强制约束大模型输出标准的 Markdown 格式报告**。
|
||||
- **测试与质量保障体系**:制定《Project Caffeine 项目测试规范指南》与《MCP Inspector 使用说明文档》,确立包含单元测试、协议集成、负载性能与安全审计的四级自动化测试体系。
|
||||
|
||||
### Changed (变更)
|
||||
|
||||
- **重构提示词服务**:将 `promptService.ts` 升级为多框架管理器,支持从本地静态 JSON 文件中动态加载思维框架库。
|
||||
- **扩展工具控制器**:更新 `toolsController.ts`,新增对意图拆解服务的路由分发能力。
|
||||
- **优化构建脚本**:在 `package.json` 的 `build` 脚本中引入跨平台构建工具 `copyfiles`,以确保在执行 `tsc` 编译时,静态 JSON 文件能够自动同步至 `dist/models/frameworks/` 目录 _(依据历史对话)_。
|
||||
|
||||
### Deprecated (废弃)
|
||||
|
||||
_(无)_
|
||||
|
||||
### Removed (移除)
|
||||
|
||||
_(无)_
|
||||
|
||||
### Fixed (修复)
|
||||
|
||||
- **静态资源编译丢失问题**:修复了因 TypeScript 原生编译器 (`tsc`) 不拷贝非 `.ts` 文件,导致运行时大模型发起 `prompts/get` 请求时抛出 `MCP error -32603: 获取框架失败` 的问题 _(依据历史对话)_。
|
||||
|
||||
### Security (安全)
|
||||
|
||||
- **非法参数防注入**:通过引入 Zod 模型层校验,在服务端自动拦截因客户端大模型未正确生成必填参数(如 SCQA 框架缺失 `situation` 字段)而导致的无效负载,并标准抛出 `JSON-RPC -32602` 错误机制。
|
||||
|
||||
---
|
||||
|
||||
## [0.0.1] - 2026-03-03
|
||||
|
||||
### Added (新增)
|
||||
|
||||
- **初始化本地基础设施**:基于 Node.js (v18+) 和 TypeScript 搭建底层架构,配置主入口 `src/app.ts` 实例化官方 MCP SDK。
|
||||
- **零网络开销通信**:实现基于 `stdio` (标准输入输出) 传输层的本地环境工作流,支持 Cherry Studio 无缝挂载。
|
||||
- **单点提示词策略引擎**:开发纯本地业务逻辑 `promptService.ts`,向客户端注册 `generate_5_whys` 工具,支持将查询主题拆解为 5 Whys 多层追问。
|
||||
- **本地知识库集成**:内置数据适配器,暴露 `list_local_notes` 和 `read_local_note` 两个核心工具,支持大模型直接读取本地 Obsidian (.md) 文件夹内容。
|
||||
- **知识库资源暴露**:新增被动资源读取协议 `obsidian-index` (`obsidian://vault/index`),向客户端暴露本地知识库的完整目录结构。
|
||||
- **源码级联调环境**:配置 `tsconfig.json` 生成 `sourceMap`,并在 `.vscode/launch.json` 中配置 `--inspect=9229` 端口映射,**实现基于底层 Node 进程的断点与日志监控**。
|
||||
|
||||
### Changed (变更)
|
||||
|
||||
_(无)_
|
||||
|
||||
### Deprecated (废弃)
|
||||
|
||||
_(无)_
|
||||
|
||||
### Removed (移除)
|
||||
|
||||
_(无)_
|
||||
|
||||
### Fixed (修复)
|
||||
|
||||
_(无)_
|
||||
|
||||
### Security (安全)
|
||||
|
||||
- **沙箱隔离与越权防御**:在 `read_local_note` 本地资源服务中实现**严格的路径防穿越(Path Traversal)安全校验**,将 AI 生成的指令视为不可信负载,直接拦截并阻断读取指定工作目录之外的恶意文件请求。
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
<!--
|
||||
---
|
||||
title: "第三方开源软件声明 (Open Source Software Disclosure)"
|
||||
description: "列出 Project Caffeine (Arabica) 版本所使用的第三方开源组件,包括生产依赖和开发测试依赖,并说明合规性和许可信息。"
|
||||
type: "NOTICE"
|
||||
project: "Project Caffeine (Arabica)"
|
||||
version: "v1.0.0"
|
||||
file: "NOTICE.md"
|
||||
author: "Gitconomy Research-郭晧"
|
||||
date: "2026-03-11"
|
||||
tags:
|
||||
- "Project Caffeine"
|
||||
- "Arabica"
|
||||
- "Open Source"
|
||||
- "Dependencies"
|
||||
- "Compliance"
|
||||
license: "CC BY-SA 4.0"
|
||||
status: "Active"
|
||||
---
|
||||
-->
|
||||
# 第三方开源软件声明 (Open Source Software Disclosure)
|
||||
|
||||
**Project Caffeine** 的开发与运行离不开开源社区的卓越贡献。为了遵守各开源软件的授权协议(如 MIT、Apache License 2.0 等)并表达我们的敬意,特在此声明本项目所引入和使用的第三方开源库及技术组件。
|
||||
|
||||
本项目在未修改第三方源代码的前提下,通过包管理器(NPM)将其作为动态链接库或工具链引入。以下列出的所有开源组件的版权和最终解释权均归属于其原始作者或开源组织。
|
||||
|
||||
---
|
||||
|
||||
## 1. 核心运行依赖
|
||||
|
||||
这些组件是 Project Caffeine 作为 MCP Server 在生产环境中稳定运行所必需的基础底座。
|
||||
|
||||
| 组件名称 (Package) | 许可证 (License) | 版本参考 | 用途说明 / 业务模块 | 项目主页 / 代码库 |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| **@modelcontextprotocol/sdk** | MIT | `^1.x.x` | **【核心底座】** 提供 MCP (Model Context Protocol) 官方协议的 SDK 实现,用于与大模型客户端(如 Cherry Studio)建立 stdio 通信层。 | [GitHub](https://github.com/modelcontextprotocol/typescript-sdk) |
|
||||
| **zod** | MIT | `^3.x.x` | **【数据护栏】** 提供 TypeScript 优先的 Schema 声明与参数校验,用于拦截大模型生成的畸形 JSON 负载,保障写入安全。 | [GitHub](https://github.com/colinhacks/zod) |
|
||||
| **axios** | MIT | `^1.x.x` | **【网络请求】** 基于 Promise 的 HTTP 客户端,在 `arxivService.ts` 中用于向 arXiv 官方 API 发起稳定、可配置的异步学术文献检索请求。 | [GitHub](https://github.com/axios/axios) |
|
||||
| **fast-xml-parser** | MIT | `^4.x.x` | **【数据清洗】** 高性能 XML 解析器,用于在 `arxivService.ts` 中将 arXiv 返回的复杂 XML 学术元数据解析并降维为 JSON 对象。 | [GitHub](https://github.com/NaturalIntelligence/fast-xml-parser) |
|
||||
|
||||
---
|
||||
|
||||
## 2. 开发与测试依赖
|
||||
|
||||
这些组件仅用于项目的本地开发、编译构建和自动化测试,不会打包到最终的生产环境运行逻辑中。
|
||||
|
||||
| 组件名称 (Package) | 许可证 (License) | 用途说明 / 业务模块 | 项目主页 / 代码库 |
|
||||
| :-------------- | :------------ | :------------------------------------------------------------------------------------------------ | :-------------------------------------------------------------------- |
|
||||
| **typescript** | Apache 2.0 | **【核心语言】** 提供静态类型系统与编译能力,将 `.ts` 源码编译为 Node.js 可执行的 `.js` 代码。 | [GitHub](https://github.com/microsoft/TypeScript) |
|
||||
| **jest** | MIT | **【自动化测试】** 核心单元测试框架,用于驱动 `toolsController.test.ts` 等容错机制与路由逻辑的自动化断言。 | [GitHub](https://github.com/jestjs/jest) |
|
||||
| **ts-jest** | MIT | **【测试编译】** Jest 的 TypeScript 预处理器,使 Jest 能够直接运行 `.ts` 测试用例。 | [GitHub](https://github.com/kulshekhar/ts-jest) |
|
||||
| **copyfiles** | MIT | **【构建工具】** 跨平台文件复制工具,用于在执行 `npm run build` 时将 `src/models/frameworks/` 下的静态 JSON 模板同步至 `dist` 目录。 | [GitHub](https://github.com/calvinmetcalf/copyfiles) |
|
||||
| **@types/node** | MIT | Node.js 核心 API(如 `fs`, `path`)的 TypeScript 类型定义文件。 | [DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped) |
|
||||
| **@types/jest** | MIT | Jest 测试框架的 TypeScript 类型定义文件。 | [DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped) |
|
||||
|
||||
---
|
||||
|
||||
## 3. 运行环境限制
|
||||
|
||||
* **Node.js**: 本项目运行依赖于 [Node.js](https://nodejs.org/) 运行时(推荐 v20+)。Node.js 核心受其自身特定的开源许可证组合保护(主要为 MIT 协议)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 合规性声明
|
||||
|
||||
1. **分发与修改**:本项目(Project Caffeine)本身的核心业务代码使用 **MIT 协议** 授权,文档库采用 **CC BY-SA 4.0** 授权。用户在克隆、分发或商业化应用本项目时,亦被默认视为同意其底层依赖项的附属协议。
|
||||
2. **免责声明 (Disclaimer)**:本文档中列出的第三方开源组件均“按原样(AS IS)”提供,不带有任何明示或暗示的担保。Project Caffeine 项目组不对由于使用这些第三方库而导致的任何直接、间接或附带损失承担法律责任。
|
||||
3. **协议兼容性**:经核对,本项目使用的第三方包均采用极其宽泛、友好的开源许可证(MIT 与 Apache 2.0),完全兼容企业内部闭源使用或二次开源分发。
|
||||
|
||||
---
|
||||
|
||||
> **致谢 (Acknowledgments)** > 站在巨人的肩膀上才能看得更远。感谢以上开源项目的维护者与贡献者,是你们的无私奉献让 Project Caffeine 的架构演进成为可能!
|
||||
83
README.md
|
|
@ -1,20 +1,20 @@
|
|||
<!--
|
||||
---
|
||||
title: "Project Caffeine README"
|
||||
description: "基于 Model Context Protocol (MCP) 协议的研报智能体系统项目介绍、架构蓝图、技术栈说明及开发路线图"
|
||||
type: "README"
|
||||
version: "v1.0.1 (Arabica)"
|
||||
file: "README.md"
|
||||
author: "Gitconomy Research-郭晧"
|
||||
title: Project Caffeine README
|
||||
description: 基于 Model Context Protocol (MCP) 协议的研报智能体系统项目介绍、架构蓝图、技术栈说明及开发路线图
|
||||
type: README
|
||||
version: v1.0.3(Arabica)
|
||||
file: README.md
|
||||
author: Gitconomy Research-郭晧
|
||||
date: 2026-02-28
|
||||
last-update: 2026-03-01
|
||||
update-description: "增加Project Caffeine MVP Srpint1 系统设计说明"
|
||||
last-update: 2026-03-08
|
||||
update-description: 新增Arabica v0.0.3(Sprint3)架构设计和开发文档指南的链接
|
||||
tags:
|
||||
- Project Caffeine
|
||||
- MCP
|
||||
- AI Agent
|
||||
license: "CC BY-SA 4.0"
|
||||
status: "Active"
|
||||
license: CC BY-SA 4.0
|
||||
status: Active
|
||||
---
|
||||
-->
|
||||
# ☕ Project Caffeine 项目
|
||||
|
|
@ -41,17 +41,34 @@ status: "Active"
|
|||
|
||||

|
||||
|
||||
在项目的MVP阶段(版本 `Arabica`)中,我们首先设计了 **3 个核心 MCP 服务端模块**作为系统的基础架构地基:
|
||||
根据提供的系统拓扑图及项目文档,Project Caffeine 的系统框架设计基于 **MCP (Model Context Protocol)** 架构,旨在构建一个深度研究助理智能体。该系统通过解耦执行、策略与数据层,实现了从意图拆解到学术文献检索,再到本地知识库沉淀的全链路闭环。
|
||||
|
||||
1. **S1: 文献查询 Server (执行者 )**
|
||||
* **职能**:作为系统的底层抓取与 I/O 节点。
|
||||
* **机制**:通过暴露标准化的工具(Tools)原语,执行外部学术 API 获取,并将结构化结果落盘为带有 YAML 元数据的 Markdown 文件。
|
||||
2. **S2: 提示词策略 Server (顾问 )**
|
||||
* **职能**:为模型提供思考框架。
|
||||
* **机制**:通过 `prompts/list` 暴露系统级提示词模板(如 SWOT、5 Whys),指导大语言模型进行意图拆解与知识盲区探究。
|
||||
3. **S3: CoT 推理 Server (分析师 )**
|
||||
* **职能**:逻辑判决与质量控制中枢。
|
||||
* **机制**:强制大模型执行多步链式推理,并实施诸如引文密度验证的学术质量控制逻辑。
|
||||
### 2.1 系统分层架构设计
|
||||
|
||||
系统由四个核心物理与逻辑区域组成,通过标准协议进行通信:
|
||||
|
||||
- **用户层 (本地运行环境)**:研究人员通过支持 MCP 的客户端(如 Claude Desktop 或 Cursor)发起指令。客户端负责大模型的上下文管理与协议调度,并通过 HTTPS 与远程大模型(如 OpenAI 或 Anthropic API)进行异步通信。
|
||||
- **MCP 传输协议层**:作为客户端与 Server 之间的桥梁,支持 **stdio**(本地进程间同步通信)和 **SSE / HTTP**(异步/远程通信)两种模式,统一采用 **JSON-RPC 2.0** 标准。
|
||||
- **单体 MCP Server (核心逻辑层)**:系统的中枢,由三大核心原语组成,负责执行具体的研究逻辑。
|
||||
- **外部与本地基础设施**:包括云端的学术数据库(如 arXiv、Semantic Scholar)和本地的持久化存储(如 Obsidian 知识库)。
|
||||
|
||||
### 2.2 MCP 三大核心原语分工
|
||||
|
||||
系统框架严格遵循 MCP 规范,将功能划分为 Tools、Prompts 和 Resources 三大部分:
|
||||
|
||||
|**原语名称**|**核心职责 (Core Responsibility)**|**典型工具与指令示例**|**所属 Server 角色**|
|
||||
|---|---|---|---|
|
||||
|**Tools (工具)**|**动作执行者**:暴露给模型的主动操作,负责与外部学术 API 通信或执行本地文件 I/O。|`search_academic_literature`, `save_to_local_vault`|**S1: 执行者 (Executor)**|
|
||||
|**Prompts (提示词)**|**策略军师**:提供结构化的思维框架模板,用于指导大模型进行意图拆解与深度推理。|`5W3H`, `SCQA`, `5 Whys`, `generate_search_queries`|**S2: 军师 (Strategist)**|
|
||||
|**Resources (资源)**|**数据管家**:被动的静态上下文数据源,允许模型以只读方式挂载本地知识库内容。|`vault://local_literature/`, `note://local/`|**S1/S3: 数据管家/分析师**|
|
||||
|
||||
### 2.3 原语协同逻辑要点
|
||||
|
||||
拓扑图展示了系统内部的协同工作流:
|
||||
|
||||
1. **动态策略驱动**:当用户输入模糊主题时,系统首先调用 **Prompts 原语** 中的 `generate_search_queries` 将意图降维并转化为专业检索词。
|
||||
2. **物理链路执行**:大模型根据拆解后的 Query,通过 **Tools 原语** 调用外部 API(如 arXiv)并执行“双轨制落盘”,将 JSON 数据转化为带有 YAML 元数据的 Markdown 文件。
|
||||
3. **知识闭环构建**:在递归深挖阶段,模型通过 **Resources 原语** 回读已保存在本地知识库中的文献卡片,确保后续的推理基于已获知的“先验知识(Learnings)”。
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -63,12 +80,17 @@ status: "Active"
|
|||
|
||||
为确保系统的高并发处理能力与协议严谨性,Project Caffeine 采用以下核心开发框架与技术标准:
|
||||
|
||||
* **核心语言与运行环境**:采用 TypeScript 与 Node.js (LTS v20+)。MCP服务端必须引入全面的异步处理模型(如 Node.js 非阻塞事件流)以应对高吞吐量的数据解析。
|
||||
* **MCP 协议与 SDK**:统一使用官方针对 TypeScript 提供的标准 SDK,深度封装底层 JSON-RPC 2.0 报文解析与状态机管理。
|
||||
* **工程化与 Monorepo**:采用原生 npm Workspaces 进行包管理,在根目录统一管控共享的 JSON-RPC Schema 与多个微服务子包,实现依赖隔离与跨服务快速编译。
|
||||
* **通信传输层** :MVP阶段采用 STDIO 协议,利用同一台机器上本地进程间的 stdin 和 stdout 管道进行直接通信,无需复杂加密握手,实现零网络传输开销。
|
||||
* **集成开发环境** :采用 Visual Studio Code (VS Code) 作为核心开发工具。需配合安装相关的 MCP 扩展插件,支持在编写代码时直接进行对话联调与协议协议测试。
|
||||
* **安全与环境管控**:协议遵循零信任架构原则,默认将AI生成的指令视为不可信负载。敏感凭证严禁硬编码,必须通过 `.env.example` 模板化并在运行环境中安全注入。
|
||||
| 层次 | 技术选型 |
|
||||
| ------------ | ---------------------------------------------------------- |
|
||||
| **核心语言** | TypeScript(所有核心功能强制使用) |
|
||||
| **运行环境** | Node.js LTS v20+(异步非阻塞 I/O) |
|
||||
| **协议层** | MCP SDK(`@modelcontextprotocol/sdk`),支持 `stdio` 与 `SSE` 传输 |
|
||||
| **包管理** | npm(单体仓库,单一 `package.json`) |
|
||||
| **校验工具** | Zod(运行时类型校验与参数验证) |
|
||||
| **HTTP 客户端** | axios(封装学术 API 调用,支持重试与速率限制) |
|
||||
| **日志工具** | winston / log4js(生产级日志记录) |
|
||||
| **测试工具** | Jest + k6(单元测试与负载压测) |
|
||||
| **调试工具** | VS Code 断点调试(通过 `--inspect` 挂载) |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -82,10 +104,11 @@ status: "Active"
|
|||
|
||||
当前开发进度:
|
||||
|
||||
| **阶段** | **主题** | **开发目标** | **核心功能实现** | **设计文档** |
|
||||
| ------------ | ------------------------- | ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
|
||||
| MVP Sprint 1 | 提示词策略 MCP Server 原型验证<br> | 部署基于 Node.js 和 Express.js 的轻量级服务,验证 MCP 协议组件间通讯、大语言模型推理等基本运行环境。<br> | 实现最简化的提示词策略 MCP Server。当用户发起查询请求时,系统调用 **5 Whys** 模板对查询进行分解,生成增强提示词,并发送至大语言模型进行深度推理分析,最终返回研究洞察。 | [Project Caffeine提示词策略MCP Server原型设计](./docs/design/project-caffeine-mvp-sprint1-architecture-design.md) |
|
||||
|
||||
| **版本** | **开发目标** | **设计文档** | 开发文档 |
|
||||
| ---------------------------------------------------- | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------- |
|
||||
| [`v0.0.1`](./projects/arabica/src/sprint1/README.md) | 部署基于 Node.js 的开发环境,验证 MCP 协议组件间通讯、大语言模型推理等基本运行环境。<br> | [Arabicat Sprint1系统设计文档](./projects/arabica/docs/design/arabica-sprint1-architecture-specification.md) | [Arabicat Sprint1系统开发文档](./projects/arabica/docs/design/arabica-sprint1-development-specification.md) |
|
||||
| [`v0.0.2`](./projects/arabica/src/sprint2/README.md) | 基于 Sprint 1 原型,扩展为支持 MCP Prompts 原语的多框架引擎,实现意图拆解工具与本地知识库集成,构建模块化、可扩展的提示词策略服务器。 | [Arabica Sprint2系统设计文档](./projects/arabica/docs/design/arabica-sprint2-architecture-specification.md) | [Arabica Sprint2系统开发文档](./projects/arabica/docs/design/arabica-sprint2-development-specification.md) |
|
||||
| [`v0.0.3`](./projects/arabica/src/sprint3/README.md) | 构建文献查询 Server,集成学术 API 实现基础外围检索能力,并开发双轨制数据落盘模块,将离散的 JSON 数据转换为带有标准 YAML 元数据的本地化 Markdown 文件。 | [Arabica Sprint3系统设计文档](./projects/arabica/docs/design/arabica-sprint3-architecture-specification.md) | [Arabica Sprint3系统开发文档](./projects/arabica/docs/design/arabica-sprint3-development-specification.md) |
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@
|
|||
图表名称:提示词策略 MCP Server 原型架构图 (Prompt Strategy MVP Architecture)
|
||||
文件命名:prompt-strategy-mcp-server-prototype-architecture.svg
|
||||
用途:展示从 Client 请求到 5 Whys 策略引擎、资源层再到大模型推理的“上下层级”完整组件链及执行流。
|
||||
版本:v2.0.0 (Top-Down Layout with 50px Spacing Adjustment)
|
||||
版本:v2.1.0 (Top-Down Layout with 50px Spacing Adjustment)
|
||||
作者:Gitconomy Research-郭晧
|
||||
SPDX-License-Identifier: MIT & CC-BY-SA-4.0
|
||||
创建日期:2026-03-01
|
||||
更新日期:2026-03-1
|
||||
更新描述:根据更新的架构设计文件,重新设计整个开发组件架构与工作流示意图
|
||||
更新日期:2026-03-5
|
||||
更新描述:更新架构图名称
|
||||
================================================================================
|
||||
-->
|
||||
|
||||
|
|
@ -67,8 +67,8 @@ SPDX-License-Identifier: MIT & CC-BY-SA-4.0
|
|||
-->
|
||||
<g id="title-block" transform="translate(640, 40)">
|
||||
<text y="0" text-anchor="middle" class="font-mono" font-size="12" fill="var(--c-neutral-gray)" letter-spacing="1">FIG-01</text>
|
||||
<text y="30" text-anchor="middle" class="font-sans" font-weight="bold" font-size="24" fill="#000000">提示词策略 MCP Server 系统架构图</text>
|
||||
<text y="55" text-anchor="middle" class="font-mono" font-size="14" fill="var(--c-neutral-gray)">架构图 > Arabica Sprint1 > 系统开发组件架构与工作流</text>
|
||||
<text y="30" text-anchor="middle" class="font-sans" font-weight="bold" font-size="24" fill="#000000">>Arabica Sprint 1 系统开发组件架构图</text>
|
||||
<text y="55" text-anchor="middle" class="font-mono" font-size="14" fill="var(--c-neutral-gray)">架构图 > Arabica Sprint1 > 单思维框架与意图拆解工作流</text>
|
||||
<rect x="-30" y="70" width="60" height="3" fill="var(--c-local-green)" />
|
||||
</g>
|
||||
|
||||
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
|
@ -0,0 +1,420 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 1000" width="100%" height="100%">
|
||||
<!--
|
||||
================================================================================
|
||||
图表名称:Arabica Sprint 2 架构图 (Arabica Sprint 2 Architecture)
|
||||
文件命名:arabica-srpint2-architecture-design.svg
|
||||
用途:展示 Project Caffeine 提示词策略 MCP Server 在 Sprint 2 中的多维思维框架接入与意图拆解工作流。
|
||||
版本:v1.0.0
|
||||
作者:Gitconomy Research-郭晧
|
||||
SPDX-License-Identifier: MIT & CC-BY-SA-4.0
|
||||
创建日期:2026-03-05
|
||||
================================================================================
|
||||
-->
|
||||
|
||||
<!-- 头部注释:图表背景声明 -->
|
||||
<!-- Background: Solid White -->
|
||||
<rect width="100%" height="100%" fill="#FFFFFF" />
|
||||
|
||||
<defs>
|
||||
<style>
|
||||
:root {
|
||||
/* 语义色定义 */
|
||||
--c-cloud-blue: #0099FF;
|
||||
--c-cloud-blue-light: rgba(0, 153, 255, 0.08);
|
||||
--c-local-green: #009900;
|
||||
--c-local-green-light: rgba(0, 153, 0, 0.08);
|
||||
--c-risk-amber: #F97316;
|
||||
--c-risk-amber-light: rgba(249, 115, 22, 0.08);
|
||||
--c-gov-blue: #3B82F6;
|
||||
--c-gov-blue-light: rgba(59, 130, 246, 0.08);
|
||||
--c-neutral-gray: #475569;
|
||||
--c-neutral-gray-light: #B2B2B2;
|
||||
|
||||
/* 步骤高亮色 */
|
||||
--c-step-red: #EF4444;
|
||||
}
|
||||
|
||||
/* 字体降级机制 */
|
||||
.font-sans { font-family: 'Noto Sans', 'Helvetica Neue', Arial, sans-serif; }
|
||||
.font-mono { font-family: 'JetBrains Mono', Consolas, 'Courier New', monospace; }
|
||||
|
||||
/* 文本层级 */
|
||||
.card-title { font-size: 15px; font-weight: bold; fill: #FFFFFF; }
|
||||
.card-text { font-size: 13px; fill: var(--c-neutral-gray); }
|
||||
.text-code { font-size: 13px; font-weight: bold; fill: #0F172A; }
|
||||
.label-text { font-size: 12px; font-weight: bold; fill: var(--c-neutral-gray); }
|
||||
.step-text { font-size: 13px; font-weight: bold; fill: #FFFFFF; }
|
||||
</style>
|
||||
|
||||
<!-- 箭头标记 -->
|
||||
<marker id="arrow-gray" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto">
|
||||
<path d="M 0 0 L 8 4 L 0 8 Z" fill="var(--c-neutral-gray)" />
|
||||
</marker>
|
||||
<marker id="arrow-green" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto">
|
||||
<path d="M 0 0 L 8 4 L 0 8 Z" fill="var(--c-local-green)" />
|
||||
</marker>
|
||||
<marker id="arrow-blue" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto">
|
||||
<path d="M 0 0 L 8 4 L 0 8 Z" fill="var(--c-cloud-blue)" />
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<!--
|
||||
===========================================================================
|
||||
顶层标题块 (Title Block)
|
||||
===========================================================================
|
||||
-->
|
||||
<g id="title-block" transform="translate(640, 40)">
|
||||
<text y="0" text-anchor="middle" class="font-mono" font-size="12" fill="var(--c-neutral-gray)" letter-spacing="1">FIG-01</text>
|
||||
<text y="30" text-anchor="middle" class="font-sans" font-weight="bold" font-size="24" fill="#000000">Arabica Sprint 2 系统开发组件架构图</text>
|
||||
<text y="55" text-anchor="middle" class="font-mono" font-size="14" fill="var(--c-neutral-gray)">架构图 > Arabica Sprint 2 > 多维思维框架与意图拆解工作流</text>
|
||||
<!-- Context Indicator: Y=70 -->
|
||||
<rect x="-30" y="70" width="60" height="3" fill="var(--c-cloud-blue)" />
|
||||
</g>
|
||||
|
||||
<!--
|
||||
===========================================================================
|
||||
架构图主体内容 (Diagram Content)
|
||||
===========================================================================
|
||||
-->
|
||||
<g id="diagram-content" transform="translate(0, 80)">
|
||||
|
||||
<!-- 物理边界与容器 -->
|
||||
<g id="boundaries" transform="translate(0, 240)">
|
||||
<!-- Node.js MCP Server Runtime Boundary -->
|
||||
<rect x="290" y="0" width="700" height="540" rx="12" fill="var(--c-local-green-light)" stroke="var(--c-local-green)" stroke-dasharray="4 4" stroke-width="2" />
|
||||
<rect x="290" y="0" width="700" height="36" fill="var(--c-local-green)" opacity="0.1" rx="12"/>
|
||||
<text x="310" y="23" class="font-mono" font-size="14" font-weight="bold" fill="var(--c-local-green)">📦 MCP Server Runtime(Sprint2) </text>
|
||||
</g>
|
||||
|
||||
<!--
|
||||
===========================================================================
|
||||
核心连线与数据流向 (Connections)
|
||||
===========================================================================
|
||||
-->
|
||||
<g id="connections" fill="none" stroke-width="2">
|
||||
<!-- User to Cherry Studio -->
|
||||
<path d="M 400 110 L 482 110" stroke="var(--c-neutral-gray)" marker-end="url(#arrow-gray)" />
|
||||
<path d="M 490 140 L 408 140" stroke="var(--c-neutral-gray)" marker-end="url(#arrow-gray)" />
|
||||
|
||||
<!-- Cherry Studio to Remote LLM -->
|
||||
<path d="M 760 100 L 892 100" stroke="var(--c-cloud-blue)" stroke-dasharray="4 2" marker-end="url(#arrow-blue)" />
|
||||
<path d="M 760 120 L 892 120" stroke="var(--c-cloud-blue)" stroke-dasharray="4 2" marker-end="url(#arrow-blue)" />
|
||||
<path d="M 900 140 L 768 140" stroke="var(--c-cloud-blue)" stroke-dasharray="4 2" marker-end="url(#arrow-blue)" />
|
||||
|
||||
<!-- stdio Transport -->
|
||||
<path d="M 600 170 L 600 292" stroke="var(--c-local-green)" marker-end="url(#arrow-green)" />
|
||||
<path d="M 640 300 L 640 178" stroke="var(--c-local-green)" marker-end="url(#arrow-green)" />
|
||||
|
||||
<!-- app.ts to Controllers -->
|
||||
<path d="M 600 370 L 600 395 L 410 395 L 410 422" stroke="var(--c-neutral-gray)" marker-end="url(#arrow-gray)" />
|
||||
<path d="M 640 370 L 640 422" stroke="var(--c-neutral-gray)" marker-end="url(#arrow-gray)" />
|
||||
<path d="M 680 370 L 680 395 L 870 395 L 870 422" stroke="var(--c-neutral-gray)" marker-end="url(#arrow-gray)" />
|
||||
|
||||
<!-- Controllers to Services -->
|
||||
<path d="M 410 490 L 410 532" stroke="var(--c-neutral-gray)" marker-end="url(#arrow-gray)" />
|
||||
<path d="M 640 490 L 640 532" stroke="var(--c-neutral-gray)" marker-end="url(#arrow-gray)" />
|
||||
<path d="M 870 490 L 870 532" stroke="var(--c-neutral-gray)" marker-end="url(#arrow-gray)" />
|
||||
|
||||
<!-- resourceService to Local Vault -->
|
||||
<path d="M 700 460 L 750 460 L 750 580 L 780 580" stroke="var(--c-neutral-gray)" marker-end="url(#arrow-gray)" />
|
||||
|
||||
<!-- Services to Schemas/Frameworks -->
|
||||
<path d="M 410 620 L 410 652" stroke="var(--c-neutral-gray)" stroke-dasharray="2 2" marker-end="url(#arrow-gray)" />
|
||||
<path d="M 640 620 L 640 652" stroke="var(--c-neutral-gray)" stroke-dasharray="2 2" marker-end="url(#arrow-gray)" />
|
||||
|
||||
<!-- toolsController to resourceService -->
|
||||
<path d="M 870 620 L 870 680" stroke="var(--c-local-green)" marker-end="url(#arrow-green)" />
|
||||
|
||||
</g>
|
||||
|
||||
<!--
|
||||
===========================================================================
|
||||
路径标签说明 (Path Labels)
|
||||
===========================================================================
|
||||
-->
|
||||
<g id="path-labels" class="font-sans label-text">
|
||||
<!-- Top Level -->
|
||||
<text x="445" y="95" text-anchor="middle">Query</text>
|
||||
<text x="445" y="160" text-anchor="middle">Report</text>
|
||||
<text x="830" y="85" text-anchor="middle" fill="var(--c-cloud-blue)">Reasoning</text>
|
||||
<text x="830" y="160" text-anchor="middle" fill="var(--c-cloud-blue)">LLM Result</text>
|
||||
|
||||
<!-- stdio Link -->
|
||||
<text x="585" y="200" text-anchor="end" fill="var(--c-local-green)">prompts/get</text>
|
||||
<text x="585" y="220" text-anchor="end" fill="var(--c-local-green)">tools/call</text>
|
||||
<text x="655" y="210" text-anchor="start" fill="var(--c-local-green)">Result Data</text>
|
||||
|
||||
<rect x="585" y="250" width="70" height="20" rx="4" fill="#FFFFFF" stroke="var(--c-local-green)" />
|
||||
<text x="620" y="264" text-anchor="middle" class="font-mono" font-size="11" fill="var(--c-local-green)">stdio</text>
|
||||
<text x="820" y="650" text-anchor="middle" class="font-mono" font-size="11" fill="var(--c-neutral-gray)">load/save</text>
|
||||
</g>
|
||||
|
||||
<!--
|
||||
===========================================================================
|
||||
架构节点卡片 (Node Cards)
|
||||
===========================================================================
|
||||
-->
|
||||
<g id="nodes">
|
||||
<!-- 1. User Node -->
|
||||
<g transform="translate(280, 95)">
|
||||
<rect x="0" y="0" width="110" height="60" rx="8" fill="#F8FAFC" stroke="var(--c-neutral-gray)" stroke-width="2" />
|
||||
<circle cx="55" cy="22" r="10" fill="var(--c-neutral-gray)" />
|
||||
<path d="M 35 50 C 35 35, 75 35, 75 50" fill="none" stroke="var(--c-neutral-gray)" stroke-width="3" stroke-linecap="round"/>
|
||||
<text x="55" y="75" text-anchor="middle" class="font-sans label-text">User / 用户</text>
|
||||
</g>
|
||||
|
||||
<!-- 2. Cherry Studio -->
|
||||
<g transform="translate(490, 80)">
|
||||
<rect x="0" y="0" width="260" height="90" rx="8" fill="var(--c-gov-blue-light)" stroke="var(--c-gov-blue)" stroke-width="2" />
|
||||
<rect x="0" y="0" width="260" height="30" fill="var(--c-gov-blue)" rx="8" />
|
||||
<rect x="0" y="20" width="260" height="10" fill="var(--c-gov-blue)" />
|
||||
<text x="130" y="20" text-anchor="middle" class="font-sans card-title">Cherry Studio (MCP Client)</text>
|
||||
<text x="130" y="55" text-anchor="middle" class="font-sans card-text" font-weight="bold">大模型对话与调度宿主</text>
|
||||
<text x="130" y="75" text-anchor="middle" class="font-mono card-text" font-size="11">Initiates stdio subprocess</text>
|
||||
</g>
|
||||
|
||||
<!-- 3. Remote LLM Brain -->
|
||||
<g transform="translate(970, 120)">
|
||||
<polygon points="0,-45 40,-20 40,20 0,45 -40,20 -40,-20" fill="var(--c-cloud-blue-light)" stroke="var(--c-cloud-blue)" stroke-width="3" />
|
||||
<text x="0" y="-5" text-anchor="middle" class="font-mono card-test" fill="var(--c-cloud-blue)" font-size="14">LLM</text>
|
||||
<text x="0" y="15" text-anchor="middle" class="font-mono card-text" font-size="10" fill="var(--c-cloud-blue)">(Remote)</text>
|
||||
</g>
|
||||
|
||||
<!-- 4. app.ts -->
|
||||
<g transform="translate(560, 300)">
|
||||
<rect x="-10" y="0" width="180" height="70" rx="6" fill="#FFFFFF" stroke="var(--c-local-green)" stroke-width="2" />
|
||||
<rect x="-10" y="0" width="180" height="26" fill="var(--c-local-green)" rx="6" />
|
||||
<rect x="-10" y="20" width="180" height="6" fill="var(--c-local-green)" />
|
||||
<text x="80" y="18" text-anchor="middle" class="font-mono card-title">app.ts</text>
|
||||
<text x="80" y="45" text-anchor="middle" class="font-sans text-code" font-size="12">McpServer 注册原语</text>
|
||||
<text x="80" y="62" text-anchor="middle" class="font-mono card-text" font-size="10">Prompts & Tools (stdio)</text>
|
||||
</g>
|
||||
|
||||
<!-- 5. promptsController.ts -->
|
||||
<g transform="translate(320, 430)">
|
||||
<rect x="0" y="0" width="180" height="60" rx="6" fill="#FFFFFF" stroke="var(--c-risk-amber)" stroke-width="2" />
|
||||
<rect x="0" y="0" width="180" height="26" fill="var(--c-risk-amber)" rx="6" />
|
||||
<rect x="0" y="20" width="180" height="6" fill="var(--c-risk-amber)" />
|
||||
<text x="90" y="18" text-anchor="middle" class="font-mono card-title">promptsController</text>
|
||||
<text x="90" y="48" text-anchor="middle" class="font-sans card-text">处理 prompts/get 和 list</text>
|
||||
</g>
|
||||
|
||||
<!-- 6. toolsController.ts -->
|
||||
<g transform="translate(550, 430)">
|
||||
<rect x="0" y="0" width="180" height="60" rx="6" fill="#FFFFFF" stroke="var(--c-risk-amber)" stroke-width="2" />
|
||||
<rect x="0" y="0" width="180" height="26" fill="var(--c-risk-amber)" rx="6" />
|
||||
<rect x="0" y="20" width="180" height="6" fill="var(--c-risk-amber)" />
|
||||
<text x="90" y="18" text-anchor="middle" class="font-mono card-title">toolsController</text>
|
||||
<text x="90" y="48" text-anchor="middle" class="font-sans card-text">处理 tools 工具调度分发</text>
|
||||
</g>
|
||||
|
||||
<!-- 7. resourcesController.ts -->
|
||||
<g transform="translate(780, 430)">
|
||||
<rect x="0" y="0" width="180" height="60" rx="6" fill="#FFFFFF" stroke="var(--c-neutral-gray-light)" stroke-width="2" stroke-dasharray="2 2" opacity="0.8"/>
|
||||
<rect x="0" y="0" width="180" height="26" fill="var(--c-neutral-gray-light)" rx="6" />
|
||||
<rect x="0" y="20" width="180" height="6" fill="var(--c-neutral-gray-light)" />
|
||||
<text x="90" y="18" text-anchor="middle" class="font-mono card-title">resourcesController</text>
|
||||
<text x="90" y="48" text-anchor="middle" class="font-sans card-text">处理 resources 请求分发</text>
|
||||
</g>
|
||||
|
||||
<!-- 8. promptService.ts -->
|
||||
<g transform="translate(320, 540)">
|
||||
<rect x="0" y="0" width="180" height="80" rx="6" fill="#FFFFFF" stroke="var(--c-local-green)" stroke-width="2" />
|
||||
<rect x="0" y="0" width="180" height="26" fill="var(--c-local-green)" rx="6" />
|
||||
<rect x="0" y="20" width="180" height="6" fill="var(--c-local-green)" />
|
||||
<text x="90" y="18" text-anchor="middle" class="font-mono card-title">promptService.ts</text>
|
||||
<text x="90" y="50" text-anchor="middle" class="font-sans text-code" font-size="12" fill="var(--c-local-green)">管理多维思维框架库</text>
|
||||
<text x="90" y="70" text-anchor="middle" class="font-sans card-text" font-size="11">返回静态结构化模板</text>
|
||||
</g>
|
||||
|
||||
<!-- 9. intentService.ts -->
|
||||
<g transform="translate(550, 540)">
|
||||
<rect x="0" y="0" width="180" height="80" rx="6" fill="#FFFFFF" stroke="var(--c-local-green)" stroke-width="2" />
|
||||
<rect x="0" y="0" width="180" height="26" fill="var(--c-local-green)" rx="6" />
|
||||
<rect x="0" y="20" width="180" height="6" fill="var(--c-local-green)" />
|
||||
<text x="90" y="18" text-anchor="middle" class="font-mono card-title">intentService.ts</text>
|
||||
<rect x="25" y="35" width="130" height="20" rx="3" fill="var(--c-risk-amber-light)" />
|
||||
<text x="90" y="50" text-anchor="middle" class="font-sans text-code" font-size="12" fill="var(--c-risk-amber)">意图拆解核心算法</text>
|
||||
<text x="90" y="70" text-anchor="middle" class="font-sans card-text" font-size="11">生成专业检索词(3~5个)</text>
|
||||
</g>
|
||||
|
||||
<!-- 10. resourceService.ts -->
|
||||
<g transform="translate(780, 540)">
|
||||
<rect x="0" y="0" width="180" height="80" rx="6" fill="#FFFFFF" stroke="var(--c-local-green)" stroke-width="2" stroke-dasharray="2 2" opacity="0.8"/>
|
||||
<rect x="0" y="0" width="180" height="26" fill="var(--c-local-green)" rx="6" />
|
||||
<rect x="0" y="20" width="180" height="6" fill="var(--c-local-green)" />
|
||||
<text x="90" y="18" text-anchor="middle" class="font-mono card-title">resourceService.ts</text>
|
||||
<text x="90" y="50" text-anchor="middle" class="font-sans text-code" font-size="12">文件系统 I/O 交互</text>
|
||||
<text x="90" y="70" text-anchor="middle" class="font-sans card-text" font-size="11">读取本地知识库</text>
|
||||
</g>
|
||||
|
||||
<!-- 11. models/frameworks -->
|
||||
<g transform="translate(320, 660)">
|
||||
<rect x="0" y="0" width="180" height="60" rx="6" fill="#F8FAFC" stroke="var(--c-neutral-gray)" stroke-width="1.5" stroke-dasharray="4 2" />
|
||||
<path d="M 0 20 L 180 20" stroke="var(--c-neutral-gray)" stroke-width="1.5" stroke-dasharray="4 2" />
|
||||
<text x="90" y="14" text-anchor="middle" class="font-mono text-code" font-size="11">models/frameworks/</text>
|
||||
<text x="90" y="38" text-anchor="middle" class="font-mono card-text" font-size="11">5w3h.json, scqa.json</text>
|
||||
<text x="90" y="52" text-anchor="middle" class="font-mono card-text" font-size="11">swot.json, pestle.json</text>
|
||||
</g>
|
||||
|
||||
<!-- 12. schemas.ts -->
|
||||
<g transform="translate(595, 660)">
|
||||
<rect x="0" y="0" width="90" height="60" rx="6" fill="#F8FAFC" stroke="var(--c-neutral-gray)" stroke-width="1.5" />
|
||||
<text x="45" y="25" text-anchor="middle" class="font-mono text-code" font-size="12">schemas.ts</text>
|
||||
<text x="45" y="45" text-anchor="middle" class="font-sans card-text" font-size="11">Zod 强校验</text>
|
||||
</g>
|
||||
|
||||
<!-- 13. External Resources -->
|
||||
<g transform="translate(820, 680)">
|
||||
<path d="M 0 15 A 50 15 0 1 0 100 15 V 60 A 50 15 0 1 1 0 60 Z" fill="var(--c-local-green-light)" stroke="var(--c-neutral-gray)" stroke-width="2" stroke-dasharray="2 2" />
|
||||
<ellipse cx="50" cy="15" rx="50" ry="15" fill="var(--c-local-green-light)" stroke="var(--c-neutral-gray)" stroke-width="2" stroke-dasharray="2 2" />
|
||||
<text x="50" y="55" text-anchor="middle" class="font-sans card-text" fill="var(--c-neutral-gray)" font-size="12">Local Vault</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!--
|
||||
===========================================================================
|
||||
步骤侧边栏与流程图注 (Execution Steps)
|
||||
===========================================================================
|
||||
-->
|
||||
<g id="execution-steps">
|
||||
<g transform="translate(440, 110)">
|
||||
<circle cx="0" cy="0" r="10" fill="var(--c-step-red)" />
|
||||
<text x="0" y="4.5" text-anchor="middle" class="font-sans step-text">1</text>
|
||||
</g>
|
||||
<g transform="translate(830, 100)">
|
||||
<circle cx="0" cy="0" r="10" fill="var(--c-step-red)" />
|
||||
<text x="0" y="4.5" text-anchor="middle" class="font-sans step-text">2</text>
|
||||
</g>
|
||||
<g transform="translate(600, 210)">
|
||||
<circle cx="0" cy="0" r="10" fill="var(--c-step-red)" />
|
||||
<text x="0" y="4.5" text-anchor="middle" class="font-sans step-text">3</text>
|
||||
</g>
|
||||
<g transform="translate(410, 505)">
|
||||
<circle cx="0" cy="0" r="10" fill="var(--c-step-red)" />
|
||||
<text x="0" y="4.5" text-anchor="middle" class="font-sans step-text">4</text>
|
||||
</g>
|
||||
<g transform="translate(830, 140)">
|
||||
<circle cx="0" cy="0" r="10" fill="var(--c-step-red)" />
|
||||
<text x="0" y="4.5" text-anchor="middle" class="font-sans step-text">5</text>
|
||||
</g>
|
||||
<g transform="translate(640, 505)">
|
||||
<circle cx="0" cy="0" r="10" fill="var(--c-step-red)" />
|
||||
<text x="0" y="4.5" text-anchor="middle" class="font-sans step-text">6</text>
|
||||
</g>
|
||||
<g transform="translate(440, 140)">
|
||||
<circle cx="0" cy="0" r="10" fill="var(--c-step-red)" />
|
||||
<text x="0" y="4.5" text-anchor="middle" class="font-sans step-text">7</text>
|
||||
</g>
|
||||
<g transform="translate(640, 210)">
|
||||
<circle cx="0" cy="0" r="10" fill="var(--c-step-red)" />
|
||||
<text x="0" y="4.5" text-anchor="middle" class="font-sans step-text">8</text>
|
||||
</g>
|
||||
<g transform="translate(870, 645)">
|
||||
<circle cx="0" cy="0" r="10" fill="var(--c-step-red)" />
|
||||
<text x="0" y="4.5" text-anchor="middle" class="font-sans step-text">9</text>
|
||||
</g>
|
||||
|
||||
<!-- 步骤流程侧边栏 -->
|
||||
<g transform="translate(20, 280)">
|
||||
<rect x="0" y="0" width="260" height="240" rx="8" fill="#FEF2F2" stroke="var(--c-step-red)" stroke-width="1.5" />
|
||||
<text x="130" y="25" text-anchor="middle" class="font-sans" font-size="14" font-weight="bold" fill="var(--c-step-red)">Sprint 2 执行步骤流</text>
|
||||
<line x1="15" y1="35" x2="245" y2="35" stroke="var(--c-step-red)" stroke-dasharray="2 2" />
|
||||
|
||||
<g transform="translate(15, 55)">
|
||||
<circle cx="6" cy="-4" r="7" fill="var(--c-step-red)" />
|
||||
<text x="6" y="0" text-anchor="middle" class="font-sans step-text" font-size="10">1</text>
|
||||
<text x="20" y="0" class="font-sans card-text" fill="#000000">用户在 Cherry Studio 输入查询</text>
|
||||
</g>
|
||||
<g transform="translate(15, 75)">
|
||||
<circle cx="6" cy="-4" r="7" fill="var(--c-step-red)" />
|
||||
<text x="6" y="0" text-anchor="middle" class="font-sans step-text" font-size="10">2</text>
|
||||
<text x="20" y="0" class="font-sans card-text" fill="#000000">大模型判断需要调用多维思维框架</text>
|
||||
</g>
|
||||
<g transform="translate(15, 95)">
|
||||
<circle cx="6" cy="-4" r="7" fill="var(--c-step-red)" />
|
||||
<text x="6" y="0" text-anchor="middle" class="font-sans step-text" font-size="10">3</text>
|
||||
<text x="20" y="0" class="font-sans card-text" fill="#000000">Client发起 prompts/get 获取模板</text>
|
||||
</g>
|
||||
<g transform="translate(15, 115)">
|
||||
<circle cx="6" cy="-4" r="7" fill="var(--c-step-red)" />
|
||||
<text x="6" y="0" text-anchor="middle" class="font-sans step-text" font-size="10">4</text>
|
||||
<text x="20" y="0" class="font-sans card-text" fill="#000000">组合模板与查询,大模型初步推理</text>
|
||||
</g>
|
||||
<g transform="translate(15, 135)">
|
||||
<circle cx="6" cy="-4" r="7" fill="var(--c-step-red)" />
|
||||
<text x="6" y="0" text-anchor="middle" class="font-sans step-text" font-size="10">5</text>
|
||||
<text x="20" y="0" class="font-sans card-text" fill="#000000">调用 tools/call 获取专业检索词</text>
|
||||
</g>
|
||||
<g transform="translate(15, 155)">
|
||||
<circle cx="6" cy="-4" r="7" fill="var(--c-step-red)" />
|
||||
<text x="6" y="0" text-anchor="middle" class="font-sans step-text" font-size="10">6</text>
|
||||
<text x="20" y="0" class="font-sans card-text" fill="#000000">Server 执行意图拆解并返回结果</text>
|
||||
</g>
|
||||
<g transform="translate(15, 175)">
|
||||
<circle cx="6" cy="-4" r="7" fill="var(--c-step-red)" />
|
||||
<text x="6" y="0" text-anchor="middle" class="font-sans step-text" font-size="10">7</text>
|
||||
<text x="20" y="0" class="font-sans card-text" fill="#000000">大模型结合检索词完成返回查询结果</text>
|
||||
</g>
|
||||
<g transform="translate(15, 195)">
|
||||
<circle cx="6" cy="-4" r="7" fill="var(--c-step-red)" />
|
||||
<text x="6" y="0" text-anchor="middle" class="font-sans step-text" font-size="10">8</text>
|
||||
<text x="20" y="0" class="font-sans card-text" fill="#000000">结构化提示词传递给模型</text>
|
||||
</g>
|
||||
<g transform="translate(15, 215)">
|
||||
<circle cx="6" cy="-4" r="7" fill="var(--c-step-red)" />
|
||||
<text x="6" y="0" text-anchor="middle" class="font-sans step-text" font-size="10">9</text>
|
||||
<text x="20" y="0" class="font-sans card-text" fill="#000000">查询结果保存到本地知识库</text>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!--
|
||||
===========================================================================
|
||||
底部图例与注释块 (Legend & Key Block)
|
||||
===========================================================================
|
||||
-->
|
||||
<g id="legend" transform="translate(640, 920)">
|
||||
<!-- 统一的半透明背景容器 -->
|
||||
<rect x="-440" y="-16" width="880" height="40" rx="4" fill="rgba(255, 255, 255, 0.9)" stroke="var(--c-neutral-gray)" stroke-width="0.5"/>
|
||||
|
||||
<g transform="translate(-420, 0)">
|
||||
<!-- 图例引导文本 -->
|
||||
<text x="0" y="8" class="font-sans" font-size="13" font-weight="bold" fill="var(--c-neutral-gray)">组件与语义对照:</text>
|
||||
|
||||
<!-- 图例项 1 -->
|
||||
<g transform="translate(130, 0)">
|
||||
<rect x="-6" y="-4" width="12" height="12" rx="2" fill="var(--c-gov-blue)" />
|
||||
<text x="12" y="8" class="font-sans" font-size="12" fill="var(--c-neutral-gray)">宿主客户端架构</text>
|
||||
</g>
|
||||
|
||||
<!-- 图例项 2 -->
|
||||
<g transform="translate(280, 0)">
|
||||
<rect x="-6" y="-4" width="12" height="12" rx="2" fill="var(--c-risk-amber)" />
|
||||
<text x="12" y="8" class="font-sans" font-size="12" fill="var(--c-neutral-gray)">请求分发控制器 (Controllers)</text>
|
||||
</g>
|
||||
|
||||
<!-- 图例项 3 -->
|
||||
<g transform="translate(490, 0)">
|
||||
<rect x="-6" y="-4" width="12" height="12" rx="2" fill="var(--c-local-green)" />
|
||||
<text x="12" y="8" class="font-sans" font-size="12" fill="var(--c-neutral-gray)">核心执行引擎与资源层</text>
|
||||
</g>
|
||||
|
||||
<!-- 图例项 4 -->
|
||||
<g transform="translate(680, 0)">
|
||||
<rect x="-6" y="-4" width="12" height="12" rx="2" fill="var(--c-neutral-gray-light)" />
|
||||
<text x="12" y="8" class="font-sans" font-size="12" fill="var(--c-neutral-gray)">Sprint 3 预留扩展 (灰色)</text>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!--
|
||||
===========================================================================
|
||||
版权许可声明 (License)
|
||||
===========================================================================
|
||||
-->
|
||||
<text x="640" y="980" text-anchor="middle" class="font-sans" font-size="11" fill="var(--c-neutral-gray)">
|
||||
本作品采用 CC-BY-SA 4.0 进行许可,© 2025-2026 Gitconomy Research社区
|
||||
</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 24 KiB |
|
|
@ -0,0 +1,445 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="140 0 1100 1200" width="100%" height="100%">
|
||||
<!--
|
||||
================================================================================
|
||||
图表名称:Arabica Sprint 3 架构图 (Arabica Sprint 3 Architecture)
|
||||
文件命名:arabica-sprint3-architecture-design.svg
|
||||
用途:展示 Project Caffeine 在 Sprint 3 中的意图路由、学术检索接入与高容错防死循环落盘机制。
|
||||
版本:v1.0.0 (Arabica) - Sprint 3
|
||||
作者:Gitconomy Research-郭晧
|
||||
SPDX-License-Identifier: MIT & CC-BY-SA-4.0
|
||||
创建日期:2026-03-11
|
||||
================================================================================
|
||||
-->
|
||||
|
||||
<!-- 头部注释:图表背景声明 -->
|
||||
<!-- Background: Solid White -->
|
||||
<rect width="100%" height="100%" fill="#FFFFFF" />
|
||||
|
||||
<defs>
|
||||
<style>
|
||||
:root {
|
||||
/* 语义色定义 */
|
||||
--c-cloud-blue: #0099FF;
|
||||
--c-cloud-blue-light: rgba(0, 153, 255, 0.08);
|
||||
--c-local-green: #009900;
|
||||
--c-local-green-light: rgba(0, 153, 0, 0.08);
|
||||
--c-risk-amber: #F97316;
|
||||
--c-risk-amber-light: rgba(249, 115, 22, 0.08);
|
||||
--c-gov-blue: #3B82F6;
|
||||
--c-gov-blue-light: rgba(59, 130, 246, 0.08);
|
||||
--c-neutral-gray: #475569;
|
||||
--c-neutral-gray-light: #B2B2B2;
|
||||
|
||||
/* 步骤高亮色 */
|
||||
--c-step-red: #EF4444;
|
||||
}
|
||||
|
||||
/* 字体降级机制 */
|
||||
.font-sans { font-family: 'Noto Sans', 'Helvetica Neue', Arial, sans-serif; }
|
||||
.font-mono { font-family: 'JetBrains Mono', Consolas, 'Courier New', monospace; }
|
||||
|
||||
/* 文本层级 */
|
||||
.card-title { font-size: 15px; font-weight: bold; fill: #FFFFFF; }
|
||||
.card-text { font-size: 13px; fill: var(--c-neutral-gray); }
|
||||
.text-code { font-size: 13px; font-weight: bold; fill: #0F172A; }
|
||||
.label-text { font-size: 12px; font-weight: bold; fill: var(--c-neutral-gray); }
|
||||
.step-text { font-size: 13px; font-weight: bold; fill: #FFFFFF; }
|
||||
.alert-text { font-size: 12px; font-weight: bold; fill: #DC2626; } /* 红色警示文字 */
|
||||
.amber-text { font-size: 12px; font-weight: bold; fill: #D97706; } /* 琥珀色强调文字 */
|
||||
</style>
|
||||
|
||||
<!-- 箭头标记:尺寸缩小三分之一以更精致呈现 -->
|
||||
<marker id="arrow-gray" markerWidth="4" markerHeight="4" refX="3.5" refY="2" orient="auto">
|
||||
<path d="M 0 0 L 4 2 L 0 4 Z" fill="var(--c-neutral-gray)" />
|
||||
</marker>
|
||||
<marker id="arrow-green" markerWidth="4" markerHeight="4" refX="3.5" refY="2" orient="auto">
|
||||
<path d="M 0 0 L 4 2 L 0 4 Z" fill="var(--c-local-green)" />
|
||||
</marker>
|
||||
<marker id="arrow-blue" markerWidth="4" markerHeight="4" refX="3.5" refY="2" orient="auto">
|
||||
<path d="M 0 0 L 4 2 L 0 4 Z" fill="var(--c-cloud-blue)" />
|
||||
</marker>
|
||||
<marker id="arrow-amber" markerWidth="4" markerHeight="4" refX="3.5" refY="2" orient="auto">
|
||||
<path d="M 0 0 L 4 2 L 0 4 Z" fill="var(--c-risk-amber)" />
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<!--
|
||||
===========================================================================
|
||||
顶层标题块 (Title Block)
|
||||
===========================================================================
|
||||
-->
|
||||
<g id="title-block" transform="translate(640, 40)">
|
||||
<text y="0" text-anchor="middle" class="font-mono" font-size="12" fill="var(--c-neutral-gray)" letter-spacing="1">FIG-02</text>
|
||||
<text y="30" text-anchor="middle" class="font-sans" font-weight="bold" font-size="24" fill="#000000">Arabica Sprint 3 系统开发组件架构图</text>
|
||||
<text y="55" text-anchor="middle" class="font-mono" font-size="14" fill="var(--c-neutral-gray)">架构图 > Arabica Sprint 3 > 意图路由、学术检索与高容错落盘机制</text>
|
||||
<!-- Context Indicator: Y=70 -->
|
||||
<rect x="-30" y="70" width="60" height="3" fill="var(--c-cloud-blue)" />
|
||||
</g>
|
||||
|
||||
<!--
|
||||
===========================================================================
|
||||
架构图主体内容 (Diagram Content)
|
||||
===========================================================================
|
||||
-->
|
||||
<g id="diagram-content" transform="translate(0, 80)">
|
||||
|
||||
<!-- 物理边界与容器 -->
|
||||
<g id="boundaries" transform="translate(0, 240)">
|
||||
<!-- Node.js MCP Server Runtime Boundary -->
|
||||
<rect x="290" y="0" width="710" height="560" rx="12" fill="var(--c-local-green-light)" stroke="var(--c-local-green)" stroke-dasharray="4 4" stroke-width="2" />
|
||||
<rect x="290" y="0" width="710" height="36" fill="var(--c-local-green)" opacity="0.1" rx="12"/>
|
||||
<text x="310" y="23" class="font-mono" font-size="14" font-weight="bold" fill="var(--c-local-green)">📦 MCP Server Runtime(Sprint3) </text>
|
||||
</g>
|
||||
|
||||
<!--
|
||||
===========================================================================
|
||||
核心连线与数据流向 (Connections)
|
||||
===========================================================================
|
||||
-->
|
||||
<g id="connections" fill="none" stroke-width="2">
|
||||
<!-- User to Cherry Studio -->
|
||||
<path d="M 400 110 L 482 110" stroke="var(--c-neutral-gray)" marker-end="url(#arrow-gray)" />
|
||||
<path d="M 490 140 L 408 140" stroke="var(--c-neutral-gray)" marker-end="url(#arrow-gray)" />
|
||||
|
||||
<!-- Cherry Studio to Remote LLM -->
|
||||
<path d="M 760 110 L 892 110" stroke="var(--c-cloud-blue)" stroke-dasharray="4 2" marker-end="url(#arrow-blue)" />
|
||||
<path d="M 900 140 L 768 140" stroke="var(--c-cloud-blue)" stroke-dasharray="4 2" marker-end="url(#arrow-blue)" />
|
||||
|
||||
<!-- stdio Transport -->
|
||||
<path d="M 600 170 L 600 292" stroke="var(--c-local-green)" marker-end="url(#arrow-green)" />
|
||||
<path d="M 640 300 L 640 178" stroke="var(--c-local-green)" marker-end="url(#arrow-green)" />
|
||||
|
||||
<!-- app.ts to Controllers -->
|
||||
<!-- To toolsController (Router Brain) -->
|
||||
<path d="M 560 370 L 560 395 L 420 395 L 420 422" stroke="var(--c-risk-amber)" stroke-width="2.5" marker-end="url(#arrow-amber)" />
|
||||
|
||||
<!-- To resourcesController -->
|
||||
<path d="M 680 370 L 680 395 L 770 395 L 770 422" stroke="var(--c-neutral-gray)" marker-end="url(#arrow-gray)" />
|
||||
|
||||
<!-- toolsController (Router) to Services -->
|
||||
<path d="M 410 495 L 410 532" stroke="var(--c-local-green)" marker-end="url(#arrow-green)" />
|
||||
<path d="M 580 495 L 580 515 L 770 515 L 770 532" stroke="var(--c-local-green)" marker-end="url(#arrow-green)" />
|
||||
|
||||
<!-- resourcesController to resourceService -->
|
||||
<path d="M 770 495 L 770 532" stroke="var(--c-neutral-gray)" marker-end="url(#arrow-gray)" />
|
||||
|
||||
<!-- arxivService to External arXiv API (Network) 穿越边界线 -->
|
||||
<path d="M 320 580 L 220 580" stroke="var(--c-cloud-blue)" stroke-dasharray="4 4" marker-end="url(#arrow-blue)" />
|
||||
|
||||
<!-- resourceService to Local Vault (File I/O) -->
|
||||
<path d="M 770 620 L 770 672" stroke="var(--c-local-green)" marker-end="url(#arrow-green)" />
|
||||
|
||||
<!-- direct read Frameworks from toolsController (Bypass) -->
|
||||
<path d="M 560 495 L 560 652" stroke="var(--c-risk-amber)" stroke-dasharray="2 2" marker-end="url(#arrow-amber)" opacity="0.6"/>
|
||||
|
||||
</g>
|
||||
|
||||
<!--
|
||||
===========================================================================
|
||||
路径标签说明 (Path Labels)
|
||||
===========================================================================
|
||||
-->
|
||||
<g id="path-labels" class="font-sans label-text">
|
||||
<!-- Top Level -->
|
||||
<text x="445" y="95" text-anchor="middle">Query</text>
|
||||
<text x="445" y="160" text-anchor="middle">Report / Confirm</text>
|
||||
<text x="830" y="95" text-anchor="middle" fill="var(--c-cloud-blue)">Routing Intent</text>
|
||||
<text x="830" y="160" text-anchor="middle" fill="var(--c-cloud-blue)">LLM Result / Ask</text>
|
||||
|
||||
<!-- stdio Link -->
|
||||
<text x="585" y="210" text-anchor="end" fill="var(--c-local-green)">tools/call</text>
|
||||
<text x="655" y="210" text-anchor="start" fill="var(--c-local-green)">Result / Brake</text>
|
||||
<text x="700" y="415" text-anchor="start" fill="var(--c-neutral-gray)">resources/read</text>
|
||||
|
||||
<rect x="585" y="250" width="70" height="20" rx="4" fill="#FFFFFF" stroke="var(--c-local-green)" />
|
||||
<text x="620" y="264" text-anchor="middle" class="font-mono" font-size="11" fill="var(--c-local-green)">stdio</text>
|
||||
|
||||
<!-- Internal Labels -->
|
||||
<text x="420" y="515" text-anchor="start" fill="var(--c-local-green)" font-size="11">search_arxiv</text>
|
||||
<text x="650" y="510" text-anchor="start" fill="var(--c-local-green)" font-size="11">save_note (z.any容错)</text>
|
||||
<text x="570" y="590" text-anchor="start" fill="var(--c-risk-amber)" font-size="10">fetch_framework</text>
|
||||
</g>
|
||||
|
||||
<!--
|
||||
===========================================================================
|
||||
架构节点卡片 (Node Cards)
|
||||
===========================================================================
|
||||
-->
|
||||
<g id="nodes">
|
||||
<!-- 1. User Node -->
|
||||
<g transform="translate(280, 95)">
|
||||
<rect x="0" y="0" width="110" height="60" rx="8" fill="#F8FAFC" stroke="var(--c-neutral-gray)" stroke-width="2" />
|
||||
<circle cx="55" cy="22" r="10" fill="var(--c-neutral-gray)" />
|
||||
<path d="M 35 50 C 35 35, 75 35, 75 50" fill="none" stroke="var(--c-neutral-gray)" stroke-width="3" stroke-linecap="round"/>
|
||||
<text x="55" y="75" text-anchor="middle" class="font-sans label-text">User / 用户</text>
|
||||
</g>
|
||||
|
||||
<!-- 2. Cherry Studio -->
|
||||
<g transform="translate(490, 80)">
|
||||
<rect x="0" y="0" width="260" height="90" rx="8" fill="var(--c-gov-blue-light)" stroke="var(--c-gov-blue)" stroke-width="2" />
|
||||
<rect x="0" y="0" width="260" height="30" fill="var(--c-gov-blue)" rx="8" />
|
||||
<rect x="0" y="20" width="260" height="10" fill="var(--c-gov-blue)" />
|
||||
<text x="130" y="20" text-anchor="middle" class="font-sans card-title">Cherry Studio (MCP Client)</text>
|
||||
<text x="130" y="55" text-anchor="middle" class="font-sans card-text" font-weight="bold">大模型交互与意图传递中枢</text>
|
||||
<text x="130" y="75" text-anchor="middle" class="font-mono card-text" font-size="11">Initiates stdio subprocess</text>
|
||||
</g>
|
||||
|
||||
<!-- 3. Remote LLM Brain -->
|
||||
<g transform="translate(970, 120)">
|
||||
<polygon points="0,-45 40,-20 40,20 0,45 -40,20 -40,-20" fill="var(--c-cloud-blue-light)" stroke="var(--c-cloud-blue)" stroke-width="3" />
|
||||
<text x="0" y="-5" text-anchor="middle" class="font-mono card-text" fill="var(--c-cloud-blue)" font-size="14">LLM</text>
|
||||
<text x="0" y="15" text-anchor="middle" class="font-mono card-text" font-size="10" fill="var(--c-cloud-blue)">(Agent)</text>
|
||||
</g>
|
||||
|
||||
<!-- 4. app.ts -->
|
||||
<g transform="translate(520, 300)">
|
||||
<rect x="0" y="0" width="200" height="70" rx="6" fill="#FFFFFF" stroke="var(--c-local-green)" stroke-width="2" />
|
||||
<rect x="0" y="0" width="200" height="26" fill="var(--c-local-green)" rx="6" />
|
||||
<rect x="0" y="20" width="200" height="6" fill="var(--c-local-green)" />
|
||||
<text x="100" y="18" text-anchor="middle" class="font-mono card-title">app.ts</text>
|
||||
<text x="100" y="45" text-anchor="middle" class="font-sans text-code" font-size="12">McpServer 注册入口</text>
|
||||
<text x="100" y="62" text-anchor="middle" class="font-mono card-text" font-size="10">Tools & Resources (stdio)</text>
|
||||
</g>
|
||||
|
||||
<!-- 5. toolsController.ts (Expanded Central Router) -->
|
||||
<g transform="translate(320, 430)">
|
||||
<rect x="0" y="0" width="280" height="65" rx="6" fill="var(--c-risk-amber-light)" stroke="var(--c-risk-amber)" stroke-width="2.5" />
|
||||
<rect x="0" y="0" width="280" height="26" fill="var(--c-risk-amber)" rx="6" />
|
||||
<rect x="0" y="20" width="280" height="6" fill="var(--c-risk-amber)" />
|
||||
<text x="140" y="18" text-anchor="middle" class="font-mono card-title">toolsController</text>
|
||||
<text x="140" y="45" text-anchor="middle" class="font-sans card-text" font-weight="bold">统一调度与防呆:截获意图请求并分发</text>
|
||||
<text x="140" y="58" text-anchor="middle" class="font-sans alert-text" font-size="11">自动剥离框架 JSON 外壳,注入【🛑停止】指令</text>
|
||||
</g>
|
||||
|
||||
<!-- 6. resourcesController.ts -->
|
||||
<g transform="translate(630, 430)">
|
||||
<rect x="0" y="0" width="280" height="65" rx="6" fill="#FFFFFF" stroke="var(--c-neutral-gray-light)" stroke-width="2" />
|
||||
<rect x="0" y="0" width="280" height="26" fill="var(--c-neutral-gray-light)" rx="6" />
|
||||
<rect x="0" y="20" width="280" height="6" fill="var(--c-neutral-gray-light)" />
|
||||
<text x="140" y="18" text-anchor="middle" class="font-mono card-title">resourcesController</text>
|
||||
<text x="140" y="45" text-anchor="middle" class="font-sans card-text">解析 resource/read 请求</text>
|
||||
<text x="140" y="58" text-anchor="middle" class="font-sans card-text" font-size="11">扩展 literature:// 协议</text>
|
||||
</g>
|
||||
|
||||
<!-- 7. arxivService.ts -->
|
||||
<g transform="translate(320, 540)">
|
||||
<rect x="0" y="0" width="180" height="80" rx="6" fill="#FFFFFF" stroke="var(--c-local-green)" stroke-width="2" />
|
||||
<rect x="0" y="0" width="180" height="26" fill="var(--c-local-green)" rx="6" />
|
||||
<rect x="0" y="20" width="180" height="6" fill="var(--c-local-green)" />
|
||||
<text x="90" y="18" text-anchor="middle" class="font-mono card-title">arxivService.ts</text>
|
||||
<text x="90" y="45" text-anchor="middle" class="font-sans text-code" font-size="12">外部学术检索网关</text>
|
||||
<text x="90" y="60" text-anchor="middle" class="font-sans card-text" font-size="11">解析底层复杂 XML 结构</text>
|
||||
<text x="90" y="73" text-anchor="middle" class="font-sans card-text" font-size="11">转化为标准 Markdown 列表</text>
|
||||
</g>
|
||||
|
||||
<!-- 8. resourceService.ts -->
|
||||
<g transform="translate(665, 540)">
|
||||
<rect x="0" y="0" width="210" height="80" rx="6" fill="#FFFFFF" stroke="var(--c-local-green)" stroke-width="2" />
|
||||
<rect x="0" y="0" width="210" height="26" fill="var(--c-local-green)" rx="6" />
|
||||
<rect x="0" y="20" width="210" height="6" fill="var(--c-local-green)" />
|
||||
<text x="105" y="18" text-anchor="middle" class="font-mono card-title">resourceService.ts</text>
|
||||
<text x="105" y="45" text-anchor="middle" class="font-sans text-code" font-size="12">处理长文本落盘 (save_note)</text>
|
||||
<rect x="25" y="52" width="160" height="20" rx="3" fill="var(--c-risk-amber-light)" />
|
||||
<text x="105" y="66" text-anchor="middle" class="font-sans amber-text" font-size="11">拦截 z.any() 畸形入参并序列化</text>
|
||||
</g>
|
||||
|
||||
<!-- 9. External arXiv API -->
|
||||
<g transform="translate(0, 580)">
|
||||
<polygon points="30,-24 190,-24 220,0 190,24 30,24 0,0" fill="var(--c-cloud-blue-light)" stroke="var(--c-cloud-blue)" stroke-width="2" stroke-dasharray="4 2" />
|
||||
|
||||
<!-- arXiv 学术网站图标 (学士帽/文档云) -->
|
||||
<g transform="translate(35, -10)">
|
||||
<polygon points="12,0 2,5 12,10 22,5" fill="none" stroke="var(--c-cloud-blue)" stroke-width="1.5" stroke-linejoin="round"/>
|
||||
<path d="M 5 6.5 L 5 12 Q 12 16 19 12 L 19 6.5" fill="none" stroke="var(--c-cloud-blue)" stroke-width="1.5"/>
|
||||
<polyline points="12,5 22,5 22,11" fill="none" stroke="var(--c-cloud-blue)" stroke-width="1.5" stroke-linejoin="round"/>
|
||||
<circle cx="22" cy="12" r="1.5" fill="var(--c-cloud-blue)"/>
|
||||
</g>
|
||||
|
||||
<text x="130" y="4" text-anchor="middle" class="font-sans card-text" fill="var(--c-cloud-blue)" font-weight="bold">External arXiv API</text>
|
||||
</g>
|
||||
|
||||
<!-- 10. models/frameworks -->
|
||||
<g transform="translate(470, 660)">
|
||||
<rect x="0" y="0" width="180" height="60" rx="6" fill="#F8FAFC" stroke="var(--c-neutral-gray)" stroke-width="1.5" stroke-dasharray="4 2" />
|
||||
<path d="M 0 20 L 180 20" stroke="var(--c-neutral-gray)" stroke-width="1.5" stroke-dasharray="4 2" />
|
||||
<text x="90" y="14" text-anchor="middle" class="font-mono text-code" font-size="11">models/frameworks/</text>
|
||||
<text x="90" y="38" text-anchor="middle" class="font-mono card-text" font-size="11">静态分析框架模板</text>
|
||||
<text x="90" y="52" text-anchor="middle" class="font-mono card-text" font-size="11">(swot.json, scqa.json)</text>
|
||||
</g>
|
||||
|
||||
<!-- 11. Local Vault -->
|
||||
<g transform="translate(695, 680)">
|
||||
<path d="M 0 15 A 75 15 0 1 0 150 15 V 45 A 75 15 0 1 1 0 45 Z" fill="var(--c-local-green-light)" stroke="var(--c-neutral-gray)" stroke-width="2" />
|
||||
<ellipse cx="75" cy="15" rx="75" ry="15" fill="var(--c-local-green-light)" stroke="var(--c-neutral-gray)" stroke-width="2" />
|
||||
<text x="75" y="45" text-anchor="middle" class="font-sans card-text" fill="var(--c-neutral-gray)" font-size="12" font-weight="bold">Obsidian Vault</text>
|
||||
</g>
|
||||
|
||||
<!-- 12. schemas.ts (Zod Validation) -->
|
||||
<g transform="translate(695, 755)">
|
||||
<rect x="0" y="0" width="150" height="24" rx="4" fill="#F8FAFC" stroke="var(--c-neutral-gray)" stroke-width="1" />
|
||||
<text x="75" y="16" text-anchor="middle" class="font-mono text-code" font-size="11">schemas.ts: z.any()</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!--
|
||||
===========================================================================
|
||||
步骤侧边栏与流程图注 (Execution Steps) - 点落连线上
|
||||
===========================================================================
|
||||
-->
|
||||
<g id="execution-steps">
|
||||
<!-- 1. User input -->
|
||||
<g transform="translate(440, 110)">
|
||||
<circle cx="0" cy="0" r="10" fill="var(--c-step-red)" />
|
||||
<text x="0" y="4.5" text-anchor="middle" class="font-sans step-text">1</text>
|
||||
</g>
|
||||
<!-- 2. LLM routes search -->
|
||||
<g transform="translate(826, 110)">
|
||||
<circle cx="0" cy="0" r="10" fill="var(--c-step-red)" />
|
||||
<text x="0" y="4.5" text-anchor="middle" class="font-sans step-text">2</text>
|
||||
</g>
|
||||
<!-- 3. arXiv fetch (下移至跨界线上) -->
|
||||
<g transform="translate(270, 580)">
|
||||
<circle cx="0" cy="0" r="10" fill="var(--c-step-red)" />
|
||||
<text x="0" y="4.5" text-anchor="middle" class="font-sans step-text">3</text>
|
||||
</g>
|
||||
<!-- 4. LLM routes framework -->
|
||||
<g transform="translate(600, 210)">
|
||||
<circle cx="0" cy="0" r="10" fill="var(--c-step-red)" />
|
||||
<text x="0" y="4.5" text-anchor="middle" class="font-sans step-text">4</text>
|
||||
</g>
|
||||
<!-- 5. tools strips and brakes -->
|
||||
<g transform="translate(560, 580)">
|
||||
<circle cx="0" cy="0" r="10" fill="var(--c-step-red)" />
|
||||
<text x="0" y="4.5" text-anchor="middle" class="font-sans step-text">5</text>
|
||||
</g>
|
||||
<!-- 6. LLM stops tool, outputs & asks -->
|
||||
<g transform="translate(826, 140)">
|
||||
<circle cx="0" cy="0" r="10" fill="var(--c-step-red)" />
|
||||
<text x="0" y="4.5" text-anchor="middle" class="font-sans step-text">6</text>
|
||||
</g>
|
||||
<!-- 7. User confirms -->
|
||||
<g transform="translate(440, 140)">
|
||||
<circle cx="0" cy="0" r="10" fill="var(--c-step-red)" />
|
||||
<text x="0" y="4.5" text-anchor="middle" class="font-sans step-text">7</text>
|
||||
</g>
|
||||
<!-- 8. z.any fallback -->
|
||||
<g transform="translate(675, 602)">
|
||||
<circle cx="0" cy="0" r="10" fill="var(--c-step-red)" />
|
||||
<text x="0" y="4.5" text-anchor="middle" class="font-sans step-text">8</text>
|
||||
</g>
|
||||
<!-- 9. Write to vault -->
|
||||
<g transform="translate(770, 646)">
|
||||
<circle cx="0" cy="0" r="10" fill="var(--c-step-red)" />
|
||||
<text x="0" y="4.5" text-anchor="middle" class="font-sans step-text">9</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
</g>
|
||||
|
||||
<!--
|
||||
===========================================================================
|
||||
底部步骤流程说明面板 (Sprint 3 执行步骤流)
|
||||
===========================================================================
|
||||
-->
|
||||
<g transform="translate(290, 910)">
|
||||
<rect x="0" y="0" width="710" height="160" rx="8" fill="#FEF2F2" stroke="var(--c-step-red)" stroke-width="1.5" />
|
||||
<text x="355" y="25" text-anchor="middle" class="font-sans" font-size="14" font-weight="bold" fill="var(--c-step-red)">Sprint 3 执行步骤流</text>
|
||||
<line x1="15" y1="35" x2="695" y2="35" stroke="var(--c-step-red)" stroke-dasharray="2 2" />
|
||||
|
||||
<!-- Column 1 (Steps 1-5) -->
|
||||
<g transform="translate(30, 55)">
|
||||
<circle cx="6" cy="-4" r="7" fill="var(--c-step-red)" />
|
||||
<text x="6" y="0" text-anchor="middle" class="font-sans step-text" font-size="10">1</text>
|
||||
<text x="20" y="0" class="font-sans card-text" fill="#000000">用户输入包含检索与分析的复合查询</text>
|
||||
</g>
|
||||
<g transform="translate(30, 75)">
|
||||
<circle cx="6" cy="-4" r="7" fill="var(--c-step-red)" />
|
||||
<text x="6" y="0" text-anchor="middle" class="font-sans step-text" font-size="10">2</text>
|
||||
<text x="20" y="0" class="font-sans card-text" fill="#000000">大模型识别意图1: 请求 search_arxiv</text>
|
||||
</g>
|
||||
<g transform="translate(30, 95)">
|
||||
<circle cx="6" cy="-4" r="7" fill="var(--c-step-red)" />
|
||||
<text x="6" y="0" text-anchor="middle" class="font-sans step-text" font-size="10">3</text>
|
||||
<text x="20" y="0" class="font-sans card-text" fill="#000000">arxivService 请求外网并解析格式化</text>
|
||||
</g>
|
||||
<g transform="translate(30, 115)">
|
||||
<circle cx="6" cy="-4" r="7" fill="var(--c-step-red)" />
|
||||
<text x="6" y="0" text-anchor="middle" class="font-sans step-text" font-size="10">4</text>
|
||||
<text x="20" y="0" class="font-sans card-text" fill="#000000">大模型识别意图2: 请求分析框架模板</text>
|
||||
</g>
|
||||
<g transform="translate(30, 135)">
|
||||
<circle cx="6" cy="-4" r="7" fill="var(--c-step-red)" />
|
||||
<text x="6" y="0" text-anchor="middle" class="font-sans step-text" font-size="10">5</text>
|
||||
<text x="20" y="0" class="font-sans card-text" fill="#000000">Tools 剥离 JSON,注入🛑【刹车指令】</text>
|
||||
</g>
|
||||
<!-- Column 2 (Steps 6-9) -->
|
||||
<g transform="translate(380, 55)">
|
||||
<circle cx="6" cy="-4" r="7" fill="var(--c-step-red)" />
|
||||
<text x="6" y="0" text-anchor="middle" class="font-sans step-text" font-size="10">6</text>
|
||||
<text x="20" y="0" class="font-sans card-text" fill="#000000">模型工具被截断,输出报告并询问保存</text>
|
||||
</g>
|
||||
<g transform="translate(380, 75)">
|
||||
<circle cx="6" cy="-4" r="7" fill="var(--c-step-red)" />
|
||||
<text x="6" y="0" text-anchor="middle" class="font-sans step-text" font-size="10">7</text>
|
||||
<text x="20" y="0" class="font-sans card-text" fill="#000000">用户确认保存,触发意图3: save_note</text>
|
||||
</g>
|
||||
<g transform="translate(380, 95)">
|
||||
<circle cx="6" cy="-4" r="7" fill="var(--c-step-red)" />
|
||||
<text x="6" y="0" text-anchor="middle" class="font-sans step-text" font-size="10">8</text>
|
||||
<text x="20" y="0" class="font-sans card-text" fill="#000000">底层 z.any() 宽容拦截长文本并序列化</text>
|
||||
</g>
|
||||
<g transform="translate(380, 115)">
|
||||
<circle cx="6" cy="-4" r="7" fill="var(--c-step-red)" />
|
||||
<text x="6" y="0" text-anchor="middle" class="font-sans step-text" font-size="10">9</text>
|
||||
<text x="20" y="0" class="font-sans card-text" fill="#000000">resourceService 安全写入知识库落盘</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!--
|
||||
===========================================================================
|
||||
底部图例与注释块 (Legend & Key Block)
|
||||
===========================================================================
|
||||
-->
|
||||
<g id="legend" transform="translate(640, 1110)">
|
||||
<!-- 统一的半透明背景容器 -->
|
||||
<rect x="-440" y="-16" width="880" height="40" rx="4" fill="rgba(255, 255, 255, 0.9)" stroke="var(--c-neutral-gray)" stroke-width="0.5"/>
|
||||
|
||||
<g transform="translate(-420, 0)">
|
||||
<!-- 图例引导文本 -->
|
||||
<text x="0" y="8" class="font-sans" font-size="13" font-weight="bold" fill="var(--c-neutral-gray)">组件与语义对照:</text>
|
||||
|
||||
<!-- 图例项 1 -->
|
||||
<g transform="translate(130, 0)">
|
||||
<rect x="-6" y="-4" width="12" height="12" rx="2" fill="var(--c-gov-blue)" />
|
||||
<text x="12" y="8" class="font-sans" font-size="12" fill="var(--c-neutral-gray)">宿主客户端</text>
|
||||
</g>
|
||||
|
||||
<!-- 图例项 2 -->
|
||||
<g transform="translate(240, 0)">
|
||||
<rect x="-6" y="-4" width="12" height="12" rx="2" fill="var(--c-risk-amber)" />
|
||||
<text x="12" y="8" class="font-sans" font-size="12" fill="var(--c-neutral-gray)">防火墙路由 (防死循环)</text>
|
||||
</g>
|
||||
|
||||
<!-- 图例项 3 -->
|
||||
<g transform="translate(430, 0)">
|
||||
<rect x="-6" y="-4" width="12" height="12" rx="2" fill="var(--c-local-green)" />
|
||||
<text x="12" y="8" class="font-sans" font-size="12" fill="var(--c-neutral-gray)">底层服务引擎与资源落盘</text>
|
||||
</g>
|
||||
|
||||
<!-- 图例项 4 -->
|
||||
<g transform="translate(640, 0)">
|
||||
<rect x="-6" y="-4" width="12" height="12" rx="2" fill="var(--c-cloud-blue)" />
|
||||
<text x="12" y="8" class="font-sans" font-size="12" fill="var(--c-neutral-gray)">外部云端/网络依赖</text>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!--
|
||||
===========================================================================
|
||||
版权许可声明 (License)
|
||||
===========================================================================
|
||||
-->
|
||||
<text x="640" y="1170" text-anchor="middle" class="font-sans" font-size="11" fill="var(--c-neutral-gray)">
|
||||
本作品采用 CC-BY-SA 4.0 进行许可,© 2025-2026 Gitconomy Research社区
|
||||
</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 25 KiB |
|
|
@ -1,15 +1,17 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 850" width="100%" height="100%">
|
||||
<!--
|
||||
================================================================================
|
||||
图表名称:基于 MCP 的学术研究系统拓扑图 (Research MCP Server Topology)
|
||||
图表名称:基于 MCP 的学术研究系统拓扑图 (Research MCP System Topology)
|
||||
文件命名:figure01-mcp-system-topology.svg
|
||||
用途:展示研报智能体 MCP 系统的核心组件交互拓扑,包括用户层客户端、MCP 传输协议层、MCP Server 集群逻辑分工,以及与外部学术基础设施的数据流向。
|
||||
版本:v1.0.0
|
||||
用途:展示研报智能体 MCP 系统的核心组件交互拓扑,包括用户层客户端、MCP 传输协议层、单体 MCP Server 的 3 大原语分工,以及与外部学术基础设施的数据流向。
|
||||
版本:v2.0.0
|
||||
作者:Gitconomy Research-郭晧
|
||||
SPDX-License-Identifier: MIT & CC-BY-SA-4.0
|
||||
创建日期:2026-02-28
|
||||
创建日期:2026-03-01
|
||||
更新日期:2026-03-08
|
||||
更新说明:按照MCP的三大原语组件更新 Project Caffeine的系统架构图设计。
|
||||
================================================================================
|
||||
-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 850" width="100%" height="100%">
|
||||
<!-- Background: Solid White (强制兼容深色模式的白底) -->
|
||||
<rect width="100%" height="100%" fill="#FFFFFF" />
|
||||
|
||||
|
|
@ -73,11 +75,11 @@ SPDX-License-Identifier: MIT & CC-BY-SA-4.0
|
|||
<!-- ========================================================================= -->
|
||||
<g id="title-block" transform="translate(640, 50)">
|
||||
<!-- 图表编号 -->
|
||||
<text y="-30" text-anchor="middle" class="text-mono" font-size="12" fill="var(--c-neutral-gray)" letter-spacing="1">FIG-MCP-01</text>
|
||||
<text y="-30" text-anchor="middle" class="text-mono" font-size="12" fill="var(--c-neutral-gray)" letter-spacing="1">FIG-01</text>
|
||||
<!-- 主标题 -->
|
||||
<text y="0" text-anchor="middle" class="text-title" font-size="24" fill="#000000">Project Caffeine 研报智能体 MCP 系统拓扑图</text>
|
||||
<!-- 面包屑导航 -->
|
||||
<text y="25" text-anchor="middle" class="text-mono" font-size="14" fill="var(--c-neutral-gray)">架构图 > 智能体 MCP > 系统拓扑</text>
|
||||
<text y="25" text-anchor="middle" class="text-mono" font-size="14" fill="var(--c-neutral-gray)">架构图 > 智能体 MCP > 核心原语拓扑</text>
|
||||
<!-- 上下文指示线 (使用主色调代表架构主体) -->
|
||||
<rect x="-30" y="40" width="60" height="3" fill="var(--c-local-green)" />
|
||||
</g>
|
||||
|
|
@ -92,15 +94,15 @@ SPDX-License-Identifier: MIT & CC-BY-SA-4.0
|
|||
|
||||
<!-- MCP Server 集群区域 (Center: Local/Container) -->
|
||||
<rect x="420" y="0" width="340" height="520" rx="8" class="box-local" />
|
||||
<text x="435" y="25" class="text-title" font-size="14" fill="var(--c-local-green)">MCP 服务端集群 (核心执行区)</text>
|
||||
<text x="435" y="25" class="text-title" font-size="14" fill="var(--c-local-green)">单体 MCP Server (三大核心原语)</text>
|
||||
|
||||
<!-- 外部基础设施区域 (Right: Cloud) -->
|
||||
<rect x="850" y="0" width="400" height="330" rx="8" class="box-cloud" />
|
||||
<rect x="850" y="0" width="400" height="200" rx="8" class="box-cloud" />
|
||||
<text x="865" y="25" class="text-title" font-size="14" fill="var(--c-cloud-blue)">外部学术基础设施 (公有云)</text>
|
||||
|
||||
<!-- 本地知识库区域 (Right Bottom: Local Storage) -->
|
||||
<rect x="850" y="360" width="400" height="160" rx="8" class="box-local" />
|
||||
<text x="865" y="385" class="text-title" font-size="14" fill="var(--c-local-green)">本地知识库</text>
|
||||
<rect x="850" y="240" width="400" height="280" rx="8" class="box-local" />
|
||||
<text x="865" y="265" class="text-title" font-size="14" fill="var(--c-local-green)">本地持久化存储</text>
|
||||
</g>
|
||||
|
||||
<!-- ========================================================================= -->
|
||||
|
|
@ -121,7 +123,7 @@ SPDX-License-Identifier: MIT & CC-BY-SA-4.0
|
|||
<text x="365" y="325" text-anchor="middle" class="text-mono" font-size="10" fill="var(--c-neutral-gray)">JSON-RPC 2.0</text>
|
||||
|
||||
<!-- 协议层标签 -->
|
||||
<text x="365" y="380" text-anchor="middle" class="text-title" font-size="12" fill="var(--c-neutral-gray)">MCP 标准传输协议层</text>
|
||||
<text x="365" y="380" text-anchor="middle" class="text-title" font-size="12" fill="var(--c-neutral-gray)">MCP 传输协议层</text>
|
||||
</g>
|
||||
|
||||
<!-- ========================================================================= -->
|
||||
|
|
@ -145,7 +147,7 @@ SPDX-License-Identifier: MIT & CC-BY-SA-4.0
|
|||
<text x="110" y="30" text-anchor="middle" class="text-title" font-size="16" fill="var(--c-local-green)">MCP 客户端</text>
|
||||
<text x="110" y="50" text-anchor="middle" class="text-mono" font-size="12" fill="#000000">Claude Desktop / Cursor</text>
|
||||
<rect x="20" y="70" width="180" height="30" rx="4" fill="#FFFFFF" stroke="var(--c-local-green)" stroke-width="1" />
|
||||
<text x="110" y="89" text-anchor="middle" class="text-sans" font-size="12" fill="#000000">大模型上下文与工具调度</text>
|
||||
<text x="110" y="89" text-anchor="middle" class="text-sans" font-size="12" fill="#000000">大模型上下文与协议调度</text>
|
||||
</g>
|
||||
|
||||
<!-- 远程 LLM (Remote Brain) - 从 Client 发起调用 -->
|
||||
|
|
@ -161,33 +163,33 @@ SPDX-License-Identifier: MIT & CC-BY-SA-4.0
|
|||
<text x="180" y="380" class="text-sans" font-size="10" fill="var(--c-risk-amber)">提示词请求 / 结果流式响应</text>
|
||||
|
||||
|
||||
<!-- [4.2] MCP Server 集群 -->
|
||||
<!-- [4.2] 单体 MCP Server 的 3 大核心原语 -->
|
||||
|
||||
<!-- MCP Server 1: 文献查询 -->
|
||||
<!-- MCP 原语 1: Tools -->
|
||||
<g transform="translate(450, 70)">
|
||||
<rect width="280" height="100" rx="6" class="node-local" />
|
||||
<rect x="0" y="0" width="280" height="35" rx="6" fill="var(--c-local-green)" />
|
||||
<text x="140" y="22" text-anchor="middle" class="text-title" font-size="14" fill="#FFFFFF">文献查询 MCP Server</text>
|
||||
<text x="140" y="55" text-anchor="middle" class="text-mono" font-size="11" fill="#000000">工具: search_papers(), fetch_pdf()</text>
|
||||
<text x="140" y="75" text-anchor="middle" class="text-sans" font-size="11" fill="var(--c-neutral-gray)">负责多源学术数据库 API 聚合与清洗</text>
|
||||
<text x="140" y="22" text-anchor="middle" class="text-title" font-size="14" fill="#FFFFFF">Tools 原语 (动作执行)</text>
|
||||
<text x="140" y="55" text-anchor="middle" class="text-mono" font-size="11" fill="#000000">search_academic_literature(), save_to_vault()</text>
|
||||
<text x="140" y="75" text-anchor="middle" class="text-sans" font-size="11" fill="var(--c-neutral-gray)">调用外部API检索文献并执行标准化数据双轨落盘</text>
|
||||
</g>
|
||||
|
||||
<!-- MCP Server 2: 提示词策略 -->
|
||||
<!-- MCP 原语 2: Prompts -->
|
||||
<g transform="translate(450, 210)">
|
||||
<rect width="280" height="100" rx="6" class="node-local" />
|
||||
<rect x="0" y="0" width="280" height="35" rx="6" fill="var(--c-local-green)" />
|
||||
<text x="140" y="22" text-anchor="middle" class="text-title" font-size="14" fill="#FFFFFF">提示词策略 MCP Server</text>
|
||||
<text x="140" y="55" text-anchor="middle" class="text-mono" font-size="11" fill="#000000">提示词: synthesis_template</text>
|
||||
<text x="140" y="75" text-anchor="middle" class="text-sans" font-size="11" fill="var(--c-neutral-gray)">负责基于主题动态组装超级提示词</text>
|
||||
<text x="140" y="22" text-anchor="middle" class="text-title" font-size="14" fill="#FFFFFF">Prompts 原语 (上下文策略)</text>
|
||||
<text x="140" y="55" text-anchor="middle" class="text-mono" font-size="11" fill="#000000">prompts/get: 5w3h, scqa, pestle 等</text>
|
||||
<text x="140" y="75" text-anchor="middle" class="text-sans" font-size="11" fill="var(--c-neutral-gray)">提供多维思维框架,动态指导大模型分析与拆解</text>
|
||||
</g>
|
||||
|
||||
<!-- MCP Server 3: CoT 推理 -->
|
||||
<!-- MCP 原语 3: Resources -->
|
||||
<g transform="translate(450, 350)">
|
||||
<rect width="280" height="100" rx="6" class="node-amber" />
|
||||
<rect x="0" y="0" width="280" height="35" rx="6" fill="var(--c-risk-amber)" />
|
||||
<text x="140" y="22" text-anchor="middle" class="text-title" font-size="14" fill="#FFFFFF">CoT 推理 MCP Server</text>
|
||||
<text x="140" y="55" text-anchor="middle" class="text-mono" font-size="11" fill="#000000">工具: run_cot_chain(), verify_logic()</text>
|
||||
<text x="140" y="75" text-anchor="middle" class="text-sans" font-size="11" fill="var(--c-neutral-gray)">协调多步思考推理,抽取实体与洞察生成</text>
|
||||
<rect width="280" height="100" rx="6" class="node-local" />
|
||||
<rect x="0" y="0" width="280" height="35" rx="6" fill="var(--c-local-green)" />
|
||||
<text x="140" y="22" text-anchor="middle" class="text-title" font-size="14" fill="#FFFFFF">Resources 原语 (数据暴露)</text>
|
||||
<text x="140" y="55" text-anchor="middle" class="text-mono" font-size="11" fill="#000000">literature://local/, note://local/</text>
|
||||
<text x="140" y="75" text-anchor="middle" class="text-sans" font-size="11" fill="var(--c-neutral-gray)">暴露本地知识库的文献卡片与笔记供大模型访问</text>
|
||||
</g>
|
||||
|
||||
|
||||
|
|
@ -198,49 +200,39 @@ SPDX-License-Identifier: MIT & CC-BY-SA-4.0
|
|||
<!-- Cylinder -->
|
||||
<path d="M 0,20 A 40,15 0 0,0 80,20 A 40,15 0 0,0 0,20 L 0,80 A 40,15 0 0,0 80,80 L 80,20" class="node-cloud" />
|
||||
<text x="40" y="50" text-anchor="middle" class="text-title" font-size="12" fill="#000000">学术数据库</text>
|
||||
<text x="40" y="68" text-anchor="middle" class="text-mono" font-size="10" fill="var(--c-cloud-blue)">PubMed, arXiv</text>
|
||||
<text x="40" y="82" text-anchor="middle" class="text-mono" font-size="10" fill="var(--c-cloud-blue)">IEEE, WOS</text>
|
||||
<text x="40" y="68" text-anchor="middle" class="text-mono" font-size="10" fill="var(--c-cloud-blue)">arXiv API</text>
|
||||
<text x="40" y="82" text-anchor="middle" class="text-mono" font-size="10" fill="var(--c-cloud-blue)">Semantic Scholar</text>
|
||||
</g>
|
||||
|
||||
<!-- 互联网资源 -->
|
||||
<g transform="translate(1080, 200)">
|
||||
<!-- Hexagon-like Cloud Service -->
|
||||
<polygon points="20,0 80,0 100,40 80,80 20,80 0,40" class="node-cloud" />
|
||||
<text x="50" y="35" text-anchor="middle" class="text-title" font-size="12" fill="#000000">互联网资源</text>
|
||||
<text x="50" y="55" text-anchor="middle" class="text-mono" font-size="10" fill="var(--c-cloud-blue)">PDF / HTML</text>
|
||||
</g>
|
||||
|
||||
|
||||
<!-- [4.4] 本地知识库 (Local Knowledge Base) -->
|
||||
|
||||
<g transform="translate(980, 410)">
|
||||
<g transform="translate(930, 360)">
|
||||
<!-- Cylinder -->
|
||||
<path d="M 0,20 A 50,15 0 0,0 100,20 A 50,15 0 0,0 0,20 L 0,80 A 50,15 0 0,0 100,80 L 100,20" class="node-local" />
|
||||
<text x="50" y="50" text-anchor="middle" class="text-title" font-size="12" fill="#000000">本地知识库</text>
|
||||
<text x="50" y="70" text-anchor="middle" class="text-mono" font-size="11" fill="var(--c-local-green)">Obsidian / Logseq</text>
|
||||
<text x="50" y="70" text-anchor="middle" class="text-mono" font-size="11" fill="var(--c-local-green)">Obsidian / PKM</text>
|
||||
<text x="50" y="85" text-anchor="middle" class="text-mono" font-size="9" fill="var(--c-neutral-gray)">(YAML / Markdown)</text>
|
||||
</g>
|
||||
|
||||
<!-- [4.5] 服务器到外部/本地基础设施的连线 -->
|
||||
|
||||
<!-- 文献查询 MCP -> 学术数据库 (Async) -->
|
||||
<!-- Tools 原语 -> 学术数据库 (Async) -->
|
||||
<path d="M 730,100 L 930,100" class="line-async" marker-end="url(#arrow-async)" />
|
||||
<text x="830" y="90" text-anchor="middle" class="text-sans" font-size="10" fill="var(--c-risk-amber)">API 请求调用 (REST/GraphQL)</text>
|
||||
<text x="810" y="90" text-anchor="middle" class="text-sans" font-size="10" fill="var(--c-risk-amber)">API 请求调用 (REST)</text>
|
||||
|
||||
<!-- CoT 推理 MCP -> 互联网资源 (Async) -->
|
||||
<path d="M 730,400 L 1130,400 L 1130,285" class="line-async" marker-end="url(#arrow-async)" />
|
||||
<text x="1000" y="390" text-anchor="middle" class="text-sans" font-size="10" fill="var(--c-risk-amber)">拉取文献全文 / 网页爬取</text>
|
||||
<!-- Tools 原语 -> Local KB (Sync) 写入落盘 -->
|
||||
<!-- 采用肘型连接线绕行 -->
|
||||
<path d="M 730,140 L 880,140 L 880,380 L 930,380" class="line-sync" marker-end="url(#arrow-sync)" />
|
||||
<text x="800" y="160" text-anchor="middle" class="text-sans" font-size="10" fill="var(--c-local-green)">JSON 转 Markdown+YAML</text>
|
||||
|
||||
<!-- 提示词策略 MCP -> Local KB (Sync) (如果是读取本地上下文) -->
|
||||
<!-- 文献查询/CoT推理 MCP -> Local KB (Sync) (双轨制写入落盘) -->
|
||||
<!-- 采用肘型连接线 -->
|
||||
<path d="M 730,420 L 790,420 L 790,460 L 980,460" class="line-sync" marker-end="url(#arrow-sync)" />
|
||||
<text x="885" y="452" text-anchor="middle" class="text-sans" font-size="10" fill="var(--c-local-green)">本地读写 Markdown / YAML</text>
|
||||
<!-- Resources 原语 -> Local KB (Sync) 读取数据 -->
|
||||
<path d="M 730,400 L 930,400" class="line-sync" marker-end="url(#arrow-sync)" />
|
||||
<text x="830" y="390" text-anchor="middle" class="text-sans" font-size="10" fill="var(--c-local-green)">读取文献卡片与笔记</text>
|
||||
|
||||
<!-- MCP 内部的协同工作流虚线表示 (可选的逻辑联系) -->
|
||||
<path d="M 590,170 L 590,210" class="line-neutral" marker-end="url(#arrow-async)" />
|
||||
<path d="M 590,310 L 590,350" class="line-neutral" marker-end="url(#arrow-async)" />
|
||||
<text x="600" y="195" class="text-sans" font-size="9" fill="var(--c-neutral-gray)">本地上下文共享</text>
|
||||
<path d="M 590,170 L 590,210" class="line-neutral" />
|
||||
<path d="M 590,310 L 590,350" class="line-neutral" />
|
||||
<text x="600" y="195" class="text-sans" font-size="9" fill="var(--c-neutral-gray)">核心引擎共享调度</text>
|
||||
|
||||
</g>
|
||||
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 16 KiB |
|
|
@ -1,7 +1,7 @@
|
|||
<!--
|
||||
================================================================================
|
||||
图表名称:MCP 系统逻辑架构与核心流转图 (MCP Logic Architecture & Core Flow)
|
||||
文件命名:figure03-mcp-logic-architecture.svg
|
||||
文件命名:figure02-mcp-logic-architecture.svg
|
||||
用途:展示 Model Context Protocol (MCP) 在大语言模型宿主与外部工具系统之间的标准化交互机制与核心逻辑流。
|
||||
版本:v1.0.0
|
||||
作者:Gitconomy Research-郭晧
|
||||
|
|
@ -61,7 +61,7 @@ SPDX-License-Identifier: MIT & CC-BY-SA-4.0
|
|||
<!-- 1. 标题块 (Title Block) -->
|
||||
<!-- =========================================================================== -->
|
||||
<g id="title-block" transform="translate(600, 40)">
|
||||
<text y="-10" text-anchor="middle" class="font-mono title-id">FIG-MCP-03</text>
|
||||
<text y="-10" text-anchor="middle" class="font-mono title-id">FIG-02</text>
|
||||
<text y="20" text-anchor="middle" class="font-sans title-main">MCP 系统工作流逻辑示意图</text>
|
||||
<text y="45" text-anchor="middle" class="font-mono title-sub">架构图 > 智能体 MCP > 工作流</text>
|
||||
<line x1="-30" y1="60" x2="30" y2="60" stroke="var(--c-cloud-blue)" stroke-width="3" />
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
|
@ -1,320 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 1200" width="100%" height="100%">
|
||||
<!--
|
||||
================================================================================
|
||||
图表名称:个人级研究助手智能体 - 开发框架与技术栈架构图
|
||||
文件命名:project-caffeine-tech-stack-framework.svg
|
||||
用途:展示从宿主端到三个核心 MCP Server 的底层技术栈、核心算法、Monorepo工程化及安全交互框架。
|
||||
版本:v1.0.0 (Added Legend)
|
||||
作者:Gitconomy Research-郭晧
|
||||
SPDX-License-Identifier: MIT & CC-BY-SA-4.0
|
||||
创建日期:2026-02-27
|
||||
================================================================================
|
||||
-->
|
||||
|
||||
<!-- Background: Solid White for Git compatibility -->
|
||||
<rect width="100%" height="100%" fill="#FFFFFF" />
|
||||
|
||||
<defs>
|
||||
<style>
|
||||
:root {
|
||||
/* 语义色定义 */
|
||||
--c-cloud-blue: #0099FF;
|
||||
--c-cloud-blue-light: rgba(0, 153, 255, 0.08);
|
||||
--c-local-green: #009900;
|
||||
--c-local-green-light: rgba(0, 153, 0, 0.08);
|
||||
--c-risk-amber: #FF991F;
|
||||
--c-risk-amber-light: rgba(255, 153, 31, 0.08);
|
||||
--c-gov-blue: #0052CC;
|
||||
--c-gov-blue-light: rgba(0, 82, 204, 0.04);
|
||||
--c-neutral-gray: #475569;
|
||||
--c-neutral-gray-light: #E2E8F0;
|
||||
}
|
||||
|
||||
/* 字体降级机制 */
|
||||
.font-sans { font-family: 'Noto Sans', 'Helvetica Neue', Arial, sans-serif; }
|
||||
.font-mono { font-family: 'JetBrains Mono', Consolas, 'Courier New', monospace; }
|
||||
|
||||
/* 文本层级 */
|
||||
.title-main { font-size: 16px; font-weight: bold; fill: var(--c-neutral-gray); }
|
||||
.title-sub { font-size: 14px; font-weight: bold; fill: #0F172A; }
|
||||
.tech-badge { font-size: 12px; font-weight: bold; }
|
||||
.desc-text { font-size: 12px; fill: #64748B; }
|
||||
</style>
|
||||
|
||||
<!-- 箭头标记 -->
|
||||
<marker id="arrow-green" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto">
|
||||
<path d="M 0 0 L 8 4 L 0 8 Z" fill="var(--c-local-green)" />
|
||||
</marker>
|
||||
<marker id="arrow-blue" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto">
|
||||
<path d="M 0 0 L 8 4 L 0 8 Z" fill="var(--c-cloud-blue)" />
|
||||
</marker>
|
||||
<marker id="arrow-amber" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto">
|
||||
<path d="M 0 0 L 8 4 L 0 8 Z" fill="var(--c-risk-amber)" />
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<!--
|
||||
===========================================================================
|
||||
顶层标题块 (Title Block)
|
||||
===========================================================================
|
||||
-->
|
||||
<g id="title-block" transform="translate(600, 40)">
|
||||
<text y="0" text-anchor="middle" class="font-mono" font-size="12" fill="var(--c-neutral-gray)" letter-spacing="1">FIG-02</text>
|
||||
<text y="30" text-anchor="middle" class="font-sans" font-weight="bold" font-size="24" fill="#000000">Project Caffeine开发框架与技术栈架构图</text>
|
||||
<text y="55" text-anchor="middle" class="font-mono" font-size="14" fill="var(--c-neutral-gray)">架构图 > 智能体 MCP > 开发框架和技术堆栈</text>
|
||||
<rect x="-30" y="70" width="60" height="3" fill="var(--c-gov-blue)" />
|
||||
</g>
|
||||
|
||||
<!--
|
||||
===========================================================================
|
||||
Layer 1: 宿主环境与模型层 (Host & LLM Layer)
|
||||
===========================================================================
|
||||
-->
|
||||
<g id="layer-host" transform="translate(100, 140)">
|
||||
<text x="0" y="0" class="font-sans title-main">Layer 1: 宿主应用与大脑层 (Host & Brain)</text>
|
||||
<rect x="0" y="15" width="1000" height="90" rx="8" fill="var(--c-neutral-gray-light)" opacity="0.2" stroke="var(--c-neutral-gray)" stroke-dasharray="4 4" stroke-width="1.5" />
|
||||
|
||||
<!-- 客户端 -->
|
||||
<rect x="30" y="30" width="220" height="60" rx="6" fill="#FFFFFF" stroke="var(--c-local-green)" stroke-width="2" />
|
||||
<text x="140" y="55" text-anchor="middle" class="font-sans title-sub">MCP Client 宿主联调环境</text>
|
||||
<text x="140" y="75" text-anchor="middle" class="font-mono desc-text">VS Code (MCP 插件) / Claude</text>
|
||||
|
||||
<!-- 双向箭头 -->
|
||||
<line x1="260" y1="60" x2="330" y2="60" stroke="var(--c-neutral-gray)" stroke-width="2" stroke-dasharray="3 3"/>
|
||||
|
||||
<!-- 远程模型 (Brain) -->
|
||||
<g transform="translate(400, 60)">
|
||||
<!-- 六边形 Token -->
|
||||
<polygon points="0,-30 26,-15 26,15 0,30 -26,15 -26,-15" fill="var(--c-cloud-blue-light)" stroke="var(--c-cloud-blue)" stroke-width="2" />
|
||||
<text x="40" y="-5" class="font-sans title-sub" fill="var(--c-cloud-blue)">Remote LLM Brain</text>
|
||||
<text x="40" y="15" class="font-mono desc-text">Claude 3.5 / DeepSeek (Context Engine)</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!--
|
||||
===========================================================================
|
||||
Layer 2: 协议总线 (Protocol Bus)
|
||||
===========================================================================
|
||||
-->
|
||||
<g id="layer-protocol" transform="translate(100, 270)">
|
||||
<text x="0" y="0" class="font-sans title-main">Layer 2: 通信协议层 (JSON-RPC 2.0 Bus)</text>
|
||||
<rect x="0" y="15" width="1000" height="40" rx="20" fill="var(--c-gov-blue-light)" stroke="var(--c-gov-blue)" stroke-width="2" />
|
||||
<text x="500" y="40" text-anchor="middle" class="font-mono" font-size="14" font-weight="bold" fill="var(--c-gov-blue)">mcp-protocol: STDIO 协议 (MVP阶段:本地 stdin/stdout 零网络开销)</text>
|
||||
|
||||
<!-- 连接上下层的管线 -->
|
||||
<line x1="210" y1="-35" x2="210" y2="15" stroke="var(--c-local-green)" stroke-width="3" marker-end="url(#arrow-green)" />
|
||||
<line x1="210" y1="55" x2="210" y2="90" stroke="var(--c-local-green)" stroke-width="3" marker-end="url(#arrow-green)" />
|
||||
|
||||
<line x1="500" y1="55" x2="500" y2="90" stroke="var(--c-local-green)" stroke-width="3" marker-end="url(#arrow-green)" />
|
||||
|
||||
<line x1="850" y1="55" x2="850" y2="90" stroke="var(--c-local-green)" stroke-width="3" marker-end="url(#arrow-green)" />
|
||||
</g>
|
||||
|
||||
<!--
|
||||
===========================================================================
|
||||
Layer 3: 核心 MCP Server 集群框架 (Microservices Stack)
|
||||
===========================================================================
|
||||
-->
|
||||
<g id="layer-servers" transform="translate(100, 370)">
|
||||
<text x="0" y="0" class="font-sans title-main">Layer 3: 核心智能体引擎 / MCP Server 集群开发框架</text>
|
||||
|
||||
<!-- 工程化管控边界 (Monorepo) -->
|
||||
<rect x="0" y="15" width="1000" height="360" rx="8" fill="transparent" stroke="var(--c-neutral-gray)" stroke-dasharray="4 4" stroke-width="2" />
|
||||
<rect x="0" y="15" width="1000" height="30" fill="var(--c-neutral-gray-light)" opacity="0.4" rx="8"/>
|
||||
<text x="20" y="35" class="font-mono desc-text" font-weight="bold" fill="var(--c-neutral-gray)">📦 npm Workspaces (Monorepo) - 统一包管理 / 依赖隔离 / 共享 JSON-RPC Schema</text>
|
||||
|
||||
<!-- ================= S1: 文献查询 Server ================= -->
|
||||
<g transform="translate(20, 45)">
|
||||
<rect x="0" y="0" width="300" height="320" rx="8" fill="var(--c-local-green-light)" stroke="var(--c-local-green)" stroke-width="2" />
|
||||
<rect x="0" y="0" width="300" height="40" fill="var(--c-local-green)" rx="8" />
|
||||
<rect x="0" y="20" width="300" height="20" fill="var(--c-local-green)" />
|
||||
<text x="150" y="25" text-anchor="middle" class="font-sans title-sub" fill="#FFFFFF">S1: 文献查询 Server (执行者)</text>
|
||||
|
||||
<!-- Tech Stack Badges (Unified TS/Node) -->
|
||||
<text x="20" y="65" class="font-sans desc-text" font-weight="bold">开发环境与框架</text>
|
||||
<rect x="20" y="75" width="115" height="24" rx="12" fill="#FFFFFF" stroke="var(--c-local-green)" />
|
||||
<text x="77.5" y="91" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-local-green)">Node.js (v20+)</text>
|
||||
<rect x="145" y="75" width="95" height="24" rx="12" fill="#FFFFFF" stroke="var(--c-local-green)" />
|
||||
<text x="192.5" y="91" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-local-green)">@mcp/sdk-ts</text>
|
||||
|
||||
<!-- Core Algorithms -->
|
||||
<text x="20" y="130" class="font-sans desc-text" font-weight="bold">核心组件与算法集成</text>
|
||||
<rect x="20" y="140" width="260" height="40" rx="4" fill="#FFFFFF" stroke="var(--c-risk-amber)" stroke-dasharray="2 2" />
|
||||
<text x="150" y="165" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-risk-amber)">Semantic Chunking 文本切块算法</text>
|
||||
|
||||
<rect x="20" y="190" width="260" height="40" rx="4" fill="#FFFFFF" stroke="var(--c-risk-amber)" stroke-dasharray="2 2" />
|
||||
<text x="150" y="215" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-risk-amber)">Playwright / Firecrawl 网页深抓取</text>
|
||||
|
||||
<!-- APIs -->
|
||||
<text x="20" y="260" class="font-sans desc-text" font-weight="bold">集成外部接口</text>
|
||||
<rect x="20" y="270" width="125" height="24" rx="4" fill="var(--c-cloud-blue-light)" stroke="var(--c-cloud-blue)" />
|
||||
<text x="82.5" y="286" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-cloud-blue)">arXiv API</text>
|
||||
<rect x="155" y="270" width="125" height="24" rx="4" fill="var(--c-cloud-blue-light)" stroke="var(--c-cloud-blue)" />
|
||||
<text x="217.5" y="286" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-cloud-blue)">S.Scholar API</text>
|
||||
</g>
|
||||
|
||||
<!-- ================= S2: 提示词策略 Server ================= -->
|
||||
<g transform="translate(350, 45)">
|
||||
<rect x="0" y="0" width="300" height="320" rx="8" fill="var(--c-local-green-light)" stroke="var(--c-local-green)" stroke-width="2" />
|
||||
<rect x="0" y="0" width="300" height="40" fill="var(--c-local-green)" rx="8" />
|
||||
<rect x="0" y="20" width="300" height="20" fill="var(--c-local-green)" />
|
||||
<text x="150" y="25" text-anchor="middle" class="font-sans title-sub" fill="#FFFFFF">S2: 提示词策略 Server (军师)</text>
|
||||
|
||||
<!-- Tech Stack Badges (Unified TS/Node) -->
|
||||
<text x="20" y="65" class="font-sans desc-text" font-weight="bold">开发环境与框架</text>
|
||||
<rect x="20" y="75" width="115" height="24" rx="12" fill="#FFFFFF" stroke="var(--c-local-green)" />
|
||||
<text x="77.5" y="91" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-local-green)">Node.js (v20+)</text>
|
||||
<rect x="145" y="75" width="95" height="24" rx="12" fill="#FFFFFF" stroke="var(--c-local-green)" />
|
||||
<text x="192.5" y="91" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-local-green)">@mcp/sdk-ts</text>
|
||||
|
||||
<!-- Core Algorithms -->
|
||||
<text x="20" y="130" class="font-sans desc-text" font-weight="bold">核心智能体架构与引擎</text>
|
||||
<rect x="20" y="140" width="260" height="40" rx="4" fill="#FFFFFF" stroke="var(--c-gov-blue)" />
|
||||
<text x="150" y="165" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-gov-blue)">Persona Matrix 角色化矩阵</text>
|
||||
|
||||
<rect x="20" y="190" width="260" height="40" rx="4" fill="#FFFFFF" stroke="var(--c-gov-blue)" />
|
||||
<text x="150" y="215" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-gov-blue)">Exploration State (防死循环账本)</text>
|
||||
|
||||
<!-- Prompts -->
|
||||
<text x="20" y="260" class="font-sans desc-text" font-weight="bold">注入的静态思维框架 (Prompts)</text>
|
||||
<rect x="20" y="270" width="80" height="24" rx="4" fill="#F1F5F9" stroke="var(--c-neutral-gray)" />
|
||||
<text x="60" y="286" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-neutral-gray)">5W3H</text>
|
||||
<rect x="110" y="270" width="80" height="24" rx="4" fill="#F1F5F9" stroke="var(--c-neutral-gray)" />
|
||||
<text x="150" y="286" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-neutral-gray)">SCQA</text>
|
||||
<rect x="200" y="270" width="80" height="24" rx="4" fill="#F1F5F9" stroke="var(--c-neutral-gray)" />
|
||||
<text x="240" y="286" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-neutral-gray)">SWOT</text>
|
||||
</g>
|
||||
|
||||
<!-- ================= S3: CoT 多步推理 Server ================= -->
|
||||
<g transform="translate(680, 45)">
|
||||
<rect x="0" y="0" width="300" height="320" rx="8" fill="var(--c-local-green-light)" stroke="var(--c-local-green)" stroke-width="2" />
|
||||
<rect x="0" y="0" width="300" height="40" fill="var(--c-local-green)" rx="8" />
|
||||
<rect x="0" y="20" width="300" height="20" fill="var(--c-local-green)" />
|
||||
<text x="150" y="25" text-anchor="middle" class="font-sans title-sub" fill="#FFFFFF">S3: CoT 推理 Server (分析师)</text>
|
||||
|
||||
<!-- Tech Stack Badges (Unified TS/Node) -->
|
||||
<text x="20" y="65" class="font-sans desc-text" font-weight="bold">开发环境与框架</text>
|
||||
<rect x="20" y="75" width="115" height="24" rx="12" fill="#FFFFFF" stroke="var(--c-local-green)" />
|
||||
<text x="77.5" y="91" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-local-green)">Node.js (v20+)</text>
|
||||
<rect x="145" y="75" width="95" height="24" rx="12" fill="#FFFFFF" stroke="var(--c-local-green)" />
|
||||
<text x="192.5" y="91" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-local-green)">@mcp/sdk-ts</text>
|
||||
|
||||
<!-- Core Algorithms -->
|
||||
<text x="20" y="130" class="font-sans desc-text" font-weight="bold">质量把控与数据组装逻辑</text>
|
||||
<rect x="20" y="140" width="260" height="40" rx="4" fill="#FFFFFF" stroke="var(--c-gov-blue)" />
|
||||
<text x="150" y="165" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-gov-blue)">引文密度强制校验 (Citation Check)</text>
|
||||
|
||||
<rect x="20" y="190" width="260" height="40" rx="4" fill="#FFFFFF" stroke="var(--c-gov-blue)" />
|
||||
<text x="150" y="215" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-gov-blue)">双轨制落盘编译器 (JSON-to-MD)</text>
|
||||
|
||||
<!-- Output Formats -->
|
||||
<text x="20" y="260" class="font-sans desc-text" font-weight="bold">标准数据协议输出</text>
|
||||
<rect x="20" y="270" width="120" height="24" rx="4" fill="#F1F5F9" stroke="var(--c-neutral-gray)" />
|
||||
<text x="80" y="286" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-neutral-gray)">Markdown 解析</text>
|
||||
<rect x="150" y="270" width="130" height="24" rx="4" fill="#F1F5F9" stroke="var(--c-neutral-gray)" />
|
||||
<text x="215" y="286" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-neutral-gray)">YAML Frontmatter</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!--
|
||||
===========================================================================
|
||||
Layer 4: 物理存储与图谱层 (PKM Storage Layer)
|
||||
===========================================================================
|
||||
-->
|
||||
<g id="layer-pkm" transform="translate(100, 790)">
|
||||
<text x="0" y="0" class="font-sans title-main">Layer 4: 数据存储与个人图谱化 (PKM Data Persistence)</text>
|
||||
|
||||
<line x1="500" y1="-30" x2="500" y2="15" stroke="var(--c-local-green)" stroke-width="3" stroke-dasharray="4 4" marker-end="url(#arrow-green)" />
|
||||
|
||||
<rect x="0" y="30" width="1000" height="80" rx="8" fill="var(--c-local-green-light)" stroke="var(--c-local-green)" stroke-width="2" />
|
||||
|
||||
<!-- Storage Cylinder Icon -->
|
||||
<path d="M 40 55 A 25 10 0 1 0 90 55 V 85 A 25 10 0 1 1 40 85 Z" fill="#FFFFFF" stroke="var(--c-local-green)" stroke-width="2"/>
|
||||
<ellipse cx="65" cy="55" rx="25" ry="10" fill="#FFFFFF" stroke="var(--c-local-green)" stroke-width="2"/>
|
||||
|
||||
<text x="120" y="65" class="font-sans title-sub">本地个人知识库 (Local Vault)</text>
|
||||
<text x="120" y="85" class="font-mono desc-text">Obsidian / Logseq Graph System</text>
|
||||
|
||||
<!-- Data Spec Badges -->
|
||||
<rect x="420" y="55" width="180" height="30" rx="4" fill="#FFFFFF" stroke="var(--c-gov-blue)" />
|
||||
<text x="510" y="75" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-gov-blue)">双向链接 [[文献名称]] 编排</text>
|
||||
|
||||
<rect x="620" y="55" width="160" height="30" rx="4" fill="#FFFFFF" stroke="var(--c-gov-blue)" />
|
||||
<text x="700" y="75" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-gov-blue)">自动化文献目录索引池</text>
|
||||
</g>
|
||||
|
||||
<!--
|
||||
===========================================================================
|
||||
Layer 5: 工程化与运维安全 (Engineering & Security)
|
||||
===========================================================================
|
||||
-->
|
||||
<g id="layer-engineering" transform="translate(100, 930)">
|
||||
<text x="0" y="0" class="font-sans title-main">Layer 5: 架构运维管控与安全基线 (Engineering & Security)</text>
|
||||
<rect x="0" y="15" width="1000" height="60" rx="8" fill="var(--c-gov-blue-light)" stroke="var(--c-gov-blue)" stroke-width="2" />
|
||||
|
||||
<!-- Security Badge 1 -->
|
||||
<rect x="30" y="30" width="280" height="30" rx="4" fill="#FFFFFF" stroke="var(--c-gov-blue)" />
|
||||
<text x="170" y="50" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-gov-blue)">零信任架构 (不可信负载防范)</text>
|
||||
|
||||
<!-- Security Badge 2 -->
|
||||
<rect x="340" y="30" width="280" height="30" rx="4" fill="#FFFFFF" stroke="var(--c-gov-blue)" />
|
||||
<text x="480" y="50" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-gov-blue)">.env.example 隔离敏感凭证</text>
|
||||
|
||||
<!-- Performance Badge -->
|
||||
<rect x="650" y="30" width="320" height="30" rx="4" fill="#FFFFFF" stroke="var(--c-gov-blue)" />
|
||||
<text x="810" y="50" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-gov-blue)">异步非阻塞事件流 (高并发处理能力)</text>
|
||||
</g>
|
||||
|
||||
<!--
|
||||
===========================================================================
|
||||
底部图例与注释块 (Legend & Key Block)
|
||||
===========================================================================
|
||||
-->
|
||||
<g id="legend" transform="translate(600, 1050)">
|
||||
<rect x="-440" y="-16" width="880" height="40" rx="4" fill="rgba(255, 255, 255, 0.9)" stroke="var(--c-neutral-gray)" stroke-width="0.5"/>
|
||||
|
||||
<g transform="translate(-420, 0)">
|
||||
<text x="0" y="8" class="font-sans" font-size="13" font-weight="bold" fill="var(--c-neutral-gray)">语义图例说明:</text>
|
||||
|
||||
<!-- 云端/外部 -->
|
||||
<g transform="translate(110, 0)">
|
||||
<rect x="-6" y="-6" width="12" height="12" rx="2" fill="var(--c-cloud-blue)" />
|
||||
<text x="12" y="8" class="font-sans" font-size="12" fill="var(--c-neutral-gray)">云端服务 / 外部资源</text>
|
||||
</g>
|
||||
|
||||
<!-- 本地/核心 -->
|
||||
<g transform="translate(265, 0)">
|
||||
<rect x="-6" y="-6" width="12" height="12" rx="2" fill="var(--c-local-green)" />
|
||||
<text x="12" y="8" class="font-sans" font-size="12" fill="var(--c-neutral-gray)">本地服务 / 核心引擎</text>
|
||||
</g>
|
||||
|
||||
<!-- 风险/高负载 -->
|
||||
<g transform="translate(420, 0)">
|
||||
<rect x="-6" y="-6" width="12" height="12" rx="2" fill="var(--c-risk-amber)" />
|
||||
<text x="12" y="8" class="font-sans" font-size="12" fill="var(--c-neutral-gray)">高频计算 / 网络爬取</text>
|
||||
</g>
|
||||
|
||||
<!-- 治理/安全 -->
|
||||
<g transform="translate(575, 0)">
|
||||
<rect x="-6" y="-6" width="12" height="12" rx="2" fill="var(--c-gov-blue)" />
|
||||
<text x="12" y="8" class="font-sans" font-size="12" fill="var(--c-neutral-gray)">治理规范 / 安全控制</text>
|
||||
</g>
|
||||
|
||||
<!-- 逻辑边界 -->
|
||||
<g transform="translate(730, 0)">
|
||||
<rect x="-8" y="-8" width="16" height="16" rx="2" fill="transparent" stroke="var(--c-neutral-gray)" stroke-dasharray="2 2" stroke-width="1.5" />
|
||||
<text x="14" y="8" class="font-sans" font-size="12" fill="var(--c-neutral-gray)">逻辑边界 / 虚拟容器</text>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!--
|
||||
===========================================================================
|
||||
底部许可声明
|
||||
===========================================================================
|
||||
-->
|
||||
<text x="600" y="1140" text-anchor="middle" class="font-sans" font-size="11" fill="var(--c-neutral-gray)">
|
||||
本作品采用 CC-BY-SA 4.0 进行许可,© 2025-2026 Gitconomy Research社区
|
||||
</text>
|
||||
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 20 KiB |
|
|
@ -0,0 +1,267 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 970" width="100%" height="100%">
|
||||
<!--
|
||||
================================================================================
|
||||
图表名称:个人级研究助手智能体 - 开发框架与技术栈架构图
|
||||
文件命名:figure03-mcp-tech-stack-framework.svg
|
||||
用途:展示从宿主端到三个核心 MCP Server 的底层技术栈、核心算法、Monorepo工程化及安全交互框架。
|
||||
版本:v2.0.0
|
||||
作者:Gitconomy Research-郭晧
|
||||
SPDX-License-Identifier: MIT & CC-BY-SA-4.0
|
||||
创建日期:2026-02-27
|
||||
更新日期:2026-03-08
|
||||
更新说明:更新更新的项目开发框架说明文档重新设计项目开发框架工具链构成的逻辑示意图。
|
||||
================================================================================
|
||||
-->
|
||||
|
||||
<!-- Background: Solid White -->
|
||||
<rect width="100%" height="100%" fill="#FFFFFF" />
|
||||
|
||||
<defs>
|
||||
<!-- CSS 变量与全局样式 -->
|
||||
<style>
|
||||
:root {
|
||||
/* 语义色彩体系 */
|
||||
--c-cloud-blue: #0099FF;
|
||||
--c-cloud-blue-light: rgba(0, 153, 255, 0.08);
|
||||
--c-local-green: #009900;
|
||||
--c-local-green-light: rgba(0, 153, 0, 0.08);
|
||||
--c-risk-amber: #FF991F;
|
||||
--c-risk-amber-light: rgba(255, 153, 31, 0.08);
|
||||
--c-neutral-gray: #475569;
|
||||
--c-neutral-gray-light: #F1F5F9;
|
||||
--c-gov-blue: #0052CC;
|
||||
--c-op-green: #00875A;
|
||||
|
||||
/* 字体系统降级适配 */
|
||||
--font-sans: 'Noto Sans', 'Helvetica Neue', Arial, sans-serif;
|
||||
--font-mono: 'JetBrains Mono', Consolas, 'Courier New', monospace;
|
||||
}
|
||||
|
||||
/* 文本工具类 */
|
||||
.font-sans { font-family: var(--font-sans); }
|
||||
.font-mono { font-family: var(--font-mono); }
|
||||
.text-bold { font-weight: bold; }
|
||||
</style>
|
||||
|
||||
<!-- 箭头标记定义 -->
|
||||
<marker id="arrow-green" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="var(--c-local-green)" />
|
||||
</marker>
|
||||
<marker id="arrow-amber" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="var(--c-risk-amber)" />
|
||||
</marker>
|
||||
<marker id="arrow-blue" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="var(--c-cloud-blue)" />
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<!-- ==========================================
|
||||
1. 标题区域 (Title Block)
|
||||
=========================================== -->
|
||||
<g id="title-block" transform="translate(500, 40)">
|
||||
<!-- 1. Figure ID -->
|
||||
<text y="0" text-anchor="middle" class="font-mono text-bold" font-size="12" fill="var(--c-neutral-gray)" letter-spacing="1">FIGURE-03</text>
|
||||
|
||||
<!-- 2. Main Title -->
|
||||
<text y="30" text-anchor="middle" class="font-sans text-bold" font-size="24" fill="#000000">Project Caffeine 开发框架与技术栈</text>
|
||||
|
||||
<!-- 3. Breadcrumbs -->
|
||||
<text y="55" text-anchor="middle" class="font-mono" font-size="14" fill="var(--c-neutral-gray)">Framework > Tech Stack > Monolithic Server</text>
|
||||
|
||||
<!-- 4. Context Indicator (Y=70, 高度3,底部为 Y=73。相对于 translate(500, 40),绝对底部 Y=113) -->
|
||||
<rect x="-30" y="70" width="60" height="3" fill="var(--c-local-green)" />
|
||||
</g>
|
||||
|
||||
<!-- ==========================================
|
||||
2. 核心架构主体 (Diagram Content)
|
||||
为了与标题底部(113)精确保持50间距,设定 translate Y 为 163
|
||||
=========================================== -->
|
||||
<g id="diagram-content" transform="translate(0, 163)">
|
||||
|
||||
<!-- 外部客户端 (MCP Client Desktop) 笔记本图标 -->
|
||||
<g id="mcp-client" transform="translate(500, 0)">
|
||||
<!-- 屏幕外框 -->
|
||||
<rect x="-35" y="0" width="70" height="45" rx="4" fill="none" stroke="var(--c-cloud-blue)" stroke-width="3"/>
|
||||
<!-- 屏幕内侧 -->
|
||||
<rect x="-31" y="4" width="62" height="37" rx="1" fill="var(--c-cloud-blue-light)"/>
|
||||
<!-- 笔记本底座 -->
|
||||
<path d="M-45,45 L45,45 L52,52 L-52,52 Z" fill="var(--c-cloud-blue)"/>
|
||||
<!-- 触控板区 -->
|
||||
<rect x="-8" y="47" width="16" height="3" fill="#FFFFFF" opacity="0.5"/>
|
||||
<!-- 键盘区装饰 -->
|
||||
<path d="M-35,46 L35,46 L38,49 L-38,49 Z" fill="#FFFFFF" opacity="0.2"/>
|
||||
|
||||
<text x="0" y="75" text-anchor="middle" class="font-sans text-bold" font-size="14" fill="var(--c-cloud-blue)">Client (Claude Desktop / Cherry Studio)</text>
|
||||
</g>
|
||||
|
||||
<!-- 连接线条:客户端到 MCP Server -->
|
||||
<line x1="500" y1="85" x2="500" y2="135" stroke="var(--c-local-green)" stroke-width="2" marker-end="url(#arrow-green)" />
|
||||
|
||||
<!-- 单体服务器容器 (整体向下偏移以容纳客户端) -->
|
||||
<g id="monolithic-server" transform="translate(0, 100)">
|
||||
|
||||
<!-- 单体服务器边界 (Monolithic Boundary) -->
|
||||
<g id="monolithic-boundary">
|
||||
<rect x="60" y="0" width="880" height="570" rx="12" fill="none" stroke="var(--c-local-green)" stroke-width="2" stroke-dasharray="6 4" />
|
||||
|
||||
<!-- 边界标签 -->
|
||||
<rect x="80" y="-12" width="220" height="24" fill="#FFFFFF" />
|
||||
<text x="90" y="4" class="font-mono text-bold" font-size="14" fill="var(--c-local-green)">📦 Monolithic MCP Server</text>
|
||||
</g>
|
||||
|
||||
<!-- 内部连接线条 (Data Flows) -->
|
||||
<g id="data-flows">
|
||||
<!-- Core to Zod (Sync) -->
|
||||
<line x1="380" y1="190" x2="340" y2="190" stroke="var(--c-local-green)" stroke-width="2" marker-end="url(#arrow-green)" marker-start="url(#arrow-green)" />
|
||||
|
||||
<!-- Core to Axios (Async) -->
|
||||
<line x1="620" y1="190" x2="660" y2="190" stroke="var(--c-risk-amber)" stroke-width="2" stroke-dasharray="4 2" marker-end="url(#arrow-amber)" />
|
||||
|
||||
<!-- Core to Runtime -->
|
||||
<line x1="500" y1="230" x2="500" y2="260" stroke="var(--c-local-green)" stroke-width="2" marker-end="url(#arrow-green)" />
|
||||
|
||||
<!-- Axios to External API (Cloud) -->
|
||||
<line x1="900" y1="190" x2="950" y2="190" stroke="var(--c-cloud-blue)" stroke-width="2" stroke-dasharray="4 2" marker-end="url(#arrow-blue)" />
|
||||
<text x="960" y="194" class="font-mono" font-size="12" fill="var(--c-cloud-blue)">Academic APIs</text>
|
||||
</g>
|
||||
|
||||
<!-- ================== 层级 1:协议与网络层 (Y=40) ================== -->
|
||||
<!-- MCP SDK -->
|
||||
<g id="card-mcp" transform="translate(380, 40)">
|
||||
<rect width="240" height="80" rx="6" fill="var(--c-cloud-blue-light)" stroke="var(--c-cloud-blue)" stroke-width="1.5" />
|
||||
<rect width="6" height="80" rx="3" fill="var(--c-cloud-blue)" />
|
||||
<text x="20" y="24" class="font-sans text-bold" font-size="14" fill="var(--c-cloud-blue)">协议层 (Protocol)</text>
|
||||
<text x="20" y="46" class="font-mono text-bold" font-size="16" fill="#1e293b">MCP SDK</text>
|
||||
<text x="20" y="66" class="font-sans" font-size="12" fill="var(--c-neutral-gray)">@modelcontextprotocol/sdk (stdio/SSE)</text>
|
||||
</g>
|
||||
|
||||
<!-- ================== 层级 2:核心业务层 (Y=150) ================== -->
|
||||
<!-- Zod -->
|
||||
<g id="card-zod" transform="translate(100, 150)">
|
||||
<rect width="240" height="80" rx="6" fill="var(--c-local-green-light)" stroke="var(--c-local-green)" stroke-width="1.5" />
|
||||
<rect width="6" height="80" rx="3" fill="var(--c-local-green)" />
|
||||
<text x="20" y="24" class="font-sans text-bold" font-size="14" fill="var(--c-local-green)">校验工具 (Validation)</text>
|
||||
<text x="20" y="46" class="font-mono text-bold" font-size="16" fill="#1e293b">Zod</text>
|
||||
<text x="20" y="66" class="font-sans" font-size="12" fill="var(--c-neutral-gray)">运行时类型校验与参数安全验证</text>
|
||||
</g>
|
||||
|
||||
<!-- TypeScript -->
|
||||
<g id="card-ts" transform="translate(380, 150)">
|
||||
<rect width="240" height="80" rx="6" fill="var(--c-local-green-light)" stroke="var(--c-local-green)" stroke-width="2" />
|
||||
<rect width="6" height="80" rx="3" fill="var(--c-local-green)" />
|
||||
<text x="20" y="24" class="font-sans text-bold" font-size="14" fill="var(--c-local-green)">核心语言 (Core Language)</text>
|
||||
<text x="20" y="46" class="font-mono text-bold" font-size="16" fill="#1e293b">TypeScript</text>
|
||||
<text x="20" y="66" class="font-sans" font-size="12" fill="var(--c-neutral-gray)">所有服务逻辑与控制流强制使用</text>
|
||||
</g>
|
||||
|
||||
<!-- axios -->
|
||||
<g id="card-axios" transform="translate(660, 150)">
|
||||
<rect width="240" height="80" rx="6" fill="var(--c-risk-amber-light)" stroke="var(--c-risk-amber)" stroke-width="1.5" />
|
||||
<rect width="6" height="80" rx="3" fill="var(--c-risk-amber)" />
|
||||
<text x="20" y="24" class="font-sans text-bold" font-size="14" fill="var(--c-risk-amber)">HTTP 客户端 (Network I/O)</text>
|
||||
<text x="20" y="46" class="font-mono text-bold" font-size="16" fill="#1e293b">axios</text>
|
||||
<text x="20" y="66" class="font-sans" font-size="12" fill="var(--c-neutral-gray)">封装学术 API,支持重试与速率限制</text>
|
||||
</g>
|
||||
|
||||
<!-- ================== 层级 3:运行环境层 (Y=260) ================== -->
|
||||
<!-- Node.js -->
|
||||
<g id="card-node" transform="translate(380, 260)">
|
||||
<rect width="240" height="80" rx="6" fill="var(--c-local-green-light)" stroke="var(--c-local-green)" stroke-width="1.5" />
|
||||
<rect width="6" height="80" rx="3" fill="var(--c-local-green)" />
|
||||
<text x="20" y="24" class="font-sans text-bold" font-size="14" fill="var(--c-local-green)">运行环境 (Runtime)</text>
|
||||
<text x="20" y="46" class="font-mono text-bold" font-size="16" fill="#1e293b">Node.js LTS v20+</text>
|
||||
<text x="20" y="66" class="font-sans" font-size="12" fill="var(--c-neutral-gray)">单进程异步非阻塞 I/O 引擎</text>
|
||||
</g>
|
||||
|
||||
<!-- 分隔线 -->
|
||||
<line x1="80" y1="365" x2="920" y2="365" stroke="var(--c-neutral-gray)" stroke-width="1" stroke-dasharray="2 4" />
|
||||
<text x="100" y="360" class="font-mono" font-size="12" fill="var(--c-neutral-gray)">DevTools & Engineering Infrastructure</text>
|
||||
|
||||
<!-- ================== 层级 4:工程化基础工具 (Y=390) ================== -->
|
||||
<!-- Winston -->
|
||||
<g id="card-log" transform="translate(100, 390)">
|
||||
<rect width="240" height="80" rx="6" fill="var(--c-neutral-gray-light)" stroke="var(--c-neutral-gray)" stroke-width="1" />
|
||||
<rect width="6" height="80" rx="3" fill="var(--c-neutral-gray)" />
|
||||
<text x="20" y="24" class="font-sans text-bold" font-size="14" fill="var(--c-neutral-gray)">日志工具 (Logging)</text>
|
||||
<text x="20" y="46" class="font-mono text-bold" font-size="16" fill="#1e293b">winston / log4js</text>
|
||||
<text x="20" y="66" class="font-sans" font-size="12" fill="#64748b">生产级结构化日志记录与追踪</text>
|
||||
</g>
|
||||
|
||||
<!-- VS Code -->
|
||||
<g id="card-vscode" transform="translate(380, 390)">
|
||||
<rect width="240" height="80" rx="6" fill="var(--c-neutral-gray-light)" stroke="var(--c-neutral-gray)" stroke-width="1" />
|
||||
<rect width="6" height="80" rx="3" fill="var(--c-neutral-gray)" />
|
||||
<text x="20" y="24" class="font-sans text-bold" font-size="14" fill="var(--c-neutral-gray)">调试工具 (Debugging)</text>
|
||||
<text x="20" y="46" class="font-mono text-bold" font-size="16" fill="#1e293b">VS Code (--inspect)</text>
|
||||
<text x="20" y="66" class="font-sans" font-size="12" fill="#64748b">统一入口进程挂载与源码级断点</text>
|
||||
</g>
|
||||
|
||||
<!-- Jest + k6 -->
|
||||
<g id="card-test" transform="translate(660, 390)">
|
||||
<rect width="240" height="80" rx="6" fill="var(--c-neutral-gray-light)" stroke="var(--c-neutral-gray)" stroke-width="1" />
|
||||
<rect width="6" height="80" rx="3" fill="var(--c-neutral-gray)" />
|
||||
<text x="20" y="24" class="font-sans text-bold" font-size="14" fill="var(--c-neutral-gray)">测试工具 (Testing)</text>
|
||||
<text x="20" y="46" class="font-mono text-bold" font-size="16" fill="#1e293b">Jest + k6</text>
|
||||
<text x="20" y="66" class="font-sans" font-size="12" fill="#64748b">服务单元测试与接口负载压测</text>
|
||||
</g>
|
||||
|
||||
<!-- ================== 层级 5:包与仓库管理 (Y=500) ================== -->
|
||||
<!-- npm -->
|
||||
<g id="card-npm" transform="translate(380, 480)">
|
||||
<rect width="240" height="60" rx="6" fill="var(--c-neutral-gray-light)" stroke="var(--c-neutral-gray)" stroke-width="1" />
|
||||
<text x="120" y="25" text-anchor="middle" class="font-sans text-bold" font-size="14" fill="var(--c-neutral-gray)">包管理 (Package Config)</text>
|
||||
<text x="120" y="45" text-anchor="middle" class="font-mono text-bold" font-size="14" fill="#1e293b">npm (单一 package.json)</text>
|
||||
</g>
|
||||
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- ==========================================
|
||||
3. 底部图例与注释块 (Legend & Key Block)
|
||||
向下调整布局至 Y=890 适应新高度
|
||||
=========================================== -->
|
||||
<g id="legend" transform="translate(500, 890)">
|
||||
<!-- 统一的半透明背景容器 -->
|
||||
<rect x="-420" y="-16" width="840" height="40" rx="4" fill="rgba(255, 255, 255, 0.9)" stroke="var(--c-neutral-gray)" stroke-width="0.5" />
|
||||
|
||||
<g transform="translate(-400, 0)">
|
||||
<!-- 图例引导文本 -->
|
||||
<text x="0" y="8" class="font-sans text-bold" font-size="12" fill="var(--c-neutral-gray)">图例说明:</text>
|
||||
|
||||
<!-- 图例项 1: Cloud/External -->
|
||||
<g transform="translate(80, 0)">
|
||||
<rect x="0" y="-2" width="12" height="12" rx="2" fill="var(--c-cloud-blue)" />
|
||||
<text x="18" y="8" class="font-mono" font-size="12" fill="var(--c-neutral-gray)">接口/外部协议 (Interface)</text>
|
||||
</g>
|
||||
|
||||
<!-- 图例项 2: Local/Core -->
|
||||
<g transform="translate(260, 0)">
|
||||
<rect x="0" y="-2" width="12" height="12" rx="2" fill="var(--c-local-green)" />
|
||||
<text x="18" y="8" class="font-mono" font-size="12" fill="var(--c-neutral-gray)">核心引擎/计算 (Core/Sync)</text>
|
||||
</g>
|
||||
|
||||
<!-- 图例项 3: Network I/O -->
|
||||
<g transform="translate(440, 0)">
|
||||
<rect x="0" y="-2" width="12" height="12" rx="2" fill="var(--c-risk-amber)" />
|
||||
<text x="18" y="8" class="font-mono" font-size="12" fill="var(--c-neutral-gray)">网络/异步流 (Async I/O)</text>
|
||||
</g>
|
||||
|
||||
<!-- 图例项 4: Engineering -->
|
||||
<g transform="translate(620, 0)">
|
||||
<rect x="0" y="-2" width="12" height="12" rx="2" fill="var(--c-neutral-gray)" />
|
||||
<text x="18" y="8" class="font-mono" font-size="12" fill="var(--c-neutral-gray)">基建/工具 (DevTools)</text>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- ==========================================
|
||||
4. 底部许可声明 (License)
|
||||
向下调整至 Y=950
|
||||
=========================================== -->
|
||||
<g id="license" transform="translate(500, 950)">
|
||||
<text text-anchor="middle" class="font-sans" font-size="10" fill="#94a3b8">
|
||||
本作品采用 CC-BY-SA 4.0 进行许可,© 2025-2026 Gitconomy Research
|
||||
</text>
|
||||
</g>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 14 KiB |
|
|
@ -1,15 +1,17 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 850" width="100%" height="100%">
|
||||
<!--
|
||||
================================================================================
|
||||
图表名称:基于 MCP 的研报智能体系统拓扑图 (Research MCP Server Topology)
|
||||
文件命名:project-caffeine-system-topology.svg
|
||||
用途:展示学术研究 MCP 系统的核心组件交互拓扑,包括用户层客户端、MCP 传输协议层、MCP Server 集群逻辑分工,以及与外部学术基础设施的数据流向。
|
||||
版本:v1.0.1
|
||||
作者:Gitconomy Research-AI
|
||||
版本:v2.0.1
|
||||
作者:Gitconomy Research-郭晧
|
||||
SPDX-License-Identifier: MIT & CC-BY-SA-4.0
|
||||
创建日期:2026-02-27
|
||||
更新r日期:2026-03-08
|
||||
更新说明:根据开发文档更新系统架构
|
||||
================================================================================
|
||||
-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 850" width="100%" height="100%">
|
||||
<!-- Background: Solid White (强制兼容深色模式的白底) -->
|
||||
<rect width="100%" height="100%" fill="#FFFFFF" />
|
||||
|
||||
|
|
@ -75,9 +77,9 @@ SPDX-License-Identifier: MIT & CC-BY-SA-4.0
|
|||
<!-- 图表编号 -->
|
||||
<text y="-30" text-anchor="middle" class="text-mono" font-size="12" fill="var(--c-neutral-gray)" letter-spacing="1">FIG-01</text>
|
||||
<!-- 主标题 -->
|
||||
<text y="0" text-anchor="middle" class="text-title" font-size="24" fill="#000000">研报智能体 MCP 系统拓扑图</text>
|
||||
<text y="0" text-anchor="middle" class="text-title" font-size="24" fill="#000000">Project Caffeine 研报智能体 MCP 系统拓扑图</text>
|
||||
<!-- 面包屑导航 -->
|
||||
<text y="25" text-anchor="middle" class="text-mono" font-size="14" fill="var(--c-neutral-gray)">架构图 > MCP > 系统拓扑结构</text>
|
||||
<text y="25" text-anchor="middle" class="text-mono" font-size="14" fill="var(--c-neutral-gray)">架构图 > 智能体 MCP > 核心原语拓扑</text>
|
||||
<!-- 上下文指示线 (使用主色调代表架构主体) -->
|
||||
<rect x="-30" y="40" width="60" height="3" fill="var(--c-local-green)" />
|
||||
</g>
|
||||
|
|
@ -92,15 +94,15 @@ SPDX-License-Identifier: MIT & CC-BY-SA-4.0
|
|||
|
||||
<!-- MCP Server 集群区域 (Center: Local/Container) -->
|
||||
<rect x="420" y="0" width="340" height="520" rx="8" class="box-local" />
|
||||
<text x="435" y="25" class="text-title" font-size="14" fill="var(--c-local-green)">MCP 服务端集群 (核心执行区)</text>
|
||||
<text x="435" y="25" class="text-title" font-size="14" fill="var(--c-local-green)">单体 MCP Server (三大核心原语)</text>
|
||||
|
||||
<!-- 外部基础设施区域 (Right: Cloud) -->
|
||||
<rect x="850" y="0" width="400" height="330" rx="8" class="box-cloud" />
|
||||
<rect x="850" y="0" width="400" height="200" rx="8" class="box-cloud" />
|
||||
<text x="865" y="25" class="text-title" font-size="14" fill="var(--c-cloud-blue)">外部学术基础设施 (公有云)</text>
|
||||
|
||||
<!-- 本地知识库区域 (Right Bottom: Local Storage) -->
|
||||
<rect x="850" y="360" width="400" height="160" rx="8" class="box-local" />
|
||||
<text x="865" y="385" class="text-title" font-size="14" fill="var(--c-local-green)">本地知识库</text>
|
||||
<rect x="850" y="240" width="400" height="280" rx="8" class="box-local" />
|
||||
<text x="865" y="265" class="text-title" font-size="14" fill="var(--c-local-green)">本地持久化存储</text>
|
||||
</g>
|
||||
|
||||
<!-- ========================================================================= -->
|
||||
|
|
@ -121,7 +123,7 @@ SPDX-License-Identifier: MIT & CC-BY-SA-4.0
|
|||
<text x="365" y="325" text-anchor="middle" class="text-mono" font-size="10" fill="var(--c-neutral-gray)">JSON-RPC 2.0</text>
|
||||
|
||||
<!-- 协议层标签 -->
|
||||
<text x="365" y="380" text-anchor="middle" class="text-title" font-size="12" fill="var(--c-neutral-gray)">MCP 标准传输协议层</text>
|
||||
<text x="365" y="380" text-anchor="middle" class="text-title" font-size="12" fill="var(--c-neutral-gray)">MCP 传输协议层</text>
|
||||
</g>
|
||||
|
||||
<!-- ========================================================================= -->
|
||||
|
|
@ -145,7 +147,7 @@ SPDX-License-Identifier: MIT & CC-BY-SA-4.0
|
|||
<text x="110" y="30" text-anchor="middle" class="text-title" font-size="16" fill="var(--c-local-green)">MCP 客户端</text>
|
||||
<text x="110" y="50" text-anchor="middle" class="text-mono" font-size="12" fill="#000000">Claude Desktop / Cursor</text>
|
||||
<rect x="20" y="70" width="180" height="30" rx="4" fill="#FFFFFF" stroke="var(--c-local-green)" stroke-width="1" />
|
||||
<text x="110" y="89" text-anchor="middle" class="text-sans" font-size="12" fill="#000000">大模型上下文与工具调度</text>
|
||||
<text x="110" y="89" text-anchor="middle" class="text-sans" font-size="12" fill="#000000">大模型上下文与协议调度</text>
|
||||
</g>
|
||||
|
||||
<!-- 远程 LLM (Remote Brain) - 从 Client 发起调用 -->
|
||||
|
|
@ -161,33 +163,33 @@ SPDX-License-Identifier: MIT & CC-BY-SA-4.0
|
|||
<text x="180" y="380" class="text-sans" font-size="10" fill="var(--c-risk-amber)">提示词请求 / 结果流式响应</text>
|
||||
|
||||
|
||||
<!-- [4.2] MCP Server 集群 -->
|
||||
<!-- [4.2] 单体 MCP Server 的 3 大核心原语 -->
|
||||
|
||||
<!-- MCP Server 1: 文献查询 -->
|
||||
<!-- MCP 原语 1: Tools -->
|
||||
<g transform="translate(450, 70)">
|
||||
<rect width="280" height="100" rx="6" class="node-local" />
|
||||
<rect x="0" y="0" width="280" height="35" rx="6" fill="var(--c-local-green)" />
|
||||
<text x="140" y="22" text-anchor="middle" class="text-title" font-size="14" fill="#FFFFFF">文献查询 MCP Server</text>
|
||||
<text x="140" y="55" text-anchor="middle" class="text-mono" font-size="11" fill="#000000">工具: search_papers(), fetch_pdf()</text>
|
||||
<text x="140" y="75" text-anchor="middle" class="text-sans" font-size="11" fill="var(--c-neutral-gray)">负责多源学术数据库 API 聚合与清洗</text>
|
||||
<text x="140" y="22" text-anchor="middle" class="text-title" font-size="14" fill="#FFFFFF">Tools 原语 (动作执行)</text>
|
||||
<text x="140" y="55" text-anchor="middle" class="text-mono" font-size="11" fill="#000000">search_academic_literature(), save_to_vault()</text>
|
||||
<text x="140" y="75" text-anchor="middle" class="text-sans" font-size="11" fill="var(--c-neutral-gray)">调用外部API检索文献并执行标准化数据双轨落盘</text>
|
||||
</g>
|
||||
|
||||
<!-- MCP Server 2: 提示词策略 -->
|
||||
<!-- MCP 原语 2: Prompts -->
|
||||
<g transform="translate(450, 210)">
|
||||
<rect width="280" height="100" rx="6" class="node-local" />
|
||||
<rect x="0" y="0" width="280" height="35" rx="6" fill="var(--c-local-green)" />
|
||||
<text x="140" y="22" text-anchor="middle" class="text-title" font-size="14" fill="#FFFFFF">提示词策略 MCP Server</text>
|
||||
<text x="140" y="55" text-anchor="middle" class="text-mono" font-size="11" fill="#000000">提示词: synthesis_template</text>
|
||||
<text x="140" y="75" text-anchor="middle" class="text-sans" font-size="11" fill="var(--c-neutral-gray)">负责基于主题动态组装超级提示词</text>
|
||||
<text x="140" y="22" text-anchor="middle" class="text-title" font-size="14" fill="#FFFFFF">Prompts 原语 (上下文策略)</text>
|
||||
<text x="140" y="55" text-anchor="middle" class="text-mono" font-size="11" fill="#000000">prompts/get: 5w3h, scqa, pestle 等</text>
|
||||
<text x="140" y="75" text-anchor="middle" class="text-sans" font-size="11" fill="var(--c-neutral-gray)">提供多维思维框架,动态指导大模型分析与拆解</text>
|
||||
</g>
|
||||
|
||||
<!-- MCP Server 3: CoT 推理 -->
|
||||
<!-- MCP 原语 3: Resources -->
|
||||
<g transform="translate(450, 350)">
|
||||
<rect width="280" height="100" rx="6" class="node-amber" />
|
||||
<rect x="0" y="0" width="280" height="35" rx="6" fill="var(--c-risk-amber)" />
|
||||
<text x="140" y="22" text-anchor="middle" class="text-title" font-size="14" fill="#FFFFFF">CoT 推理 MCP Server</text>
|
||||
<text x="140" y="55" text-anchor="middle" class="text-mono" font-size="11" fill="#000000">工具: run_cot_chain(), verify_logic()</text>
|
||||
<text x="140" y="75" text-anchor="middle" class="text-sans" font-size="11" fill="var(--c-neutral-gray)">协调多步思考推理,抽取实体与洞察生成</text>
|
||||
<rect width="280" height="100" rx="6" class="node-local" />
|
||||
<rect x="0" y="0" width="280" height="35" rx="6" fill="var(--c-local-green)" />
|
||||
<text x="140" y="22" text-anchor="middle" class="text-title" font-size="14" fill="#FFFFFF">Resources 原语 (数据暴露)</text>
|
||||
<text x="140" y="55" text-anchor="middle" class="text-mono" font-size="11" fill="#000000">literature://local/, note://local/</text>
|
||||
<text x="140" y="75" text-anchor="middle" class="text-sans" font-size="11" fill="var(--c-neutral-gray)">暴露本地知识库的文献卡片与笔记供大模型访问</text>
|
||||
</g>
|
||||
|
||||
|
||||
|
|
@ -198,49 +200,39 @@ SPDX-License-Identifier: MIT & CC-BY-SA-4.0
|
|||
<!-- Cylinder -->
|
||||
<path d="M 0,20 A 40,15 0 0,0 80,20 A 40,15 0 0,0 0,20 L 0,80 A 40,15 0 0,0 80,80 L 80,20" class="node-cloud" />
|
||||
<text x="40" y="50" text-anchor="middle" class="text-title" font-size="12" fill="#000000">学术数据库</text>
|
||||
<text x="40" y="68" text-anchor="middle" class="text-mono" font-size="10" fill="var(--c-cloud-blue)">PubMed, arXiv</text>
|
||||
<text x="40" y="82" text-anchor="middle" class="text-mono" font-size="10" fill="var(--c-cloud-blue)">IEEE, WOS</text>
|
||||
<text x="40" y="68" text-anchor="middle" class="text-mono" font-size="10" fill="var(--c-cloud-blue)">arXiv API</text>
|
||||
<text x="40" y="82" text-anchor="middle" class="text-mono" font-size="10" fill="var(--c-cloud-blue)">Semantic Scholar</text>
|
||||
</g>
|
||||
|
||||
<!-- 互联网资源 -->
|
||||
<g transform="translate(1080, 200)">
|
||||
<!-- Hexagon-like Cloud Service -->
|
||||
<polygon points="20,0 80,0 100,40 80,80 20,80 0,40" class="node-cloud" />
|
||||
<text x="50" y="35" text-anchor="middle" class="text-title" font-size="12" fill="#000000">互联网资源</text>
|
||||
<text x="50" y="55" text-anchor="middle" class="text-mono" font-size="10" fill="var(--c-cloud-blue)">PDF / HTML</text>
|
||||
</g>
|
||||
|
||||
|
||||
<!-- [4.4] 本地知识库 (Local Knowledge Base) -->
|
||||
|
||||
<g transform="translate(980, 410)">
|
||||
<g transform="translate(930, 360)">
|
||||
<!-- Cylinder -->
|
||||
<path d="M 0,20 A 50,15 0 0,0 100,20 A 50,15 0 0,0 0,20 L 0,80 A 50,15 0 0,0 100,80 L 100,20" class="node-local" />
|
||||
<text x="50" y="50" text-anchor="middle" class="text-title" font-size="12" fill="#000000">本地知识库</text>
|
||||
<text x="50" y="70" text-anchor="middle" class="text-mono" font-size="11" fill="var(--c-local-green)">Obsidian / Logseq</text>
|
||||
<text x="50" y="70" text-anchor="middle" class="text-mono" font-size="11" fill="var(--c-local-green)">Obsidian / PKM</text>
|
||||
<text x="50" y="85" text-anchor="middle" class="text-mono" font-size="9" fill="var(--c-neutral-gray)">(YAML / Markdown)</text>
|
||||
</g>
|
||||
|
||||
<!-- [4.5] 服务器到外部/本地基础设施的连线 -->
|
||||
|
||||
<!-- 文献查询 MCP -> 学术数据库 (Async) -->
|
||||
<!-- Tools 原语 -> 学术数据库 (Async) -->
|
||||
<path d="M 730,100 L 930,100" class="line-async" marker-end="url(#arrow-async)" />
|
||||
<text x="830" y="90" text-anchor="middle" class="text-sans" font-size="10" fill="var(--c-risk-amber)">API 请求调用 (REST/GraphQL)</text>
|
||||
<text x="810" y="90" text-anchor="middle" class="text-sans" font-size="10" fill="var(--c-risk-amber)">API 请求调用 (REST)</text>
|
||||
|
||||
<!-- CoT 推理 MCP -> 互联网资源 (Async) -->
|
||||
<path d="M 730,400 L 1130,400 L 1130,285" class="line-async" marker-end="url(#arrow-async)" />
|
||||
<text x="1000" y="390" text-anchor="middle" class="text-sans" font-size="10" fill="var(--c-risk-amber)">拉取文献全文 / 网页爬取</text>
|
||||
<!-- Tools 原语 -> Local KB (Sync) 写入落盘 -->
|
||||
<!-- 采用肘型连接线绕行 -->
|
||||
<path d="M 730,140 L 880,140 L 880,380 L 930,380" class="line-sync" marker-end="url(#arrow-sync)" />
|
||||
<text x="800" y="160" text-anchor="middle" class="text-sans" font-size="10" fill="var(--c-local-green)">JSON 转 Markdown+YAML</text>
|
||||
|
||||
<!-- 提示词策略 MCP -> Local KB (Sync) (如果是读取本地上下文) -->
|
||||
<!-- 文献查询/CoT推理 MCP -> Local KB (Sync) (双轨制写入落盘) -->
|
||||
<!-- 采用肘型连接线 -->
|
||||
<path d="M 730,420 L 790,420 L 790,460 L 980,460" class="line-sync" marker-end="url(#arrow-sync)" />
|
||||
<text x="885" y="452" text-anchor="middle" class="text-sans" font-size="10" fill="var(--c-local-green)">本地读写 Markdown / YAML</text>
|
||||
<!-- Resources 原语 -> Local KB (Sync) 读取数据 -->
|
||||
<path d="M 730,400 L 930,400" class="line-sync" marker-end="url(#arrow-sync)" />
|
||||
<text x="830" y="390" text-anchor="middle" class="text-sans" font-size="10" fill="var(--c-local-green)">读取文献卡片与笔记</text>
|
||||
|
||||
<!-- MCP 内部的协同工作流虚线表示 (可选的逻辑联系) -->
|
||||
<path d="M 590,170 L 590,210" class="line-neutral" marker-end="url(#arrow-async)" />
|
||||
<path d="M 590,310 L 590,350" class="line-neutral" marker-end="url(#arrow-async)" />
|
||||
<text x="600" y="195" class="text-sans" font-size="9" fill="var(--c-neutral-gray)">本地上下文共享</text>
|
||||
<path d="M 590,170 L 590,210" class="line-neutral" />
|
||||
<path d="M 590,310 L 590,350" class="line-neutral" />
|
||||
<text x="600" y="195" class="text-sans" font-size="9" fill="var(--c-neutral-gray)">核心引擎共享调度</text>
|
||||
|
||||
</g>
|
||||
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 16 KiB |
|
|
@ -1,320 +1,266 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 1200" width="100%" height="100%">
|
||||
<!--
|
||||
================================================================================
|
||||
图表名称:个人级研究助手智能体 - 开发框架与技术栈架构图
|
||||
文件命名:project-caffeine-tech-stack-framework.svg
|
||||
用途:展示从宿主端到三个核心 MCP Server 的底层技术栈、核心算法、Monorepo工程化及安全交互框架。
|
||||
版本:v1.1.1 (Added Legend)
|
||||
作者:Gitconomy Research-郭晧
|
||||
SPDX-License-Identifier: MIT & CC-BY-SA-4.0
|
||||
创建日期:2026-02-27
|
||||
================================================================================
|
||||
-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 970" width="100%" height="100%">
|
||||
<!--
|
||||
================================================================================
|
||||
图表名称:个人级研究助手智能体 - 开发框架与技术栈架构图
|
||||
文件命名:project-caffeine-tech-stack-framework.svg
|
||||
用途:展示从宿主端到三个核心 MCP Server 的底层技术栈、核心算法、Monorepo工程化及安全交互框架。
|
||||
版本:v2.0.0
|
||||
作者:Gitconomy Research-郭晧
|
||||
SPDX-License-Identifier: MIT & CC-BY-SA-4.0
|
||||
创建日期:2026-02-27
|
||||
更新日期:2026-03-08
|
||||
================================================================================
|
||||
-->
|
||||
|
||||
<!-- Background: Solid White for Git compatibility -->
|
||||
<rect width="100%" height="100%" fill="#FFFFFF" />
|
||||
<!-- Background: Solid White -->
|
||||
<rect width="100%" height="100%" fill="#FFFFFF" />
|
||||
|
||||
<defs>
|
||||
<style>
|
||||
:root {
|
||||
/* 语义色定义 */
|
||||
--c-cloud-blue: #0099FF;
|
||||
--c-cloud-blue-light: rgba(0, 153, 255, 0.08);
|
||||
--c-local-green: #009900;
|
||||
--c-local-green-light: rgba(0, 153, 0, 0.08);
|
||||
--c-risk-amber: #FF991F;
|
||||
--c-risk-amber-light: rgba(255, 153, 31, 0.08);
|
||||
--c-gov-blue: #0052CC;
|
||||
--c-gov-blue-light: rgba(0, 82, 204, 0.04);
|
||||
--c-neutral-gray: #475569;
|
||||
--c-neutral-gray-light: #E2E8F0;
|
||||
}
|
||||
<defs>
|
||||
<!-- CSS 变量与全局样式 -->
|
||||
<style>
|
||||
:root {
|
||||
/* 语义色彩体系 */
|
||||
--c-cloud-blue: #0099FF;
|
||||
--c-cloud-blue-light: rgba(0, 153, 255, 0.08);
|
||||
--c-local-green: #009900;
|
||||
--c-local-green-light: rgba(0, 153, 0, 0.08);
|
||||
--c-risk-amber: #FF991F;
|
||||
--c-risk-amber-light: rgba(255, 153, 31, 0.08);
|
||||
--c-neutral-gray: #475569;
|
||||
--c-neutral-gray-light: #F1F5F9;
|
||||
--c-gov-blue: #0052CC;
|
||||
--c-op-green: #00875A;
|
||||
|
||||
/* 字体降级机制 */
|
||||
.font-sans { font-family: 'Noto Sans', 'Helvetica Neue', Arial, sans-serif; }
|
||||
.font-mono { font-family: 'JetBrains Mono', Consolas, 'Courier New', monospace; }
|
||||
/* 字体系统降级适配 */
|
||||
--font-sans: 'Noto Sans', 'Helvetica Neue', Arial, sans-serif;
|
||||
--font-mono: 'JetBrains Mono', Consolas, 'Courier New', monospace;
|
||||
}
|
||||
|
||||
/* 文本层级 */
|
||||
.title-main { font-size: 16px; font-weight: bold; fill: var(--c-neutral-gray); }
|
||||
.title-sub { font-size: 14px; font-weight: bold; fill: #0F172A; }
|
||||
.tech-badge { font-size: 12px; font-weight: bold; }
|
||||
.desc-text { font-size: 12px; fill: #64748B; }
|
||||
</style>
|
||||
/* 文本工具类 */
|
||||
.font-sans { font-family: var(--font-sans); }
|
||||
.font-mono { font-family: var(--font-mono); }
|
||||
.text-bold { font-weight: bold; }
|
||||
</style>
|
||||
|
||||
<!-- 箭头标记 -->
|
||||
<marker id="arrow-green" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto">
|
||||
<path d="M 0 0 L 8 4 L 0 8 Z" fill="var(--c-local-green)" />
|
||||
</marker>
|
||||
<marker id="arrow-blue" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto">
|
||||
<path d="M 0 0 L 8 4 L 0 8 Z" fill="var(--c-cloud-blue)" />
|
||||
</marker>
|
||||
<marker id="arrow-amber" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto">
|
||||
<path d="M 0 0 L 8 4 L 0 8 Z" fill="var(--c-risk-amber)" />
|
||||
</marker>
|
||||
</defs>
|
||||
<!-- 箭头标记定义 -->
|
||||
<marker id="arrow-green" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="var(--c-local-green)" />
|
||||
</marker>
|
||||
<marker id="arrow-amber" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="var(--c-risk-amber)" />
|
||||
</marker>
|
||||
<marker id="arrow-blue" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="var(--c-cloud-blue)" />
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<!--
|
||||
===========================================================================
|
||||
顶层标题块 (Title Block)
|
||||
===========================================================================
|
||||
-->
|
||||
<g id="title-block" transform="translate(600, 40)">
|
||||
<text y="0" text-anchor="middle" class="font-mono" font-size="12" fill="var(--c-neutral-gray)" letter-spacing="1">FIG-02</text>
|
||||
<text y="30" text-anchor="middle" class="font-sans" font-weight="bold" font-size="24" fill="#000000">Project Caffeine开发框架与技术栈架构图</text>
|
||||
<text y="55" text-anchor="middle" class="font-mono" font-size="14" fill="var(--c-neutral-gray)">架构图 > MCP > 系统技术堆栈 </text>
|
||||
<rect x="-30" y="70" width="60" height="3" fill="var(--c-gov-blue)" />
|
||||
<!-- ==========================================
|
||||
1. 标题区域 (Title Block)
|
||||
=========================================== -->
|
||||
<g id="title-block" transform="translate(500, 40)">
|
||||
<!-- 1. Figure ID -->
|
||||
<text y="0" text-anchor="middle" class="font-mono text-bold" font-size="12" fill="var(--c-neutral-gray)" letter-spacing="1">FIGURE-02</text>
|
||||
|
||||
<!-- 2. Main Title -->
|
||||
<text y="30" text-anchor="middle" class="font-sans text-bold" font-size="24" fill="#000000">Project Caffeine 开发框架与技术栈</text>
|
||||
|
||||
<!-- 3. Breadcrumbs -->
|
||||
<text y="55" text-anchor="middle" class="font-mono" font-size="14" fill="var(--c-neutral-gray)">Framework > Tech Stack > Monolithic Server</text>
|
||||
|
||||
<!-- 4. Context Indicator (Y=70, 高度3,底部为 Y=73。相对于 translate(500, 40),绝对底部 Y=113) -->
|
||||
<rect x="-30" y="70" width="60" height="3" fill="var(--c-local-green)" />
|
||||
</g>
|
||||
|
||||
<!-- ==========================================
|
||||
2. 核心架构主体 (Diagram Content)
|
||||
为了与标题底部(113)精确保持50间距,设定 translate Y 为 163
|
||||
=========================================== -->
|
||||
<g id="diagram-content" transform="translate(0, 163)">
|
||||
|
||||
<!-- 外部客户端 (MCP Client Desktop) 笔记本图标 -->
|
||||
<g id="mcp-client" transform="translate(500, 0)">
|
||||
<!-- 屏幕外框 -->
|
||||
<rect x="-35" y="0" width="70" height="45" rx="4" fill="none" stroke="var(--c-cloud-blue)" stroke-width="3"/>
|
||||
<!-- 屏幕内侧 -->
|
||||
<rect x="-31" y="4" width="62" height="37" rx="1" fill="var(--c-cloud-blue-light)"/>
|
||||
<!-- 笔记本底座 -->
|
||||
<path d="M-45,45 L45,45 L52,52 L-52,52 Z" fill="var(--c-cloud-blue)"/>
|
||||
<!-- 触控板区 -->
|
||||
<rect x="-8" y="47" width="16" height="3" fill="#FFFFFF" opacity="0.5"/>
|
||||
<!-- 键盘区装饰 -->
|
||||
<path d="M-35,46 L35,46 L38,49 L-38,49 Z" fill="#FFFFFF" opacity="0.2"/>
|
||||
|
||||
<text x="0" y="75" text-anchor="middle" class="font-sans text-bold" font-size="14" fill="var(--c-cloud-blue)">Client (Claude Desktop / Cherry Studio)</text>
|
||||
</g>
|
||||
|
||||
<!--
|
||||
===========================================================================
|
||||
Layer 1: 宿主环境与模型层 (Host & LLM Layer)
|
||||
===========================================================================
|
||||
-->
|
||||
<g id="layer-host" transform="translate(100, 140)">
|
||||
<text x="0" y="0" class="font-sans title-main">Layer 1: 宿主应用与大脑层 (Host & Brain)</text>
|
||||
<rect x="0" y="15" width="1000" height="90" rx="8" fill="var(--c-neutral-gray-light)" opacity="0.2" stroke="var(--c-neutral-gray)" stroke-dasharray="4 4" stroke-width="1.5" />
|
||||
<!-- 连接线条:客户端到 MCP Server -->
|
||||
<line x1="500" y1="85" x2="500" y2="135" stroke="var(--c-local-green)" stroke-width="2" marker-end="url(#arrow-green)" />
|
||||
|
||||
<!-- 客户端 -->
|
||||
<rect x="30" y="30" width="220" height="60" rx="6" fill="#FFFFFF" stroke="var(--c-local-green)" stroke-width="2" />
|
||||
<text x="140" y="55" text-anchor="middle" class="font-sans title-sub">MCP Client 宿主联调环境</text>
|
||||
<text x="140" y="75" text-anchor="middle" class="font-mono desc-text">VS Code (MCP 插件) / Claude</text>
|
||||
<!-- 单体服务器容器 (整体向下偏移以容纳客户端) -->
|
||||
<g id="monolithic-server" transform="translate(0, 100)">
|
||||
|
||||
<!-- 双向箭头 -->
|
||||
<line x1="260" y1="60" x2="330" y2="60" stroke="var(--c-neutral-gray)" stroke-width="2" stroke-dasharray="3 3"/>
|
||||
<!-- 单体服务器边界 (Monolithic Boundary) -->
|
||||
<g id="monolithic-boundary">
|
||||
<rect x="60" y="0" width="880" height="570" rx="12" fill="none" stroke="var(--c-local-green)" stroke-width="2" stroke-dasharray="6 4" />
|
||||
|
||||
<!-- 远程模型 (Brain) -->
|
||||
<g transform="translate(400, 60)">
|
||||
<!-- 六边形 Token -->
|
||||
<polygon points="0,-30 26,-15 26,15 0,30 -26,15 -26,-15" fill="var(--c-cloud-blue-light)" stroke="var(--c-cloud-blue)" stroke-width="2" />
|
||||
<text x="40" y="-5" class="font-sans title-sub" fill="var(--c-cloud-blue)">Remote LLM Brain</text>
|
||||
<text x="40" y="15" class="font-mono desc-text">Claude 3.5 / DeepSeek (Context Engine)</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!--
|
||||
===========================================================================
|
||||
Layer 2: 协议总线 (Protocol Bus)
|
||||
===========================================================================
|
||||
-->
|
||||
<g id="layer-protocol" transform="translate(100, 270)">
|
||||
<text x="0" y="0" class="font-sans title-main">Layer 2: 通信协议层 (JSON-RPC 2.0 Bus)</text>
|
||||
<rect x="0" y="15" width="1000" height="40" rx="20" fill="var(--c-gov-blue-light)" stroke="var(--c-gov-blue)" stroke-width="2" />
|
||||
<text x="500" y="40" text-anchor="middle" class="font-mono" font-size="14" font-weight="bold" fill="var(--c-gov-blue)">mcp-protocol: STDIO 协议 (MVP阶段:本地 stdin/stdout 零网络开销)</text>
|
||||
|
||||
<!-- 连接上下层的管线 -->
|
||||
<line x1="210" y1="-35" x2="210" y2="15" stroke="var(--c-local-green)" stroke-width="3" marker-end="url(#arrow-green)" />
|
||||
<line x1="210" y1="55" x2="210" y2="90" stroke="var(--c-local-green)" stroke-width="3" marker-end="url(#arrow-green)" />
|
||||
|
||||
<line x1="500" y1="55" x2="500" y2="90" stroke="var(--c-local-green)" stroke-width="3" marker-end="url(#arrow-green)" />
|
||||
|
||||
<line x1="850" y1="55" x2="850" y2="90" stroke="var(--c-local-green)" stroke-width="3" marker-end="url(#arrow-green)" />
|
||||
</g>
|
||||
|
||||
<!--
|
||||
===========================================================================
|
||||
Layer 3: 核心 MCP Server 集群框架 (Microservices Stack)
|
||||
===========================================================================
|
||||
-->
|
||||
<g id="layer-servers" transform="translate(100, 370)">
|
||||
<text x="0" y="0" class="font-sans title-main">Layer 3: 核心智能体引擎 / MCP Server 集群开发框架</text>
|
||||
|
||||
<!-- 工程化管控边界 (Monorepo) -->
|
||||
<rect x="0" y="15" width="1000" height="360" rx="8" fill="transparent" stroke="var(--c-neutral-gray)" stroke-dasharray="4 4" stroke-width="2" />
|
||||
<rect x="0" y="15" width="1000" height="30" fill="var(--c-neutral-gray-light)" opacity="0.4" rx="8"/>
|
||||
<text x="20" y="35" class="font-mono desc-text" font-weight="bold" fill="var(--c-neutral-gray)">📦 npm Workspaces (Monorepo) - 统一包管理 / 依赖隔离 / 共享 JSON-RPC Schema</text>
|
||||
|
||||
<!-- ================= S1: 文献查询 Server ================= -->
|
||||
<g transform="translate(20, 45)">
|
||||
<rect x="0" y="0" width="300" height="320" rx="8" fill="var(--c-local-green-light)" stroke="var(--c-local-green)" stroke-width="2" />
|
||||
<rect x="0" y="0" width="300" height="40" fill="var(--c-local-green)" rx="8" />
|
||||
<rect x="0" y="20" width="300" height="20" fill="var(--c-local-green)" />
|
||||
<text x="150" y="25" text-anchor="middle" class="font-sans title-sub" fill="#FFFFFF">S1: 文献查询 Server (执行者)</text>
|
||||
|
||||
<!-- Tech Stack Badges (Unified TS/Node) -->
|
||||
<text x="20" y="65" class="font-sans desc-text" font-weight="bold">开发环境与框架</text>
|
||||
<rect x="20" y="75" width="115" height="24" rx="12" fill="#FFFFFF" stroke="var(--c-local-green)" />
|
||||
<text x="77.5" y="91" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-local-green)">Node.js (v20+)</text>
|
||||
<rect x="145" y="75" width="95" height="24" rx="12" fill="#FFFFFF" stroke="var(--c-local-green)" />
|
||||
<text x="192.5" y="91" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-local-green)">@mcp/sdk-ts</text>
|
||||
|
||||
<!-- Core Algorithms -->
|
||||
<text x="20" y="130" class="font-sans desc-text" font-weight="bold">核心组件与算法集成</text>
|
||||
<rect x="20" y="140" width="260" height="40" rx="4" fill="#FFFFFF" stroke="var(--c-risk-amber)" stroke-dasharray="2 2" />
|
||||
<text x="150" y="165" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-risk-amber)">Semantic Chunking 文本切块算法</text>
|
||||
|
||||
<rect x="20" y="190" width="260" height="40" rx="4" fill="#FFFFFF" stroke="var(--c-risk-amber)" stroke-dasharray="2 2" />
|
||||
<text x="150" y="215" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-risk-amber)">Playwright / Firecrawl 网页深抓取</text>
|
||||
|
||||
<!-- APIs -->
|
||||
<text x="20" y="260" class="font-sans desc-text" font-weight="bold">集成外部接口</text>
|
||||
<rect x="20" y="270" width="125" height="24" rx="4" fill="var(--c-cloud-blue-light)" stroke="var(--c-cloud-blue)" />
|
||||
<text x="82.5" y="286" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-cloud-blue)">arXiv API</text>
|
||||
<rect x="155" y="270" width="125" height="24" rx="4" fill="var(--c-cloud-blue-light)" stroke="var(--c-cloud-blue)" />
|
||||
<text x="217.5" y="286" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-cloud-blue)">S.Scholar API</text>
|
||||
<!-- 边界标签 -->
|
||||
<rect x="80" y="-12" width="220" height="24" fill="#FFFFFF" />
|
||||
<text x="90" y="4" class="font-mono text-bold" font-size="14" fill="var(--c-local-green)">📦 Monolithic MCP Server</text>
|
||||
</g>
|
||||
|
||||
<!-- ================= S2: 提示词策略 Server ================= -->
|
||||
<g transform="translate(350, 45)">
|
||||
<rect x="0" y="0" width="300" height="320" rx="8" fill="var(--c-local-green-light)" stroke="var(--c-local-green)" stroke-width="2" />
|
||||
<rect x="0" y="0" width="300" height="40" fill="var(--c-local-green)" rx="8" />
|
||||
<rect x="0" y="20" width="300" height="20" fill="var(--c-local-green)" />
|
||||
<text x="150" y="25" text-anchor="middle" class="font-sans title-sub" fill="#FFFFFF">S2: 提示词策略 Server (军师)</text>
|
||||
<!-- 内部连接线条 (Data Flows) -->
|
||||
<g id="data-flows">
|
||||
<!-- Core to Zod (Sync) -->
|
||||
<line x1="380" y1="190" x2="340" y2="190" stroke="var(--c-local-green)" stroke-width="2" marker-end="url(#arrow-green)" marker-start="url(#arrow-green)" />
|
||||
|
||||
<!-- Tech Stack Badges (Unified TS/Node) -->
|
||||
<text x="20" y="65" class="font-sans desc-text" font-weight="bold">开发环境与框架</text>
|
||||
<rect x="20" y="75" width="115" height="24" rx="12" fill="#FFFFFF" stroke="var(--c-local-green)" />
|
||||
<text x="77.5" y="91" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-local-green)">Node.js (v20+)</text>
|
||||
<rect x="145" y="75" width="95" height="24" rx="12" fill="#FFFFFF" stroke="var(--c-local-green)" />
|
||||
<text x="192.5" y="91" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-local-green)">@mcp/sdk-ts</text>
|
||||
<!-- Core to Axios (Async) -->
|
||||
<line x1="620" y1="190" x2="660" y2="190" stroke="var(--c-risk-amber)" stroke-width="2" stroke-dasharray="4 2" marker-end="url(#arrow-amber)" />
|
||||
|
||||
<!-- Core Algorithms -->
|
||||
<text x="20" y="130" class="font-sans desc-text" font-weight="bold">核心智能体架构与引擎</text>
|
||||
<rect x="20" y="140" width="260" height="40" rx="4" fill="#FFFFFF" stroke="var(--c-gov-blue)" />
|
||||
<text x="150" y="165" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-gov-blue)">Persona Matrix 角色化矩阵</text>
|
||||
<!-- Core to Runtime -->
|
||||
<line x1="500" y1="230" x2="500" y2="260" stroke="var(--c-local-green)" stroke-width="2" marker-end="url(#arrow-green)" />
|
||||
|
||||
<rect x="20" y="190" width="260" height="40" rx="4" fill="#FFFFFF" stroke="var(--c-gov-blue)" />
|
||||
<text x="150" y="215" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-gov-blue)">Exploration State (防死循环账本)</text>
|
||||
|
||||
<!-- Prompts -->
|
||||
<text x="20" y="260" class="font-sans desc-text" font-weight="bold">注入的静态思维框架 (Prompts)</text>
|
||||
<rect x="20" y="270" width="80" height="24" rx="4" fill="#F1F5F9" stroke="var(--c-neutral-gray)" />
|
||||
<text x="60" y="286" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-neutral-gray)">5W3H</text>
|
||||
<rect x="110" y="270" width="80" height="24" rx="4" fill="#F1F5F9" stroke="var(--c-neutral-gray)" />
|
||||
<text x="150" y="286" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-neutral-gray)">SCQA</text>
|
||||
<rect x="200" y="270" width="80" height="24" rx="4" fill="#F1F5F9" stroke="var(--c-neutral-gray)" />
|
||||
<text x="240" y="286" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-neutral-gray)">SWOT</text>
|
||||
<!-- Axios to External API (Cloud) -->
|
||||
<line x1="900" y1="190" x2="950" y2="190" stroke="var(--c-cloud-blue)" stroke-width="2" stroke-dasharray="4 2" marker-end="url(#arrow-blue)" />
|
||||
<text x="960" y="194" class="font-mono" font-size="12" fill="var(--c-cloud-blue)">Academic APIs</text>
|
||||
</g>
|
||||
|
||||
<!-- ================= S3: CoT 多步推理 Server ================= -->
|
||||
<g transform="translate(680, 45)">
|
||||
<rect x="0" y="0" width="300" height="320" rx="8" fill="var(--c-local-green-light)" stroke="var(--c-local-green)" stroke-width="2" />
|
||||
<rect x="0" y="0" width="300" height="40" fill="var(--c-local-green)" rx="8" />
|
||||
<rect x="0" y="20" width="300" height="20" fill="var(--c-local-green)" />
|
||||
<text x="150" y="25" text-anchor="middle" class="font-sans title-sub" fill="#FFFFFF">S3: CoT 推理 Server (分析师)</text>
|
||||
<!-- ================== 层级 1:协议与网络层 (Y=40) ================== -->
|
||||
<!-- MCP SDK -->
|
||||
<g id="card-mcp" transform="translate(380, 40)">
|
||||
<rect width="240" height="80" rx="6" fill="var(--c-cloud-blue-light)" stroke="var(--c-cloud-blue)" stroke-width="1.5" />
|
||||
<rect width="6" height="80" rx="3" fill="var(--c-cloud-blue)" />
|
||||
<text x="20" y="24" class="font-sans text-bold" font-size="14" fill="var(--c-cloud-blue)">协议层 (Protocol)</text>
|
||||
<text x="20" y="46" class="font-mono text-bold" font-size="16" fill="#1e293b">MCP SDK</text>
|
||||
<text x="20" y="66" class="font-sans" font-size="12" fill="var(--c-neutral-gray)">@modelcontextprotocol/sdk (stdio/SSE)</text>
|
||||
</g>
|
||||
|
||||
<!-- Tech Stack Badges (Unified TS/Node) -->
|
||||
<text x="20" y="65" class="font-sans desc-text" font-weight="bold">开发环境与框架</text>
|
||||
<rect x="20" y="75" width="115" height="24" rx="12" fill="#FFFFFF" stroke="var(--c-local-green)" />
|
||||
<text x="77.5" y="91" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-local-green)">Node.js (v20+)</text>
|
||||
<rect x="145" y="75" width="95" height="24" rx="12" fill="#FFFFFF" stroke="var(--c-local-green)" />
|
||||
<text x="192.5" y="91" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-local-green)">@mcp/sdk-ts</text>
|
||||
<!-- ================== 层级 2:核心业务层 (Y=150) ================== -->
|
||||
<!-- Zod -->
|
||||
<g id="card-zod" transform="translate(100, 150)">
|
||||
<rect width="240" height="80" rx="6" fill="var(--c-local-green-light)" stroke="var(--c-local-green)" stroke-width="1.5" />
|
||||
<rect width="6" height="80" rx="3" fill="var(--c-local-green)" />
|
||||
<text x="20" y="24" class="font-sans text-bold" font-size="14" fill="var(--c-local-green)">校验工具 (Validation)</text>
|
||||
<text x="20" y="46" class="font-mono text-bold" font-size="16" fill="#1e293b">Zod</text>
|
||||
<text x="20" y="66" class="font-sans" font-size="12" fill="var(--c-neutral-gray)">运行时类型校验与参数安全验证</text>
|
||||
</g>
|
||||
|
||||
<!-- Core Algorithms -->
|
||||
<text x="20" y="130" class="font-sans desc-text" font-weight="bold">质量把控与数据组装逻辑</text>
|
||||
<rect x="20" y="140" width="260" height="40" rx="4" fill="#FFFFFF" stroke="var(--c-gov-blue)" />
|
||||
<text x="150" y="165" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-gov-blue)">引文密度强制校验 (Citation Check)</text>
|
||||
<!-- TypeScript -->
|
||||
<g id="card-ts" transform="translate(380, 150)">
|
||||
<rect width="240" height="80" rx="6" fill="var(--c-local-green-light)" stroke="var(--c-local-green)" stroke-width="2" />
|
||||
<rect width="6" height="80" rx="3" fill="var(--c-local-green)" />
|
||||
<text x="20" y="24" class="font-sans text-bold" font-size="14" fill="var(--c-local-green)">核心语言 (Core Language)</text>
|
||||
<text x="20" y="46" class="font-mono text-bold" font-size="16" fill="#1e293b">TypeScript</text>
|
||||
<text x="20" y="66" class="font-sans" font-size="12" fill="var(--c-neutral-gray)">所有服务逻辑与控制流强制使用</text>
|
||||
</g>
|
||||
|
||||
<rect x="20" y="190" width="260" height="40" rx="4" fill="#FFFFFF" stroke="var(--c-gov-blue)" />
|
||||
<text x="150" y="215" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-gov-blue)">双轨制落盘编译器 (JSON-to-MD)</text>
|
||||
<!-- axios -->
|
||||
<g id="card-axios" transform="translate(660, 150)">
|
||||
<rect width="240" height="80" rx="6" fill="var(--c-risk-amber-light)" stroke="var(--c-risk-amber)" stroke-width="1.5" />
|
||||
<rect width="6" height="80" rx="3" fill="var(--c-risk-amber)" />
|
||||
<text x="20" y="24" class="font-sans text-bold" font-size="14" fill="var(--c-risk-amber)">HTTP 客户端 (Network I/O)</text>
|
||||
<text x="20" y="46" class="font-mono text-bold" font-size="16" fill="#1e293b">axios</text>
|
||||
<text x="20" y="66" class="font-sans" font-size="12" fill="var(--c-neutral-gray)">封装学术 API,支持重试与速率限制</text>
|
||||
</g>
|
||||
|
||||
<!-- Output Formats -->
|
||||
<text x="20" y="260" class="font-sans desc-text" font-weight="bold">标准数据协议输出</text>
|
||||
<rect x="20" y="270" width="120" height="24" rx="4" fill="#F1F5F9" stroke="var(--c-neutral-gray)" />
|
||||
<text x="80" y="286" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-neutral-gray)">Markdown 解析</text>
|
||||
<rect x="150" y="270" width="130" height="24" rx="4" fill="#F1F5F9" stroke="var(--c-neutral-gray)" />
|
||||
<text x="215" y="286" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-neutral-gray)">YAML Frontmatter</text>
|
||||
<!-- ================== 层级 3:运行环境层 (Y=260) ================== -->
|
||||
<!-- Node.js -->
|
||||
<g id="card-node" transform="translate(380, 260)">
|
||||
<rect width="240" height="80" rx="6" fill="var(--c-local-green-light)" stroke="var(--c-local-green)" stroke-width="1.5" />
|
||||
<rect width="6" height="80" rx="3" fill="var(--c-local-green)" />
|
||||
<text x="20" y="24" class="font-sans text-bold" font-size="14" fill="var(--c-local-green)">运行环境 (Runtime)</text>
|
||||
<text x="20" y="46" class="font-mono text-bold" font-size="16" fill="#1e293b">Node.js LTS v20+</text>
|
||||
<text x="20" y="66" class="font-sans" font-size="12" fill="var(--c-neutral-gray)">单进程异步非阻塞 I/O 引擎</text>
|
||||
</g>
|
||||
|
||||
<!-- 分隔线 -->
|
||||
<line x1="80" y1="365" x2="920" y2="365" stroke="var(--c-neutral-gray)" stroke-width="1" stroke-dasharray="2 4" />
|
||||
<text x="100" y="360" class="font-mono" font-size="12" fill="var(--c-neutral-gray)">DevTools & Engineering Infrastructure</text>
|
||||
|
||||
<!-- ================== 层级 4:工程化基础工具 (Y=390) ================== -->
|
||||
<!-- Winston -->
|
||||
<g id="card-log" transform="translate(100, 390)">
|
||||
<rect width="240" height="80" rx="6" fill="var(--c-neutral-gray-light)" stroke="var(--c-neutral-gray)" stroke-width="1" />
|
||||
<rect width="6" height="80" rx="3" fill="var(--c-neutral-gray)" />
|
||||
<text x="20" y="24" class="font-sans text-bold" font-size="14" fill="var(--c-neutral-gray)">日志工具 (Logging)</text>
|
||||
<text x="20" y="46" class="font-mono text-bold" font-size="16" fill="#1e293b">winston / log4js</text>
|
||||
<text x="20" y="66" class="font-sans" font-size="12" fill="#64748b">生产级结构化日志记录与追踪</text>
|
||||
</g>
|
||||
|
||||
<!-- VS Code -->
|
||||
<g id="card-vscode" transform="translate(380, 390)">
|
||||
<rect width="240" height="80" rx="6" fill="var(--c-neutral-gray-light)" stroke="var(--c-neutral-gray)" stroke-width="1" />
|
||||
<rect width="6" height="80" rx="3" fill="var(--c-neutral-gray)" />
|
||||
<text x="20" y="24" class="font-sans text-bold" font-size="14" fill="var(--c-neutral-gray)">调试工具 (Debugging)</text>
|
||||
<text x="20" y="46" class="font-mono text-bold" font-size="16" fill="#1e293b">VS Code (--inspect)</text>
|
||||
<text x="20" y="66" class="font-sans" font-size="12" fill="#64748b">统一入口进程挂载与源码级断点</text>
|
||||
</g>
|
||||
|
||||
<!-- Jest + k6 -->
|
||||
<g id="card-test" transform="translate(660, 390)">
|
||||
<rect width="240" height="80" rx="6" fill="var(--c-neutral-gray-light)" stroke="var(--c-neutral-gray)" stroke-width="1" />
|
||||
<rect width="6" height="80" rx="3" fill="var(--c-neutral-gray)" />
|
||||
<text x="20" y="24" class="font-sans text-bold" font-size="14" fill="var(--c-neutral-gray)">测试工具 (Testing)</text>
|
||||
<text x="20" y="46" class="font-mono text-bold" font-size="16" fill="#1e293b">Jest + k6</text>
|
||||
<text x="20" y="66" class="font-sans" font-size="12" fill="#64748b">服务单元测试与接口负载压测</text>
|
||||
</g>
|
||||
|
||||
<!-- ================== 层级 5:包与仓库管理 (Y=500) ================== -->
|
||||
<!-- npm -->
|
||||
<g id="card-npm" transform="translate(380, 480)">
|
||||
<rect width="240" height="60" rx="6" fill="var(--c-neutral-gray-light)" stroke="var(--c-neutral-gray)" stroke-width="1" />
|
||||
<text x="120" y="25" text-anchor="middle" class="font-sans text-bold" font-size="14" fill="var(--c-neutral-gray)">包管理 (Package Config)</text>
|
||||
<text x="120" y="45" text-anchor="middle" class="font-mono text-bold" font-size="14" fill="#1e293b">npm (单一 package.json)</text>
|
||||
</g>
|
||||
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- ==========================================
|
||||
3. 底部图例与注释块 (Legend & Key Block)
|
||||
向下调整布局至 Y=890 适应新高度
|
||||
=========================================== -->
|
||||
<g id="legend" transform="translate(500, 890)">
|
||||
<!-- 统一的半透明背景容器 -->
|
||||
<rect x="-420" y="-16" width="840" height="40" rx="4" fill="rgba(255, 255, 255, 0.9)" stroke="var(--c-neutral-gray)" stroke-width="0.5" />
|
||||
|
||||
<g transform="translate(-400, 0)">
|
||||
<!-- 图例引导文本 -->
|
||||
<text x="0" y="8" class="font-sans text-bold" font-size="12" fill="var(--c-neutral-gray)">图例说明:</text>
|
||||
|
||||
<!-- 图例项 1: Cloud/External -->
|
||||
<g transform="translate(80, 0)">
|
||||
<rect x="0" y="-2" width="12" height="12" rx="2" fill="var(--c-cloud-blue)" />
|
||||
<text x="18" y="8" class="font-mono" font-size="12" fill="var(--c-neutral-gray)">接口/外部协议 (Interface)</text>
|
||||
</g>
|
||||
|
||||
<!-- 图例项 2: Local/Core -->
|
||||
<g transform="translate(260, 0)">
|
||||
<rect x="0" y="-2" width="12" height="12" rx="2" fill="var(--c-local-green)" />
|
||||
<text x="18" y="8" class="font-mono" font-size="12" fill="var(--c-neutral-gray)">核心引擎/计算 (Core/Sync)</text>
|
||||
</g>
|
||||
|
||||
<!-- 图例项 3: Network I/O -->
|
||||
<g transform="translate(440, 0)">
|
||||
<rect x="0" y="-2" width="12" height="12" rx="2" fill="var(--c-risk-amber)" />
|
||||
<text x="18" y="8" class="font-mono" font-size="12" fill="var(--c-neutral-gray)">网络/异步流 (Async I/O)</text>
|
||||
</g>
|
||||
|
||||
<!-- 图例项 4: Engineering -->
|
||||
<g transform="translate(620, 0)">
|
||||
<rect x="0" y="-2" width="12" height="12" rx="2" fill="var(--c-neutral-gray)" />
|
||||
<text x="18" y="8" class="font-mono" font-size="12" fill="var(--c-neutral-gray)">基建/工具 (DevTools)</text>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!--
|
||||
===========================================================================
|
||||
Layer 4: 物理存储与图谱层 (PKM Storage Layer)
|
||||
===========================================================================
|
||||
-->
|
||||
<g id="layer-pkm" transform="translate(100, 790)">
|
||||
<text x="0" y="0" class="font-sans title-main">Layer 4: 数据存储与个人图谱化 (PKM Data Persistence)</text>
|
||||
|
||||
<line x1="500" y1="-30" x2="500" y2="15" stroke="var(--c-local-green)" stroke-width="3" stroke-dasharray="4 4" marker-end="url(#arrow-green)" />
|
||||
|
||||
<rect x="0" y="30" width="1000" height="80" rx="8" fill="var(--c-local-green-light)" stroke="var(--c-local-green)" stroke-width="2" />
|
||||
|
||||
<!-- Storage Cylinder Icon -->
|
||||
<path d="M 40 55 A 25 10 0 1 0 90 55 V 85 A 25 10 0 1 1 40 85 Z" fill="#FFFFFF" stroke="var(--c-local-green)" stroke-width="2"/>
|
||||
<ellipse cx="65" cy="55" rx="25" ry="10" fill="#FFFFFF" stroke="var(--c-local-green)" stroke-width="2"/>
|
||||
|
||||
<text x="120" y="65" class="font-sans title-sub">本地个人知识库 (Local Vault)</text>
|
||||
<text x="120" y="85" class="font-mono desc-text">Obsidian / Logseq Graph System</text>
|
||||
|
||||
<!-- Data Spec Badges -->
|
||||
<rect x="420" y="55" width="180" height="30" rx="4" fill="#FFFFFF" stroke="var(--c-gov-blue)" />
|
||||
<text x="510" y="75" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-gov-blue)">双向链接 [[文献名称]] 编排</text>
|
||||
|
||||
<rect x="620" y="55" width="160" height="30" rx="4" fill="#FFFFFF" stroke="var(--c-gov-blue)" />
|
||||
<text x="700" y="75" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-gov-blue)">自动化文献目录索引池</text>
|
||||
</g>
|
||||
|
||||
<!--
|
||||
===========================================================================
|
||||
Layer 5: 工程化与运维安全 (Engineering & Security)
|
||||
===========================================================================
|
||||
-->
|
||||
<g id="layer-engineering" transform="translate(100, 930)">
|
||||
<text x="0" y="0" class="font-sans title-main">Layer 5: 架构运维管控与安全基线 (Engineering & Security)</text>
|
||||
<rect x="0" y="15" width="1000" height="60" rx="8" fill="var(--c-gov-blue-light)" stroke="var(--c-gov-blue)" stroke-width="2" />
|
||||
|
||||
<!-- Security Badge 1 -->
|
||||
<rect x="30" y="30" width="280" height="30" rx="4" fill="#FFFFFF" stroke="var(--c-gov-blue)" />
|
||||
<text x="170" y="50" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-gov-blue)">零信任架构 (不可信负载防范)</text>
|
||||
|
||||
<!-- Security Badge 2 -->
|
||||
<rect x="340" y="30" width="280" height="30" rx="4" fill="#FFFFFF" stroke="var(--c-gov-blue)" />
|
||||
<text x="480" y="50" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-gov-blue)">.env.example 隔离敏感凭证</text>
|
||||
|
||||
<!-- Performance Badge -->
|
||||
<rect x="650" y="30" width="320" height="30" rx="4" fill="#FFFFFF" stroke="var(--c-gov-blue)" />
|
||||
<text x="810" y="50" text-anchor="middle" class="font-mono tech-badge" fill="var(--c-gov-blue)">异步非阻塞事件流 (高并发处理能力)</text>
|
||||
</g>
|
||||
|
||||
<!--
|
||||
===========================================================================
|
||||
底部图例与注释块 (Legend & Key Block)
|
||||
===========================================================================
|
||||
-->
|
||||
<g id="legend" transform="translate(600, 1050)">
|
||||
<rect x="-440" y="-16" width="880" height="40" rx="4" fill="rgba(255, 255, 255, 0.9)" stroke="var(--c-neutral-gray)" stroke-width="0.5"/>
|
||||
|
||||
<g transform="translate(-420, 0)">
|
||||
<text x="0" y="8" class="font-sans" font-size="13" font-weight="bold" fill="var(--c-neutral-gray)">语义图例说明:</text>
|
||||
|
||||
<!-- 云端/外部 -->
|
||||
<g transform="translate(110, 0)">
|
||||
<rect x="-6" y="-6" width="12" height="12" rx="2" fill="var(--c-cloud-blue)" />
|
||||
<text x="12" y="8" class="font-sans" font-size="12" fill="var(--c-neutral-gray)">云端服务 / 外部资源</text>
|
||||
</g>
|
||||
|
||||
<!-- 本地/核心 -->
|
||||
<g transform="translate(265, 0)">
|
||||
<rect x="-6" y="-6" width="12" height="12" rx="2" fill="var(--c-local-green)" />
|
||||
<text x="12" y="8" class="font-sans" font-size="12" fill="var(--c-neutral-gray)">本地服务 / 核心引擎</text>
|
||||
</g>
|
||||
|
||||
<!-- 风险/高负载 -->
|
||||
<g transform="translate(420, 0)">
|
||||
<rect x="-6" y="-6" width="12" height="12" rx="2" fill="var(--c-risk-amber)" />
|
||||
<text x="12" y="8" class="font-sans" font-size="12" fill="var(--c-neutral-gray)">高频计算 / 网络爬取</text>
|
||||
</g>
|
||||
|
||||
<!-- 治理/安全 -->
|
||||
<g transform="translate(575, 0)">
|
||||
<rect x="-6" y="-6" width="12" height="12" rx="2" fill="var(--c-gov-blue)" />
|
||||
<text x="12" y="8" class="font-sans" font-size="12" fill="var(--c-neutral-gray)">治理规范 / 安全控制</text>
|
||||
</g>
|
||||
|
||||
<!-- 逻辑边界 -->
|
||||
<g transform="translate(730, 0)">
|
||||
<rect x="-8" y="-8" width="16" height="16" rx="2" fill="transparent" stroke="var(--c-neutral-gray)" stroke-dasharray="2 2" stroke-width="1.5" />
|
||||
<text x="14" y="8" class="font-sans" font-size="12" fill="var(--c-neutral-gray)">逻辑边界 / 虚拟容器</text>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!--
|
||||
===========================================================================
|
||||
底部许可声明
|
||||
===========================================================================
|
||||
-->
|
||||
<text x="600" y="1140" text-anchor="middle" class="font-sans" font-size="11" fill="var(--c-neutral-gray)">
|
||||
本作品采用 CC-BY-SA 4.0 进行许可,© 2025-2026 Gitconomy Research社区
|
||||
<!-- ==========================================
|
||||
4. 底部许可声明 (License)
|
||||
向下调整至 Y=950
|
||||
=========================================== -->
|
||||
<g id="license" transform="translate(500, 950)">
|
||||
<text text-anchor="middle" class="font-sans" font-size="10" fill="#94a3b8">
|
||||
本作品采用 CC-BY-SA 4.0 进行许可,© 2025-2026 Gitconomy Research
|
||||
</text>
|
||||
</g>
|
||||
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 14 KiB |
|
|
@ -1,12 +1,14 @@
|
|||
<!--
|
||||
---
|
||||
title: "Project Caffeine系统开发指南"
|
||||
type: "Development Guide"
|
||||
file: "project-caffeine-development-framewok-guide.md"
|
||||
description: "个人级研报智能体系统架构与开发规范指南"
|
||||
version: "v0.1.0 (Arabica)"
|
||||
author: "Gitconomy Research-郭晧"
|
||||
title: Project Caffeine系统开发指南
|
||||
description: 个人级研报智能体系统架构与开发规范指南
|
||||
type: Development Guide
|
||||
file: project-caffeine-development-framewok-guide.md
|
||||
version: v1.1.0 (Arabica)
|
||||
author: Gitconomy Research-郭晧
|
||||
date: 2026-02-28
|
||||
last-update: 2026-03-08
|
||||
update-description: 更新系统架构、工具链以及其他相关的设计说明
|
||||
tags:
|
||||
- Project Caffeine
|
||||
- MCP
|
||||
|
|
@ -14,8 +16,8 @@ tags:
|
|||
- 系统架构
|
||||
- PKM
|
||||
- 知识管理
|
||||
license: "CC BY-SA 4.0"
|
||||
status: "Active"
|
||||
license: CC BY-SA 4.0
|
||||
status: Active
|
||||
---
|
||||
-->
|
||||
# Project Caffeine系统开发指南
|
||||
|
|
@ -52,144 +54,40 @@ MCP与传统的应用程序编程接口(API)以及消息队列等技术在
|
|||
|
||||

|
||||
|
||||
MCP的系统总体架构采用严格分层的客户端-服务端(Client-Server)模型,旨在实现传输机制与数据处理逻辑的彻底解耦。该架构由三大核心组件构成:
|
||||
### 3.1 系统分层架构设计
|
||||
|
||||
1. **宿主应用程序(Host Application)**:直接与用户交互并承载大语言模型的环境(如Claude Desktop、Cursor IDE等)。
|
||||
系统由四个核心物理与逻辑区域组成,通过标准协议进行通信:
|
||||
|
||||
2. **MCP客户端(MCP Client)**:无缝嵌入在宿主应用程序中,负责维持与外部服务端的连接,执行能力协商,并将宿主的意图翻译为符合MCP规范的协议消息。
|
||||
- **控制器层**:`promptsController`、`toolsController`、`resourcesController` 接收 MCP 请求,进行参数校验(Zod),调用对应的服务模块,并格式化响应。保持薄控制器原则,业务逻辑全部下沉到服务层。
|
||||
- **服务层**:按功能拆分为 `literature`、`prompt`、`cot`、`resource` 四个子目录,每个子目录下包含该功能相关的多个服务文件。服务之间可通过导入直接调用,例如 `promptService` 可以调用 `intentService` 生成检索词,再调用 `literatureService` 执行检索。
|
||||
- **模型层**:集中存放 TypeScript 类型定义和 Zod Schema,包括文献格式、框架定义、工具参数等,确保类型安全并在整个项目内共享。
|
||||
- **工具层**:提供跨模块复用的工具函数,如 HTTP 客户端、YAML 生成、路径安全校验、日志等。
|
||||
|
||||
3. **MCP服务端(MCP Server)**:轻量级的外部程序,专门负责连接特定的数据源或执行特定的计算任务,通过标准接口向AI应用暴露上下文和工具。
|
||||
### 3.2 MCP 三大核心原语分工
|
||||
|
||||
系统框架严格遵循 MCP 规范,将功能划分为 Tools、Prompts 和 Resources 三大部分:
|
||||
|
||||
**数据流管理**:用户发起请求 $\rightarrow$ AI模型推理决定调用外部能力 $\rightarrow$ MCP客户端打包 JSON-RPC 请求 $\rightarrow$ MCP服务端执行逻辑并返回标准 JSON 结构 $\rightarrow$ 客户端将上下文注入模型 $\rightarrow$ 模型生成最终响应。
|
||||
|
||||
---
|
||||
|
||||
## 4. 数据标准化
|
||||
|
||||
系统全面采用 JSON 格式作为基础数据承载体,并依赖 **JSON-RPC 2.0** 协议规范来管理消息交换。MCP规范设定了三种核心的系统原语:
|
||||
|
||||
- **工具(Tools)**:允许AI应用执行具体操作(如文件读写、API调用)。服务端通过 `tools/list` 暴露工具元数据,包含严谨的 `inputSchema`。
|
||||
|
||||
- **资源(Resources)**:提供被动上下文信息的数据源。通过 `resources/list` 发现,通过 `resources/read` 提取内容。
|
||||
|
||||
- **提示词(Prompts)**:作为可复用的模板,帮助模型构造标准化的交互结构。通过 `prompts/list` 和 `prompts/get` 进行流转。
|
||||
|
||||
|
||||
为保证数据兼容性,系统需对通用 JSON 进行深度扩展(如类似 JSON-LD 或结构化 BibTeX 的标准),确保输入模式和生成数据结构在底层LLM切换时保持绝对一致性。
|
||||
|
||||
---
|
||||
|
||||
## 5. 模型定义与行为规范
|
||||
|
||||
在复杂的智能体架构中,模型的行为必须遵循预设的结构与功能规范。
|
||||
|
||||
- **结构与功能定义**:系统通常通过“提示词策略服务端”注入静态思维框架(如SWOT分析、思维链CoT推理),强制模型按照标准逻辑结构进行逐步推理。可加入学术质量控制逻辑(如强制引文密度验证)。
|
||||
|
||||
- **采样(Sampling)机制**:MCP引入的反向调用机制。允许服务端主动请求LLM的推理能力来指导后续操作(如调用 `sampling/createMessage`)。任何涉及采样的递归行为都必须受到严格管控,并在应用层确保人在回路的二次确认机制。
|
||||
|
||||
---
|
||||
|
||||
## 6. 通信协议设计
|
||||
|
||||
根据系统应用场景的不同,通信协议的选择主要集中在两种官方支持的标准之上:
|
||||
|
||||
|**传输协议**|**运行机制**|**安全性与效率特征**|**适用场景**|
|
||||
|**原语名称**|**核心职责 (Core Responsibility)**|**典型工具与指令示例**|**所属 Server 角色**|
|
||||
|---|---|---|---|
|
||||
|**STDIO**|利用同一台机器上本地进程间的 stdin 和 stdout 管道进行直接通信。|零网络传输开销,不经过网卡,提供最优延迟和效率。无需复杂加密握手。|本地集成的桌面应用程序(如Claude Desktop, Cursor)及容器内部的Sidecar服务。|
|
||||
|**HTTP + SSE**|客户端通过 HTTP POST 发送请求,服务端通过 Server-Sent Events 流式下发响应。|依赖 HTTPS/TLS 保障安全,支持标准 Token 认证。效率依赖网络带宽与延迟优化。|云端部署的远程MCP服务端、微服务架构及跨物理机器访问的连接器。|
|
||||
|**Tools (工具)**|**动作执行者**:暴露给模型的主动操作,负责与外部学术 API 通信或执行本地文件 I/O。|`search_academic_literature`, `save_to_local_vault`|**S1: 执行者 (Executor)**|
|
||||
|**Prompts (提示词)**|**策略军师**:提供结构化的思维框架模板,用于指导大模型进行意图拆解与深度推理。|`5W3H`, `SCQA`, `5 Whys`, `generate_search_queries`|**S2: 军师 (Strategist)**|
|
||||
|**Resources (资源)**|**数据管家**:被动的静态上下文数据源,允许模型以只读方式挂载本地知识库内容。|`vault://local_literature/`, `note://local/`|**S1/S3: 数据管家/分析师**|
|
||||
|
||||
所有通信均封装在 JSON-RPC 2.0 信封内,通过初始化握手(`initialize`)进行双向的能力协商,并支持动态状态更新(如 `notifications/tools/list_changed`)。
|
||||
### 3.3原语协同逻辑要点
|
||||
|
||||
拓扑图展示了系统内部的协同工作流:
|
||||
|
||||
1. **动态策略驱动**:当用户输入模糊主题时,系统首先调用 **Prompts 原语** 中的 `generate_search_queries` 将意图降维并转化为专业检索词。
|
||||
2. **物理链路执行**:大模型根据拆解后的 Query,通过 **Tools 原语** 调用外部 API(如 arXiv)并执行“双轨制落盘”,将 JSON 数据转化为带有 YAML 元数据的 Markdown 文件。
|
||||
3. **知识闭环构建**:在递归深挖阶段,模型通过 **Resources 原语** 回读已保存在本地知识库中的文献卡片,确保后续的推理基于已获知的“先验知识(Learnings)”。
|
||||
|
||||
---
|
||||
|
||||
## 7. 上下文管理机制
|
||||
## 4. 系统工作流
|
||||
|
||||
构建科学的上下文管理机制是MCP系统设计的核心要务,以应对大模型的上下文窗口限制。
|
||||
*图1-2:Project Caffeine MCP 系统工作流逻辑示意图*
|
||||
|
||||
- **生命周期管理**:连接瞬间注入环境配置;多轮调用中动态更新;任务完成时显式终止销毁,防止内存泄漏。
|
||||
|
||||
- **LRU 缓存策略**:对于高频读写的中间结果,采用最近最少使用缓存策略。结合**哈希映射**(保证查找时间复杂度为 $\mathcal{O}(1)$)和**双向链表**(管理生命周期与数据逐出)。
|
||||
|
||||
- **语义分块(Semantic Chunking)**:对于长篇文档,服务端在本地完成文本解析与向量切割,仅将统计学上高度相关的片段异步同步给客户端,避免 Token 极度消耗。
|
||||
|
||||
---
|
||||
|
||||
## 8. 安全性和权限管理
|
||||
|
||||
协议遵循**零信任架构原则**,默认将AI生成的指令视为不可信负载。
|
||||
|
||||
- **加密与验证**:远程连接强制 HTTPS/TLS 1.2+。官方推荐基于 **OAuth 2.1** 的授权流,并采用如 `mcp:read_database` 形式的作用域限定防止令牌滥用。
|
||||
|
||||
- **Roots(根目录)机制**:从系统底层阻断越权访问和路径遍历漏洞。由宿主应用在初始化时传递明确的 URI 列表划定“活动沙箱”。服务端目录访问控制系统会依据 Roots 边界直接拒绝越权请求。
|
||||
|
||||
---
|
||||
|
||||
## 9. 性能优化和可扩展性
|
||||
|
||||
- **处理效率**:MCP服务端必须引入全面的异步处理模型(如 asyncio 或 Node.js 非阻塞事件流),结合代理层 LRU 缓存拦截重复请求。
|
||||
|
||||
- **网络传输**:利用 MCP JSON-RPC 协议中原生的数组分片流式返回能力,将长文本数据切片持续推送,降低首字节时间(TTFB)。
|
||||
|
||||
- **架构扩展**:引入负载均衡与水平扩展技术。云端部署可采用 Docker + Kubernetes (HPA)。针对海量异构数据,应部署独立域的多个无状态MCP服务端并结合聚合网关进行调度。
|
||||
|
||||
---
|
||||
|
||||
## 10. 系统维护和更新
|
||||
|
||||
- **版本控制策略**:当服务端发生破坏性更新时,应当在 `tools/list` 响应中并行提供新旧两套 Schema,直至宿主模型的 Prompt 被完全升级。
|
||||
|
||||
- **热部署(Hot Deployment)**:协议原生支持热更新。服务端逻辑变动时可发送 `list_changed` 通知,客户端会在后台透明重新拉取最新状态。
|
||||
|
||||
- **容错和恢复**:建立异常捕获与重试机制。利用本地轻量级数据库(如SQLite)作为事务日志,在崩溃恢复后回放状态。
|
||||
|
||||
---
|
||||
|
||||
## 11. 系统部署与运维
|
||||
|
||||
- **部署方式**:Docker 容器是封装事实标准。可结合标准化 AI 伪制品清单格式(如 `ara.json`)实现公有云自动化编排与部署。
|
||||
|
||||
- **可观测性**:部署 Prometheus + Grafana 进行系统指标监控。引入专门针对大模型和 MCP 工具监控的分析平台(如 Agnost AI)深度剖析调用频次与失败率。
|
||||
|
||||
- **日志管理**:使用 ELK 协议栈收集 JSON-RPC 往来报文,用于追踪错误栈、发现模型幻觉及优化提示词。
|
||||
|
||||
---
|
||||
|
||||
## 12. 开发工具链和环境配置
|
||||
|
||||
*图:1-2:Project Caffeine开发框架与技术栈架构图*
|
||||
|
||||

|
||||
|
||||
为确保系统的高并发处理能力与协议严谨性,Project Caffeine 采用以下核心开发框架与技术标准:
|
||||
|
||||
* **核心语言与运行环境**:采用 TypeScript 与 Node.js (LTS v20+)。MCP服务端必须引入全面的异步处理模型(如 Node.js 非阻塞事件流)以应对高吞吐量的数据解析。
|
||||
* **MCP 协议与 SDK**:统一使用官方针对 TypeScript 提供的标准 SDK,深度封装底层 JSON-RPC 2.0 报文解析与状态机管理。
|
||||
* **工程化与 Monorepo**:采用原生 npm Workspaces 进行包管理,在根目录统一管控共享的 JSON-RPC Schema 与多个微服务子包,实现依赖隔离与跨服务快速编译。
|
||||
* **通信传输层 (MVP 阶段)**:采用 STDIO 协议,利用同一台机器上本地进程间的 stdin 和 stdout 管道进行直接通信,无需复杂加密握手,实现零网络传输开销。
|
||||
* **集成开发环境 (IDE)**:采用 Visual Studio Code (VS Code) 作为核心开发工具。需配合安装相关的 MCP 扩展插件,支持在编写代码时直接进行对话联调与协议协议测试。
|
||||
* **安全与环境管控**:协议遵循零信任架构原则,默认将AI生成的指令视为不可信负载。敏感凭证严禁硬编码,必须通过 `.env.example` 模板化并在运行环境中安全注入。
|
||||
|
||||
---
|
||||
|
||||
## 13. 测试与调试
|
||||
|
||||
绝不能仅停留在“感觉测试(Vibe-Testing)”层面,必须实施严密的自动化分级测试体系:
|
||||
|
||||
|**测试层级**|**测试框架与工具推荐**|**核心目标与执行策略**|
|
||||
|---|---|---|
|
||||
|**单元测试**|Mocha, Chai, PyTest, Jest|脱离LLM环境,确保模块独立功能绝对正确(覆盖率>80%),验证是否能正确拒绝非法输入。|
|
||||
|**集成测试**|MCP Inspector, mcpjam|测试协议合规性。模拟握手与协商,通过浏览器 UI 手动验证或自动化集成交互测试。|
|
||||
|**性能测试**|k6 Load Testing|评估高并发请求下的生存能力,验证 P99 响应时间及内存泄漏监控。|
|
||||
|**安全与调试**|Chrome DevTools, OWASP|实施 SQL/命令注入及路径遍历安全审计。提供内存检查与日志调试功能定位异常。|
|
||||
|
||||
---
|
||||
|
||||
## 14. 系统工作流
|
||||
|
||||
*图1-3:Project Caffeine MCP 系统工作流逻辑示意图*
|
||||
|
||||

|
||||

|
||||
|
||||
以下步骤构成了整个系统的工作流程,从用户输入研究主题开始,到生成并优化最终的深度研究报告,整个过程依靠 **MCP协议** 和多台 **MCP Server** 的协同工作,确保高效处理复杂的研究任务并生成有价值的报告。
|
||||
|
||||
|
|
@ -240,17 +138,230 @@ MCP的系统总体架构采用严格分层的客户端-服务端(Client-Server
|
|||
- 对提示词或任务描述的优化建议。
|
||||
系统会根据反馈调整模型的推理过程或检索策略,以改进后续任务的处理效果。
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 15. 文档与支持
|
||||
## 5. 开发工具链和环境配置
|
||||
|
||||
优秀的MCP系统配备多维度文档:
|
||||
### 5.1 技术栈与工程化基础
|
||||
|
||||
- **开发者文档**:架构图、API契约、安装配置指南及常见问题解答(FAQ)。
|
||||
*图:1-3:Project Caffeine开发框架与技术栈架构图*
|
||||
|
||||
- **AI 模型文档**:`tools/list` 响应中的 `description` 字段必须经过精细的提示词工程打磨,消除歧义。利用 `prompts/list` 暴露预置最佳实践,提供少样本(Few-shot)学习参考。
|
||||

|
||||
|
||||
| 层次 | 技术选型 |
|
||||
| ------------ | ---------------------------------------------------------- |
|
||||
| **核心语言** | TypeScript(所有核心功能强制使用) |
|
||||
| **运行环境** | Node.js LTS v20+(异步非阻塞 I/O) |
|
||||
| **协议层** | MCP SDK(`@modelcontextprotocol/sdk`),支持 `stdio` 与 `SSE` 传输 |
|
||||
| **包管理** | npm(单体仓库,单一 `package.json`) |
|
||||
| **校验工具** | Zod(运行时类型校验与参数验证) |
|
||||
| **HTTP 客户端** | axios(封装学术 API 调用,支持重试与速率限制) |
|
||||
| **日志工具** | winston / log4js(生产级日志记录) |
|
||||
| **测试工具** | Jest + k6(单元测试与负载压测) |
|
||||
| **调试工具** | VS Code 断点调试(通过 `--inspect` 挂载) |
|
||||
### 5.2 模块划分与代码组织
|
||||
|
||||
```
|
||||
project-caffeine/
|
||||
├── src/
|
||||
│ ├── controllers/ # 请求控制器
|
||||
│ │ ├── promptsController.ts # 处理 prompts/list, prompts/get
|
||||
│ │ ├── toolsController.ts # 处理 tools/list, tools/call
|
||||
│ │ └── resourcesController.ts # 处理 resources/list, resources/read
|
||||
│ ├── services/ # 核心业务逻辑(按功能模块划分)
|
||||
│ │ ├── literature/ # 文献查询模块(原 S1)
|
||||
│ │ │ ├── literatureService.ts # 学术 API 调用、聚合
|
||||
│ │ │ ├── storageService.ts # 文献转 Markdown、文件写入
|
||||
│ │ │ └── index.ts
|
||||
│ │ ├── prompt/ # 提示词策略模块(原 S2)
|
||||
│ │ │ ├── promptService.ts # 框架加载、消息组装
|
||||
│ │ │ ├── intentService.ts # 意图拆解、检索词生成
|
||||
│ │ │ └── index.ts
|
||||
│ │ ├── cot/ # CoT 推理模块(原 S3,规划中)
|
||||
│ │ │ ├── synthesisService.ts # 文献合成、报告生成
|
||||
│ │ │ ├── qualityService.ts # 引用校验、时效检查
|
||||
│ │ │ └── index.ts
|
||||
│ │ └── resource/ # 本地资源管理(通用)
|
||||
│ │ └── resourceService.ts # 笔记列表、读取、保存
|
||||
│ ├── models/ # 数据模型与 Zod 校验
|
||||
│ │ ├── schemas.ts # 统一校验 Schema
|
||||
│ │ ├── literatureSchema.ts # 文献 JSON 定义
|
||||
│ │ └── frameworks/ # 思维框架 JSON 定义
|
||||
│ │ ├── 5w3h.json
|
||||
│ │ ├── scqa.json
|
||||
│ │ └── ...
|
||||
│ ├── utils/ # 通用工具函数
|
||||
│ │ ├── apiClients.ts # HTTP 客户端封装
|
||||
│ │ ├── yamlHelper.ts # YAML Frontmatter 生成
|
||||
│ │ ├── pathSafety.ts # 路径安全校验
|
||||
│ │ └── logger.ts # 日志工具
|
||||
│ └── app.ts # 入口文件:初始化 MCP Server,注册原语
|
||||
├── config/
|
||||
│ └── config.ts # 环境配置加载
|
||||
├── .env.example # 环境变量示例
|
||||
├── package.json # 项目依赖
|
||||
├── tsconfig.json # TypeScript 配置
|
||||
└── README.md # 项目说明
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 系统设计补充
|
||||
### 6.1 数据标准化
|
||||
|
||||
系统全面采用 JSON 格式作为基础数据承载体,并依赖 **JSON-RPC 2.0** 协议规范来管理消息交换。MCP规范设定了三种核心的系统原语:
|
||||
|
||||
- **工具(Tools)**:允许AI应用执行具体操作(如文件读写、API调用)。服务端通过 `tools/list` 暴露工具元数据,包含严谨的 `inputSchema`。
|
||||
|
||||
- **资源(Resources)**:提供被动上下文信息的数据源。通过 `resources/list` 发现,通过 `resources/read` 提取内容。
|
||||
|
||||
- **提示词(Prompts)**:作为可复用的模板,帮助模型构造标准化的交互结构。通过 `prompts/list` 和 `prompts/get` 进行流转。
|
||||
|
||||
|
||||
为保证数据兼容性,系统需对通用 JSON 进行深度扩展(如类似 JSON-LD 或结构化 BibTeX 的标准),确保输入模式和生成数据结构在底层LLM切换时保持绝对一致性。
|
||||
|
||||
### 6.2 通信协议设计
|
||||
|
||||
根据系统应用场景的不同,通信协议的选择主要集中在两种官方支持的标准之上:
|
||||
|
||||
|**传输协议**|**运行机制**|**安全性与效率特征**|**适用场景**|
|
||||
|---|---|---|---|
|
||||
|**STDIO**|利用同一台机器上本地进程间的 stdin 和 stdout 管道进行直接通信。|零网络传输开销,不经过网卡,提供最优延迟和效率。无需复杂加密握手。|本地集成的桌面应用程序(如Claude Desktop, Cursor)及容器内部的Sidecar服务。|
|
||||
|**HTTP + SSE**|客户端通过 HTTP POST 发送请求,服务端通过 Server-Sent Events 流式下发响应。|依赖 HTTPS/TLS 保障安全,支持标准 Token 认证。效率依赖网络带宽与延迟优化。|云端部署的远程MCP服务端、微服务架构及跨物理机器访问的连接器。|
|
||||
|
||||
所有通信均封装在 JSON-RPC 2.0 信封内,通过初始化握手(`initialize`)进行双向的能力协商,并支持动态状态更新(如 `notifications/tools/list_changed`)。
|
||||
|
||||
### 6.3 上下文管理机制
|
||||
|
||||
构建科学的上下文管理机制是MCP系统设计的核心要务,以应对大模型的上下文窗口限制。
|
||||
|
||||
- **生命周期管理**:连接瞬间注入环境配置;多轮调用中动态更新;任务完成时显式终止销毁,防止内存泄漏。
|
||||
|
||||
- **LRU 缓存策略**:对于高频读写的中间结果,采用最近最少使用缓存策略。结合**哈希映射**(保证查找时间复杂度为 $\mathcal{O}(1)$)和**双向链表**(管理生命周期与数据逐出)。
|
||||
|
||||
- **语义分块(Semantic Chunking)**:对于长篇文档,服务端在本地完成文本解析与向量切割,仅将统计学上高度相关的片段异步同步给客户端,避免 Token 极度消耗。
|
||||
|
||||
### 6.4 安全性和权限管理
|
||||
|
||||
协议遵循**零信任架构原则**,默认将AI生成的指令视为不可信负载。
|
||||
|
||||
- **加密与验证**:远程连接强制 HTTPS/TLS 1.2+。官方推荐基于 **OAuth 2.1** 的授权流,并采用如 `mcp:read_database` 形式的作用域限定防止令牌滥用。
|
||||
|
||||
- **Roots(根目录)机制**:从系统底层阻断越权访问和路径遍历漏洞。由宿主应用在初始化时传递明确的 URI 列表划定“活动沙箱”。服务端目录访问控制系统会依据 Roots 边界直接拒绝越权请求。
|
||||
|
||||
### 6.5性能优化和可扩展性
|
||||
|
||||
- **处理效率**:MCP服务端必须引入全面的异步处理模型(如 asyncio 或 Node.js 非阻塞事件流),结合代理层 LRU 缓存拦截重复请求。
|
||||
|
||||
- **网络传输**:利用 MCP JSON-RPC 协议中原生的数组分片流式返回能力,将长文本数据切片持续推送,降低首字节时间(TTFB)。
|
||||
|
||||
- **架构扩展**:引入负载均衡与水平扩展技术。云端部署可采用 Docker + Kubernetes (HPA)。针对海量异构数据,应部署独立域的多个无状态MCP服务端并结合聚合网关进行调度。
|
||||
|
||||
### 6.6 系统维护和更新
|
||||
|
||||
- **版本控制策略**:当服务端发生破坏性更新时,应当在 `tools/list` 响应中并行提供新旧两套 Schema,直至宿主模型的 Prompt 被完全升级。
|
||||
|
||||
- **热部署(Hot Deployment)**:协议原生支持热更新。服务端逻辑变动时可发送 `list_changed` 通知,客户端会在后台透明重新拉取最新状态。
|
||||
|
||||
- **容错和恢复**:建立异常捕获与重试机制。利用本地轻量级数据库(如SQLite)作为事务日志,在崩溃恢复后回放状态。
|
||||
|
||||
### 6.7 系统部署与运维
|
||||
|
||||
- **部署方式**:Docker 容器是封装事实标准。可结合标准化 AI 伪制品清单格式(如 `ara.json`)实现公有云自动化编排与部署。
|
||||
|
||||
- **可观测性**:部署 Prometheus + Grafana 进行系统指标监控。引入专门针对大模型和 MCP 工具监控的分析平台(如 Agnost AI)深度剖析调用频次与失败率。
|
||||
|
||||
- **日志管理**:使用 ELK 协议栈收集 JSON-RPC 往来报文,用于追踪错误栈、发现模型幻觉及优化提示词。
|
||||
|
||||
---
|
||||
|
||||
## 7. 调试与测试
|
||||
|
||||
### 7.1 调试配置
|
||||
|
||||
单体架构下调试更加简便:
|
||||
- 在 VS Code 中配置 `launch.json`,直接启动 `src/app.ts`,并附加 `--inspect` 参数,即可实现源码级断点。
|
||||
- 由于所有代码在一个进程中,可以统一设置断点,无需跨服务追踪。
|
||||
- 调试日志统一输出到 `stderr`(通过 `console.error` 或日志库),避免干扰 MCP 的 `stdout` 通信。
|
||||
|
||||
### 7.2 测试策略
|
||||
|
||||
| **测试层级** | **测试框架与工具推荐** | **核心目标与执行策略** |
|
||||
| --------- | ------------------------- | -------------------------------------------- |
|
||||
| **单元测试** | Mocha, Chai, PyTest, Jest | 脱离LLM环境,确保模块独立功能绝对正确(覆盖率>80%),验证是否能正确拒绝非法输入。 |
|
||||
| **集成测试** | MCP Inspector, mcpjam | 测试协议合规性。模拟握手与协商,通过浏览器 UI 手动验证或自动化集成交互测试。 |
|
||||
| **性能测试** | k6 Load Testing | 评估高并发请求下的生存能力,验证 P99 响应时间及内存泄漏监控。 |
|
||||
| **安全与调试** | Chrome DevTools, OWASP | 实施 SQL/命令注入及路径遍历安全审计。提供内存检查与日志调试功能定位异常。 |
|
||||
|
||||
_详见[Project Caffeine 项目代码测试规范指南](./../guides/project-caffeine-code-testing-specification-guide.md)_
|
||||
|
||||
---
|
||||
|
||||
## 8. 部署与运行
|
||||
|
||||
### 8.1 本地部署
|
||||
|
||||
```bash
|
||||
git clone <repository>
|
||||
cd project-caffeine
|
||||
npm install
|
||||
cp .env.example .env # 编辑配置文件,填入 API 密钥和本地知识库路径
|
||||
npm run build # 编译 TypeScript
|
||||
```
|
||||
|
||||
在支持 MCP 的客户端(如 Cherry Studio)中导入以下json配置:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"Project Caffeine": {
|
||||
"isActive": true,
|
||||
"name": "Project Caffeine",
|
||||
"type": "stdio",
|
||||
"description": "",
|
||||
"baseUrl": "",
|
||||
"command": "node",
|
||||
"args": [
|
||||
"--inspect=9229",
|
||||
"/home/wguo/Downloads/Project-Caffeine/projects/arabica/sprint2/dist/app.js"
|
||||
],
|
||||
"env": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
_`app.ts`文件需要根据每版本输入完整的绝对路径_
|
||||
|
||||
### 8.2 远程部署(SSE 模式)
|
||||
|
||||
若需部署为远程服务,可在 `app.ts` 中增加对 `SSE` 传输的支持,通过环境变量切换传输方式。部署时需配置反向代理(如 Nginx)和负载均衡,并确保安全认证(如 JWT)。
|
||||
|
||||
---
|
||||
|
||||
## 9. 编码规范与文档规范
|
||||
|
||||
### 9.1 编码规范
|
||||
|
||||
- **命名**:变量/函数使用 `camelCase`,类/接口使用 `PascalCase`,常量使用 `UPPER_SNAKE_CASE`,文件/目录使用 `kebab-case`。
|
||||
- **缩进**:2 个空格,禁止 Tab。
|
||||
- **注释**:所有模块、类、复杂函数必须使用 JSDoc 注释。
|
||||
- **类型**:优先使用 TypeScript 严格模式,所有 API 输入输出均需 Zod 校验。
|
||||
- **错误处理**:服务层抛出具体错误,控制器捕获后返回标准 MCP 错误响应(包含 `isError: true`)。
|
||||
- **安全**:文件操作必须进行路径遍历校验(使用 `path.resolve` + 检查前缀,或 `path.relative` 验证)。
|
||||
|
||||
_详见 [Project Caffeine 代码编写规范指南](./../guides/project-caffeine-coding-specification-guide.md)_
|
||||
|
||||
### 9.2 文档规范
|
||||
|
||||
- **YAML Frontmatter**:所有 Markdown 文档顶部必须包含元数据(标题、描述、版本、作者、日期、标签、许可证)。
|
||||
- **文档类型**:README、设计文档、开发指南、用户指南、API 文档、更新日志等分类存放于 `docs/` 目录。
|
||||
- **图表**:核心架构图必须使用可 diff 的 SVG(嵌入 Markdown),并添加纯白背景防止深色模式反色。
|
||||
- **写作风格**:中立客观,避免翻译腔,使用主动语态,段落首句为中心句。
|
||||
|
||||
_详见[Project Caffeine文档编写规范指南](./../guides/project-caffeine-documentation-specification-guide.md)_
|
||||
|
||||
- **社区支持**:建立 Git 仓库、论坛及 Wiki,共享复杂实现方案并组织可用性培训。
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -0,0 +1,254 @@
|
|||
<!--
|
||||
---
|
||||
title: Project Caffeine 项目代码测试规范指南
|
||||
description: 为保证 Project Caffeine 提示词策略 MCP Server 的高可用性与健壮性制定的代码测试规范,涵盖单元测试、集成测试及相关最佳实践。
|
||||
type: Testing Guide
|
||||
version: v1.0.0
|
||||
file: project-caffeine-code-testing-specification-guide.md
|
||||
author: Gitconomy Research-郭晧
|
||||
date: 2026-03-07
|
||||
tags:
|
||||
- Project Caffeine
|
||||
- Testing
|
||||
- Jest
|
||||
- MCP Server
|
||||
- Quality Assurance
|
||||
license: CC BY-SA 4.0
|
||||
status: Active
|
||||
---
|
||||
-->
|
||||
# Project Caffeine 项目代码测试规范指南
|
||||
|
||||
为保证 Project Caffeine 提示词策略 MCP Server 的高可用性、健壮性及代码质量,特制定本代码测试规范。本指南主要适用于开发阶段的自动化测试(单元测试与集成测试)及相关最佳实践。
|
||||
## 1. 测试工具栈建议
|
||||
|
||||
本项目推荐使用 **Jest** 作为核心测试框架,搭配 `ts-jest` 无缝支持 TypeScript 原生测试。
|
||||
|
||||
- **测试框架**: `jest`, `@types/jest`
|
||||
|
||||
- **TypeScript 支持**: `ts-jest`
|
||||
|
||||
- **Mock 工具**: Jest 内置 Mock 功能 (`jest.mock()`)
|
||||
|
||||
- **手动/交互式测试**: MCP Inspector
|
||||
|
||||
|
||||
_(如尚未安装,可通过 `npm install --save-dev jest ts-jest @types/jest` 安装,并使用 `npx ts-jest config:init` 初始化配置。)_
|
||||
|
||||
```bash
|
||||
npm install --save-dev jest ts-jest @types/jest
|
||||
npx ts-jest config:init
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 测试分层策略
|
||||
|
||||
基于项目的经典三层架构(接入层 `app.ts` -> 控制层 `controllers` -> 服务层 `services`),测试策略分为以下三个层级:
|
||||
|
||||
### 2.1 服务层单元测试 (Service Unit Tests)
|
||||
|
||||
- **目标**: 验证纯业务逻辑的正确性,这是测试的重中之重。
|
||||
|
||||
- **重点对象 (以 v0.1.1 为例)**: `intentService.ts` (意图拆解算法), `resourceService.ts` (文件操作), `promptService.ts` (JSON 解析与组装)。
|
||||
|
||||
- **策略**: 对于纯函数(例如 v0.1.1 中的 `generateSearchQueries`),提供不同的输入(边界值、空值、正常值)验证输出;对于涉及文件系统的操作(例如 `readObsidianNote`),**必须使用 Mock** 拦截原生 `fs` 调用,严禁在单元测试中真实读写物理磁盘。
|
||||
|
||||
### 2.2 控制层单元测试 (Controller Unit Tests)
|
||||
|
||||
- **目标**: 验证参数的 Zod 校验逻辑以及请求路由分发的正确性。
|
||||
|
||||
- **重点对象 (以 v0.1.1 为例)**: `promptsController.ts`, `toolsController.ts`。
|
||||
|
||||
- **策略**: Mock 掉底层的 Service 函数。重点测试:当传入非法参数时(如不带 `.md` 后缀的文件名),Zod Schema 是否能正确拦截并返回 `isError: true` 和标准的 MCP 错误响应格式。
|
||||
|
||||
|
||||
### 2.3 集成/E2E测试 (Integration Tests)
|
||||
|
||||
- **目标**: 验证 MCP Server 与客户端之间的 STDIO 协议通信及功能全链路。
|
||||
|
||||
- **策略**: 自动化层面投入较少,主要依赖 **MCP Inspector** 进行人工或半自动化点检。
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 3. 测试文件命名与目录结构
|
||||
|
||||
- **目录位置**: 测试文件应与被测试的源码文件同级,统一放在同级的 `__tests__` 文件夹中,或直接与源码文件同级。
|
||||
|
||||
- **命名规范**: 以 `.test.ts` 或 `.spec.ts` 结尾。
|
||||
|
||||
- 例如(以 v0.1.1 为例):被测文件 `src/services/intentService.ts`,测试文件应为 `src/services/__tests__/intentService.test.ts`。
|
||||
|
||||
---
|
||||
|
||||
## 4. 单元测试编写规范 (编写范例)
|
||||
|
||||
### 4.1 纯粹逻辑的测试 (无副作用)
|
||||
|
||||
针对 `intentService.ts` 中的 `generateSearchQueries` 函数,采用 **Given-When-Then** (假设-当-那么) 模式或清晰的用例描述:
|
||||
|
||||
```
|
||||
// src/services/__tests__/intentService.test.ts
|
||||
import { generateSearchQueries } from '../intentService';
|
||||
|
||||
describe('intentService -> generateSearchQueries', () => {
|
||||
it('当输入常规查询时,应该正确分词并返回3-5个检索词', () => {
|
||||
const result = generateSearchQueries('新能源汽车电池回收技术');
|
||||
expect(result.length).toBeGreaterThanOrEqual(3);
|
||||
expect(result.length).toBeLessThanOrEqual(5);
|
||||
expect(result).toContain('新能源汽车电池回收技术 相关研究'); // 验证补全逻辑
|
||||
});
|
||||
|
||||
it('当输入为空字符串时,应该返回默认的后备检索词', () => {
|
||||
const result = generateSearchQueries(' ');
|
||||
expect(result).toEqual(['通用研究主题']);
|
||||
});
|
||||
|
||||
it('当输入带有大量标点符号时,应该正确清洗', () => {
|
||||
const result = generateSearchQueries('AI芯片,市场趋势?2026;');
|
||||
// 验证标点符号是否被正确视为空格分割
|
||||
expect(result).toContain('AI芯片');
|
||||
expect(result).toContain('市场趋势');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### 4.2 依赖外部系统(文件系统 IO)的 Mock 测试
|
||||
|
||||
针对 `resourceService.ts`,绝不允许污染真实的 `OBSIDIAN_VAULT_PATH`。
|
||||
|
||||
```
|
||||
// src/services/__tests__/resourceService.test.ts
|
||||
import { readObsidianNote, saveNote } from '../resourceService';
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
|
||||
// 全局 Mock fs 模块
|
||||
jest.mock('fs/promises');
|
||||
|
||||
describe('resourceService', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks(); // 每个用例前清除 mock 状态
|
||||
});
|
||||
|
||||
describe('readObsidianNote', () => {
|
||||
it('当发生路径遍历攻击时 (../),应该抛出安全警告', async () => {
|
||||
await expect(readObsidianNote('../../etc/passwd')).rejects.toThrow('安全警告:越权访问拦截!');
|
||||
});
|
||||
|
||||
it('当读取合法路径时,应该返回文件内容', async () => {
|
||||
// 模拟 fs.readFile 返回成功
|
||||
(fs.readFile as jest.Mock).mockResolvedValueOnce('# Mock Note Content');
|
||||
|
||||
const content = await readObsidianNote('test.md');
|
||||
expect(content).toBe('# Mock Note Content');
|
||||
expect(fs.readFile).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### 4.3 控制层的数据校验 (Zod Schema) 测试
|
||||
|
||||
针对 `toolsController.ts`,验证 Zod 的拦截机制与 MCP 响应格式是否统一:
|
||||
|
||||
```
|
||||
// src/controllers/__tests__/toolsController.test.ts
|
||||
import { handleToolCall } from '../toolsController';
|
||||
import * as resourceService from '../../services/resourceService';
|
||||
|
||||
jest.mock('../../services/resourceService');
|
||||
|
||||
describe('toolsController -> handleToolCall', () => {
|
||||
it('当调用 save_note 且文件名缺失 .md 后缀时,应该返回 Zod 拦截的错误响应', async () => {
|
||||
const params = { filename: 'invalidName', content: 'test' };
|
||||
const result = await handleToolCall('save_note', params);
|
||||
|
||||
expect(result.isError).toBe(true);
|
||||
expect(result.content[0].type).toBe('text');
|
||||
expect(result.content[0].text).toContain('文件名必须以 .md 结尾'); // 验证 Zod 自定义错误消息
|
||||
});
|
||||
|
||||
it('当调用未知的工具名时,应该返回未知工具的错误响应', async () => {
|
||||
const result = await handleToolCall('unknown_tool', {});
|
||||
expect(result.isError).toBe(true);
|
||||
expect(result.content[0].text).toContain('未知工具: unknown_tool');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 测试覆盖率标准
|
||||
|
||||
为了保障核心功能的稳定性,项目 CI/CD 流程中应设置覆盖率门槛:
|
||||
|
||||
- **Service 层**: 语句覆盖率 (Statements) 不低于 **85%**。
|
||||
|
||||
- **Controller 层**: 分支覆盖率 (Branches) 不低于 **80%**。
|
||||
|
||||
- 配置命令:`jest --coverage`。
|
||||
|
||||
---
|
||||
|
||||
## 6. 测试编写的红线规定
|
||||
|
||||
1. **禁止真实 I/O**: 单元测试中严禁发起真实的磁盘读写或网络请求。必须使用 Mock。
|
||||
|
||||
2. **独立性**: 每个 `it` 用例必须相互独立。禁止用例 A 的运行结果作为用例 B 的依赖。必须善用 `beforeEach` 和 `afterEach` 清理状态(例如 `jest.clearAllMocks()`)。
|
||||
|
||||
3. **断言明确**: 不要只断言 `expect(result).toBeDefined()`。必须断言具体的数据结构或内容,例如 MCP 要求的 `content: [{ type: "text", text: "..." }]` 结构。
|
||||
|
||||
4. **涵盖异常流**: 测试不仅要覆盖“Happy Path”(快乐路径,即正常执行的流程),**必须**编写针对 `throw Error` 和 Zod `isError: true` 的异常分支测试(Unhappy Path)。
|
||||
|
||||
|
||||
**附注**: 编写完测试后,建议将 `npm run test` 和 `npm run test:coverage` 配置入 `package.json` 的 `scripts` 中,以便日常开发与构建流集成。
|
||||
|
||||
---
|
||||
|
||||
## 7. 测试执行与查看指引
|
||||
|
||||
项目的 `package.json` 中已集成了标准化的测试脚本指令,开发者可以直接在终端使用以下命令执行测试:
|
||||
|
||||
### 7.1 运行所有测试
|
||||
|
||||
```bash
|
||||
npm run test
|
||||
```
|
||||
|
||||
- **功能说明**:Jest 会自动全局扫描您的项目,找到所有符合命名规范的测试文件(如 `*.test.ts` 或 `*.spec.ts`),并串行/并行执行其中的所有用例。
|
||||
|
||||
- **输出查看**:终端会实时输出每个用例的测试结果,绿色的 `PASS` 表示通过,红色的 `FAIL` 表示失败及具体的报错堆栈。
|
||||
|
||||
|
||||
### 7.2 运行测试并生成代码覆盖率报告
|
||||
|
||||
```bash
|
||||
npm run test:coverage
|
||||
```
|
||||
|
||||
- **功能说明**:在跑完所有测试用例的同时,额外收集代码被测试用例“触碰”的情况,借此衡量测试的完备度。
|
||||
|
||||
- **输出查看**:
|
||||
|
||||
- **终端报表**:在控制台底部会输出一张表格,直观展示当前项目的“语句 (Stmts)”、“分支 (Branch)”、“函数 (Funcs)”和“行 (Lines)”覆盖率比例。
|
||||
|
||||
- **网页视图**:命令执行完毕后,项目根目录会自动生成一个 `coverage/` 文件夹。您可以通过浏览器打开 `coverage/lcov-report/index.html`,以直观的界面逐行查看哪些代码片段处于“漏测”状态。
|
||||
|
||||
|
||||
### 7.3 运行指定文件的测试
|
||||
|
||||
如果您正在专注开发某个模块(如意图拆解服务),不需要每次都全量执行测试,可在命令后追加关键词或文件名进行过滤:
|
||||
|
||||
```bash
|
||||
npm run test -- intentService
|
||||
```
|
||||
|
||||
- **功能说明**:Jest 将仅匹配文件名中包含 `intentService` 的测试文件并执行,从而大幅提高 TDD(测试驱动开发)环节下的反馈效率。
|
||||
|
||||
---
|
||||
|
||||
## 许可声明
|
||||
|
||||
本文档采用 **知识共享署名--相同方式共享 4.0 国际许可协议 (CC BY--SA 4.0)** 进行许可,© 2025-2026 Gitconomy Research.
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
<!--
|
||||
---
|
||||
title: "Project Caffeine 代码编写规范指南"
|
||||
description: "为 Project Caffeine 项目开发人员提供一致的编码风格、系统架构指引和最佳实践的规范文档"
|
||||
type: "Guide"
|
||||
version: "v1.0.0"
|
||||
file: project-caffeine-coding-specification-guide.md
|
||||
author: "Gitconomy Research-郭晧"
|
||||
date: 2026-03-02
|
||||
tags:
|
||||
- Project Caffeine
|
||||
- 代码规范
|
||||
- TypeScript
|
||||
- MCP
|
||||
- Node.js
|
||||
license: "CC BY-SA 4.0"
|
||||
status: "Active"
|
||||
---
|
||||
-->
|
||||
# Project Caffeine 代码编写指南
|
||||
## 1. 目的与概述
|
||||
|
||||
本代码编写规范旨在为 **Project Caffeine** 项目的开发人员提供一致的编码风格和最佳实践,确保代码的可读性、可维护性和团队协作的效率。遵循这些规范将有助于提升代码质量、减少错误并优化开发过程。
|
||||
|
||||
---
|
||||
|
||||
## 2. 技术栈与工程化基础
|
||||
|
||||
所有代码必须遵循清晰、简洁、结构化的原则,并基于以下核心技术栈构建:
|
||||
|
||||
- **核心语言**:所有核心功能必须使用 TypeScript 编写。强烈建议优先使用 TypeScript,以利用其静态类型检查减少运行时错误。
|
||||
- **运行环境**:服务端必须使用 Node.js (LTS v20+)。需确保高效的异步执行和非阻塞 I/O 操作。
|
||||
- **工程架构**:采用原生 npm Workspaces 进行 Monorepo(单体仓库)包管理。必须在根目录统一管控共享的 JSON-RPC Schema 与多个微服务子包。
|
||||
|
||||
---
|
||||
|
||||
## 3. 代码风格与命名规则
|
||||
|
||||
## 3.1 格式与排版
|
||||
|
||||
- **缩进**:使用 **2 个空格**作为缩进(严禁使用 Tab)。
|
||||
- **行长度**:每行代码的字符数应不超过 **120 个字符**,避免横向滚动条,便于阅读和维护。
|
||||
- **文件结尾**:每个文件的结尾必须保留一个空行。
|
||||
- **函数复杂度**:长函数或复杂逻辑必须拆分为多个函数,确保每个函数的职责单一。
|
||||
|
||||
## 3.2 命名规范
|
||||
|
||||
- **变量与函数**:使用 `camelCase`(小驼峰命名法),如 `userProfile`, `generateReport`。
|
||||
- **类与接口**:使用 `PascalCase`(大驼峰命名法),如 `UserService`, `ReportGenerator`。
|
||||
- **常量**:使用 `UPPER_SNAKE_CASE`(全大写蛇形命名法),如 `MAX_RETRIES`, `API_TIMEOUT`。
|
||||
- **文件与目录**:使用 `kebab-case`(短横线命名法),如 `user-service.ts`, `data-fetcher.ts`。
|
||||
|
||||
---
|
||||
|
||||
## 4. 注释与文档
|
||||
|
||||
每个模块、类和复杂函数必须包含文件头部文档或块级注释,简要说明其作用。必须使用标准的 **JSDoc** 格式来解释功能、参数和返回值:
|
||||
|
||||
```TypeScript
|
||||
/**
|
||||
* 计算两个数的和。
|
||||
* @param {number} a - 第一个加数。
|
||||
* @param {number} b - 第二个加数。
|
||||
* @returns {number} - 返回两数之和。
|
||||
*/
|
||||
function add(a: number, b: number): number {
|
||||
return a + b;
|
||||
}
|
||||
```
|
||||
---
|
||||
|
||||
## 5. 开源许可证声明
|
||||
|
||||
**所有源代码文件的顶部必须包含开源相关的版权信息与 SPDX 格式的许可证标识。** 本项目源代码统一采用 **MIT 许可证**。在每个 `.ts` 或者 `。js` 文件头部必须添加如下块级注释:
|
||||
|
||||
```plaintext
|
||||
/**
|
||||
* Project Caffeine
|
||||
* Copyright (c) 2025-2026 Gitconomy Research
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
```
|
||||
为了保障代码的可追溯性并尊重每一位开发者的劳动成果,当新的团队成员或开源社区开发者对该文件进行了**实质性修改或重构**时,应当在头部的 `Contributors` 列表中追加自己的信息。
|
||||
|
||||
在每个 `.ts` 或 `.js` 文件头部,必须添加如下块级注释模板:
|
||||
|
||||
|
||||
```plaintext
|
||||
/**
|
||||
* Project Caffeine
|
||||
* Copyright (c) 2025-2026 Gitconomy Research
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Contributors:
|
||||
* - 郭晧 <guohao@gitconomy.org> (Initial Author)
|
||||
* - [新贡献者姓名/ID] <[联系邮箱]> ([简述贡献内容,例如:重构了 LRU 缓存模块 / 2026-03])
|
||||
* - [其他贡献者姓名] <[联系邮箱]> ([例如:修复了 MCP 握手超时的 Bug / 2026-04])
|
||||
*/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 目录结构与模块化解耦
|
||||
|
||||
系统应保持高度的模块化,功能模块应独立且彼此解耦。建议采用以下标准目录结构:
|
||||
|
||||
- `/src/controllers`:控制器,处理传入的 JSON-RPC 或 HTTP 请求。
|
||||
- `/src/services`:服务层,包含核心的 MCP 业务逻辑。
|
||||
- `/src/models`:数据模型与 Schema 定义。
|
||||
- `/src/routes`:路由定义(适用于 HTTP+SSE 传输模式)。
|
||||
- `/src/utils`:跨模块共享的工具函数。
|
||||
- `/config`:环境与系统配置文件。
|
||||
- `/tests`:单元测试与集成测试文件。
|
||||
|
||||
---
|
||||
|
||||
## 7. MCP 协议与核心原语
|
||||
|
||||
系统全面采用 JSON 格式作为基础数据承载体,并依赖 **JSON-RPC 2.0** 协议规范来管理消息交换。
|
||||
|
||||
- **传输层**:本地服务必须采用 STDIO 协议进行无网络开销的直接通信。云端部署则采用 HTTP + SSE 模式。
|
||||
- **Tools (工具)**:暴露给 LLM 的操作必须通过 `tools/list` 注册,并包含严谨的 `inputSchema`。
|
||||
- **Resources (资源)**:被动的静态上下文数据需通过 `resources/list` 和 `resources/read` 暴露。
|
||||
- **Prompts (提示词)**:作为复用模板,通过 `prompts/list` 暴露,指导模型构造标准化交互结构。
|
||||
|
||||
---
|
||||
|
||||
## 8. 异常处理与日志记录
|
||||
|
||||
## 7.1 错误捕获与响应
|
||||
|
||||
- 在业务逻辑中,必须使用 `try-catch` 语句来捕获和处理可能的错误。
|
||||
- 对于 HTTP/SSE 传输层,遇到预期错误需返回适当的 HTTP 状态码(如 400 错误请求、404 未找到资源)。对于不可预见的严重异常,返回 500 错误。
|
||||
- 在 MCP 协议层,所有错误必须被标准封装为 JSON-RPC 错误对象,并返回给客户端。
|
||||
|
||||
## 7.2 日志系统
|
||||
|
||||
- 必须使用成熟的日志库(如 `winston` 或 `log4js`)来记录事件。
|
||||
- 明确日志级别:`info`(正常操作流程)、`warn`(潜在问题)、`error`(系统异常)。
|
||||
- 记录重要事件(如用户操作、API 调用),以便追踪、审计和发现模型幻觉。
|
||||
- **安全红线**:绝对禁止在生产环境日志中记录敏感信息(如用户密码、API 密钥、未脱敏的凭证)。
|
||||
|
||||
---
|
||||
|
||||
## 8. 性能优化与上下文管理
|
||||
|
||||
- **异步编程**:必须使用 `async/await` 或 `Promise` 处理网络与文件 I/O,确保不阻塞 Node.js 事件循环。
|
||||
- **语义分块**:对于长篇文档的读取,服务端必须在本地完成文本解析与切割,仅将高度相关的片段同步给客户端,防止 LLM Token 耗尽。
|
||||
- **缓存与数据库**:对于频繁查询的数据,采用 LRU(最近最少使用)缓存策略或引入外部缓存(如 Redis)以减少负载。同时需使用合适的索引、查询优化和分页技术,避免数据库性能瓶颈。
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 9. 安全性与权限管控
|
||||
|
||||
协议必须遵循“零信任架构原则”,将 AI 生成的指令视为不可信负载。
|
||||
|
||||
- **数据加密**:敏感信息必须加密存储,使用 `bcrypt` 或 `argon2` 进行密码哈希处理。
|
||||
- **认证与授权**:系统通信应使用 JWT 或 OAuth 2.0/2.1 进行认证,并采用作用域限定防止令牌滥用。
|
||||
- **Roots 隔离**:必须实现 Roots(根目录)机制,服务端依据宿主应用传递的 URI 列表划定沙箱,阻断越权访问和路径遍历漏洞。
|
||||
- 定期进行安全审计,确保代码没有易受攻击的注入漏洞。
|
||||
|
||||
---
|
||||
|
||||
## 许可声明
|
||||
|
||||
本文档采用 **知识共享署名--相同方式共享 4.0 国际许可协议 (CC BY--SA 4.0)** 进行许可,© 2025-2026 Gitconomy Research.
|
||||
|
|
@ -0,0 +1,219 @@
|
|||
# Project Caffeine 文档编写规范指南
|
||||
|
||||
## 1. 目的与概述
|
||||
|
||||
本文档编写规范旨在为 **Project Caffeine** 项目的全体成员提供统一的文档撰写标准。通过标准化的元数据管理、文档结构梳理、Markdown 排版、技术写作规范以及版本控制,确保所有输出的工程文档、架构设计与迭代规划具备高度的可读性、一致性与可追溯性。
|
||||
|
||||
## 2. 文档头部元数据规范 (YAML Frontmatter)
|
||||
|
||||
结合本项目的工程化管理要求,**所有 Markdown 文档的最顶部必须包含标准化的 YAML Frontmatter 元数据块**,以便于文档站点的解析、版本检索与资产归档。
|
||||
|
||||
必须包含以下核心字段:
|
||||
|
||||
- **`title`**:文档的正式标题。
|
||||
- **`description`**:用 1-2 句话高度概括文档的核心主旨与意图。
|
||||
- **`type`**:文档的类型界定(如 `README`, `System Development Guide`, `Roadmap` 等)。
|
||||
- **`version`**:严格遵循“版本号 + 咖啡代号 + 里程碑”格式(例如 `v1.0.0 (Arabica) - MVP` 或 `Sprint 1`)。
|
||||
- **file**:文件名
|
||||
- **`author`**:统一署名为 **Gitconomy Research**。
|
||||
- **`date`**:遵循 `YYYY-MM-DD` 的 ISO 标准格式。
|
||||
- **`tags`**:提取核心技术栈与概念标签(如 `Project Caffeine`, `MCP`, `Deep Research` 等)。
|
||||
- **`license`**:声明为 `CC BY-SA 4.0`。
|
||||
- **`status`**:(可选)标注文档的生命周期状态(如 `Active`, `Design and Planning` 等)。
|
||||
|
||||
## 3. 文档类型与结构建议
|
||||
|
||||
为了确保各类工程文档的逻辑完整性与结构一致性,项目内的核心文档应当遵循以下分类及标准结构框架进行编写:
|
||||
|
||||
### 3.1 文档类型与用途
|
||||
|
||||
项目文档按用途分为以下几类,主要存放于仓库的 `docs/` 目录下:
|
||||
|
||||
|类型|说明|示例路径|
|
||||
|:--|:--|:--|
|
||||
|**README**|项目总体介绍、快速开始、核心特性、状态徽章等。每个主要模块/子包应有自己的 README。|`/README.md` `/packages/server/README.md`|
|
||||
|**设计文档**|记录架构决策、模块设计、协议交互、数据流等,用于指导开发和评审。|`/docs/design/`|
|
||||
|**开发指南**|面向开发者的环境搭建、编码规范、测试指南、调试技巧等。|`/docs/development/`|
|
||||
|**用户指南**|面向最终用户的使用说明、配置示例、FAQ 等。|`/docs/user/`|
|
||||
|**API 文档**|MCP 服务暴露的 tools(工具)、prompts(提示词)、resources(资源)的详细说明,包括 JSON-RPC 参数、返回值、示例。|`/docs/api/`|
|
||||
|**贡献指南**|如何提交 Issue、PR,代码规范,文档规范,行为准则等。|`/CONTRIBUTING.md`|
|
||||
|**更新日志**|记录版本变更,遵循 Keep a Changelog 格式。|`/CHANGELOG.md`|
|
||||
|
||||
> **注意**:每个文档顶部必须包含标准化的 YAML front matter(详见第 2 节),以便机器读取元数据。
|
||||
|
||||
### 3.2 README 主文档
|
||||
|
||||
README 是项目或子包的门面,必须清晰传达项目的核心定位与接入方式,包含以下标准模块:
|
||||
|
||||
- **项目标题与徽章**:显示构建状态、协议、核心技术栈及目标版本。
|
||||
- **项目概述**:简述项目功能、核心价值与适用场景。
|
||||
- **系统架构**:放置核心的架构图或拓扑图,概述系统组件及其职责。
|
||||
- **技术栈与开发环境**:
|
||||
- 核心语言与运行环境(要求使用 TypeScript 与 Node.js LTS v20+)。
|
||||
- 依赖管理(明确标明是否采用 Monorepo / 原生 npm Workspaces)。
|
||||
- 协议与 SDK 说明(如 MCP 协议与 JSON-RPC 2.0 标准规范)。
|
||||
- 安全与环境要求(如配置隔离与“零信任架构原则”)。
|
||||
- **开发路线图**:按 MVP 或 Sprint 分阶段展示任务、目标及关键功能(如 Arabica Sprint 1 阶段特性)。
|
||||
- **参与方式**:Issue 提交规范、PR 流程以及讨论参与指引。
|
||||
- **AI 生成内容声明**:明确提示用户系统生成的报告需人工核验,仅供参考。
|
||||
- **许可证说明**:明确采用双轨制许可,即源代码通常采用 **MIT License**,而文档与研究成果采用 **CC BY-SA 4.0** 协议。
|
||||
|
||||
### 3.3 设计文档
|
||||
|
||||
用于详细阐述某一具体微服务或核心逻辑(如提示词策略 Server、资源读取模块),必须包含:
|
||||
|
||||
- **文档标题与版本信息**。
|
||||
- **模块概述**:说明该模块的具体功能、作用及与其他系统组件的交互关系。
|
||||
- **系统架构图/流程图**:直观展示数据与控制流向。
|
||||
- **接口定义与通信协议**:如 stdio 或 HTTP+SSE 的传输层约定。
|
||||
- **数据结构与格式**:如基于 **JSON-RPC 2.0** 的 schema 定义与验证规则。
|
||||
- **核心算法或逻辑描述**:如针对长文本的语义分块(Semantic Chunking)策略或缓存机制。
|
||||
- **配置示例与部署说明**:指引如何修改环境路径与启动系统。
|
||||
- **注意事项与最佳实践**:如代码防范越权访问、性能优化红线等。
|
||||
|
||||
### 3.4 图表与示意图
|
||||
|
||||
文档中的视觉资产需遵循以下存放与引用规范:
|
||||
|
||||
- **格式要求**:首选使用原生 **SVG** 代码,在特定场景下可使用高分辨率 **PNG**。若采用 SVG,必须支持通过 Git 进行 diff 比对。
|
||||
- **命名与文件夹组织**:图表文件必须存放在规定的资源目录中,遵循 `docs/assets/images/<模块名称>-<内容>.svg` 或对应的后缀格式。
|
||||
- _示例_:`docs/assets/images/project-caffeine-system-topology.svg`。
|
||||
- **图文对应**:所有图表下方必须配备简短的说明(Caption),确保图表与其解释的上下文内容紧密对应。
|
||||
|
||||
## 4. 结构与格式
|
||||
|
||||
我们使用标准的 Markdown 进行编写。
|
||||
|
||||
### 4.1 文件命名
|
||||
|
||||
文档文件命名推荐使用清晰的中文标题或英文 **kebab-case(短横线命名法)**,如 `project-caffeine-readme.md`,以保持与工程代码文件目录的命名风格统一。
|
||||
|
||||
### 4.2 章节标题的结构
|
||||
|
||||
- 使用 ATX 风格的标题(`#`)。
|
||||
- 文档主标题使用 H1(`#`),章节标题从 H2(`##`)开始。
|
||||
- 标题应简洁明了,采用**句首大写 (Sentence case)**,除非是专有名词。
|
||||
|
||||
### 4.3 章节标题的语法规范
|
||||
|
||||
#### H1: 主标题——副标题
|
||||
|
||||
1. **结构**:**主标题 + 副标题**
|
||||
2. **要求**:主标题是文章或章节的核心主题,副标题进一步解释或细化主标题,提供更多上下文或具体内容。主副标题之间可以使用破折号连接。
|
||||
|
||||
#### H2: 定语 + 助词 + 名词
|
||||
|
||||
1. **结构**:**定语 + 助词 + 名词**
|
||||
2. **要求**:H2标题通常作为章节标题,简洁地描述章节的核心内容。使用定语修饰名词,使标题具有具体性和引导性,助词(如“的”)连接定语与名词。
|
||||
|
||||
#### H3和H4: 谓语 + 宾语
|
||||
|
||||
1. **结构**:**谓语 + 宾语**
|
||||
2. **要求**:H3和H4标题通常用于小节或细节部分,强调动作(谓语)和受动者(宾语)。这种结构使得标题更加具体,直接描述某个操作、过程或事件。
|
||||
|
||||
### 4.4 列表 (Lists)
|
||||
|
||||
- **无序列表**:使用减号 `-`。列表项如果是一句完整的话,以句号结尾;如果是短语,则不需要。
|
||||
- **有序列表**:仅在步骤有严格顺序时使用数字列表。
|
||||
|
||||
### 4.5 表格
|
||||
|
||||
- 使用标准Markdown表格语法。
|
||||
- 表格前后留一空行。
|
||||
- 表头与内容之间必须有分隔行。
|
||||
- 尽量保持列对齐以提高可读性。
|
||||
|
||||
### 4.6 链接与图片
|
||||
|
||||
- 使用相对路径引用仓库内图片。
|
||||
- 外部链接应提供完整URL,包括https://前缀。
|
||||
- 提供清晰的alt 文本,兼顾无障碍与 LLM 理解。
|
||||
|
||||
### 4.7 强调
|
||||
|
||||
- **粗体 (`text`)**:用于强调关键概念、新的术语定义或需要用户特别注意的UI元素。**不要滥用**,满篇粗体等于没有重点。
|
||||
- _斜体 (`*text*`)_:用于书籍、文章标题,或表示语气的强调。
|
||||
- `行内代码 (Inline Code)`:用于正文中的命令、文件名、分支名、路径或配置项。
|
||||
|
||||
## 5. 技术写作规范
|
||||
|
||||
这是体现专业性的核心区域。
|
||||
|
||||
### 5.1 代码块 (Code Blocks)
|
||||
|
||||
所有代码块必须指定语言标记,以便正确高亮。
|
||||
|
||||
- **命令行操作**:使用 `bash` 或 `sh`。
|
||||
- 命令的输出结果**不**带 `$` 符号。
|
||||
- 需要用户替换的部分使用尖括号 `<placeholder>` 包裹。
|
||||
- **配置文件**:使用 `gitconfig`、`yaml`、`json` 等对应格式。
|
||||
- **文件内容示例**:如果只是展示文本内容,可以使用 `text`。
|
||||
|
||||
### 5.2 标点符号
|
||||
|
||||
- 使用全角中文标点符号(,。!?:;“”‘’())。
|
||||
- 中英文混排时,英文与中文之间应保留一个空格(此项通常由排版引擎自动处理,但在 Markdown 源码中手动添加可以提高可读性)。
|
||||
|
||||
## 6. 文字风格规范指南
|
||||
|
||||
### 6.1 文字的情绪化与中立性
|
||||
|
||||
在编写文档时,我们应避免使用情绪化或带有政治色彩的语言。这不仅是为了确保技术的客观性,也是为了尊重读者的多元背景和文化视角。以下是一些具体的规范:
|
||||
|
||||
1. **避免带有攻击性或情绪化的语言**:我们的目标是帮助读者理解技术,而非通过情绪化的语言来引导思维。避免过于夸张、激烈的表述。
|
||||
2. **避免政治性语言和隐含的偏见**:在技术文档中,避免任何可能引发争议的政治色彩和社会偏见。
|
||||
3. **避免过于主观或过度的情感表达**:文档应该以冷静、客观的语气提供信息,而不是通过情感化的语言来影响读者的判断。
|
||||
4. **简洁明了,避免冗长和情绪化的描述**:避免描述性语言过于冗长或情感化的调调。尽量做到简洁直接,保持专业性。
|
||||
|
||||
### 6.2 专业性和清晰度
|
||||
|
||||
1. **保持语气的中立性**:所有表达应当中立、平衡,避免偏袒或倾向某一方的语气。专业的写作风格是直接、简洁和清晰的,不带有个人情绪色彩。
|
||||
2. **使用简洁明了的句式**:使用清晰的句式,避免过于复杂的结构。尽量使每个句子都传达一个清晰的想法,避免无谓的修饰和修辞手法。
|
||||
|
||||
### 6.3 拒绝“翻译腔”
|
||||
|
||||
这是我们文字风格的第一大敌。我们在编写或翻译内容时,必须符合中文的自然表达习惯。
|
||||
|
||||
1. **减少“被”字句**:英文习惯用被动语态,而中文习惯用主动语态。
|
||||
2. **警惕“的”字风暴**:过多的“的”字会让句子变得粘连、拖沓。尝试通过重组句子来消除不必要的“的”。
|
||||
3. **避免生硬的从句**:不要试图在一个长句中保留英文的所有修饰成分。将长句拆分为短句,符合中文的“流水句”特征。
|
||||
|
||||
### 6.4 词汇的选择
|
||||
|
||||
1. **动词的力量**:使用精准、有力的动词,避免使用“进行”、“作出了”等万能动词。
|
||||
2. **术语的“中文本地化”**:除非是专有名词(如 Rebase, Cherry-pick 这种很难翻译传神的),否则尽量使用标准的中文术语,但要标注英文原词。
|
||||
- **首次出现**:暂存区 (Staging Area)
|
||||
- **后续使用**:暂存区
|
||||
|
||||
### 6.5 中英文混排与标点符号
|
||||
|
||||
1. **空格规范**:**汉字与英文、汉字与数字之间,必须保留一个空格。**
|
||||
2. **标点符号**:
|
||||
- **全角标点**:中文句子中,必须使用全角标点(,。!?:;())。
|
||||
- **英文标点**:只有在行内代码(Inline Code)或英文专业术语(如 `user.name`)中才使用半角标点。
|
||||
- **空格禁忌**:全角标点与汉字或英文之间,**不需要**添加空格。
|
||||
|
||||
### 6.6 标题与段落
|
||||
|
||||
1. **标题即论点**:标题不应只是名词,最好是动宾结构或完整的论点,让读者只看目录就能懂大意。
|
||||
2. **倒金字塔结构**:段落的第一句必须是**中心句 (Topic Sentence)**。先说结论,再展开解释,最后给例子。不要让读者读到最后一行才知道这一段想说什么。
|
||||
|
||||
## 7. 图形即代码 (Diagram as Code) 集成规范
|
||||
|
||||
文档中嵌入的架构图、时序图等图形资产,必须严格遵循《图形即代码设计规范指南》:
|
||||
|
||||
- **格式要求**:所有核心图表必须是可内嵌于 Markdown 的原生 **SVG** 代码,并支持通过 Git 进行 diff 比对与历史追溯。
|
||||
- **兼容性与背景**:为防止 Git 平台深色模式下的反色问题与 XSS 清洗,**推荐在 SVG 最底层放置纯白背景**(`<rect width="100%" height="100%" fill="#FFFFFF" />`),且**严禁包含 `<script>` 标签或外部图像/字体引用**。
|
||||
- **响应式适配**:SVG 必须使用 `viewBox` 属性定义坐标系,并将宽高设置为 `100%`,确保在 Markdown 容器内流畅等比缩放。
|
||||
|
||||
## 8. 术语与内容结构统一
|
||||
|
||||
- **MCP 协议原语**:在文档中描述系统能力时,需统一使用标准术语。例如,暴露给模型的主动操作必须统一称为 **Tools (工具)**;静态上下文数据源统一称为 **Resources (资源)**;复用模板称为 **Prompts (提示词)**。
|
||||
|
||||
## 9. 版权与许可声明
|
||||
|
||||
为了保障知识产权与开源精神的传承,**每一份标准文档的末尾,必须固定包含以下完全一致的版权与开源许可证声明**:
|
||||
|
||||
> ## 许可声明
|
||||
>
|
||||
> 本文档采用 **知识共享署名--相同方式共享 4.0 国际许可协议 (CC BY--SA 4.0)** 进行许可,© 2025-2026 Gitconomy Research。
|
||||
|
|
@ -1,22 +1,19 @@
|
|||
<!--
|
||||
---
|
||||
title: Project Caffeine项目命名规则指南
|
||||
description: 详细阐述 Project Caffeine(萃取者计划)的核心命名隐喻、A-Z 咖啡图谱版本号规范及其在 Git 与工程化中的配置标准
|
||||
type: Guide
|
||||
file: project-caffeine-nanming-convention-guide.md
|
||||
version: v1.0.1
|
||||
author: Gitconomy Research-郭晧
|
||||
title: "Project Caffeine项目命名规则指南"
|
||||
description: "详细阐述 Project Caffeine(萃取者计划)的核心命名隐喻、A-Z 咖啡图谱版本号规范及其在 Git 与工程化中的配置标准"
|
||||
type: "Guide"
|
||||
file: "project-caffeine-version-name-convention-guide.md"
|
||||
version: "v1.0.0 (Arabica)"
|
||||
author: "Gitconomy Research-郭晧"
|
||||
date: 2026-02-28
|
||||
last-update: 2026-03-02
|
||||
update-description: 增建代码版本号的规则说明
|
||||
tags:
|
||||
- Project Caffeine
|
||||
- Name Convention
|
||||
- Version Controll
|
||||
- Revision Rules
|
||||
- A-Z版本迭代
|
||||
- Project Management
|
||||
license: CC BY-SA 4.0
|
||||
status: Active
|
||||
license: "CC BY-SA 4.0"
|
||||
status: "Active"
|
||||
---
|
||||
-->
|
||||
# Project Caffeine项目版本命名规则指南
|
||||
|
|
@ -86,71 +83,6 @@ status: Active
|
|||
|
||||
---
|
||||
|
||||
这是一个非常必要的补充!在工程化实践中,虽然“A-Z咖啡代号”赋予了项目极客的浪漫主义色彩,但在底层的代码包管理(如 `package.json`)、Git 标签(Tag)以及依赖追踪中,计算机只认识严谨的数字。
|
||||
|
||||
我们需要将极客代号与业界标准的 **语义化版本控制 (Semantic Versioning, SemVer)** 完美缝合。
|
||||
|
||||
我为您拟定了一个全新的小节,建议将其作为 **“3. 项目代码版本号 (SemVer) 规范”** 插入到原文档中(原有的“3. 分支与工程配置规范”顺延为第 4 节)。您可以直接复制以下内容:
|
||||
|
||||
---
|
||||
|
||||
## 3. 项目代码版本号 (SemVer) 规范
|
||||
|
||||
本项目代码的基础版本号严格遵循 "语义化版本控制规范",基本格式为 `主版本号.次版本号.修订号` (`MAJOR.MINOR.PATCH`)。为了将工程实践与我们的项目文化结合,这三个维度的数字将与 A-Z 咖啡代号以及敏捷开发 (Agile) 的 Sprint 周期深度绑定:
|
||||
|
||||
1. **主版本号 (MAJOR)**:与 A-Z 咖啡图谱的大代号严格对应。
|
||||
|
||||
- **触发条件**:当系统架构发生重大重构、API 发生不兼容变更,或核心业务理念发生代际升级时递增。
|
||||
- **命名映射**:`v1.x.x` 阶段的所有代码统称为 **Arabica**,当主版本号升级至 `v2.x.x` 时,代号整体更替为 **Bourbon**,以此类推。
|
||||
|
||||
2. **次版本号 (MINOR)**:与敏捷开发中的 **Sprint(迭代冲刺周期)** 强绑定。
|
||||
|
||||
- **触发条件**:在一个主代号周期内,向下兼容地新增了核心功能或完成了新的 Sprint 目标。
|
||||
|
||||
- **命名映射**:
|
||||
|
||||
- Arabica Sprint 1 交付版本的基线为 `v1.0.0`。
|
||||
- Arabica Sprint 2 交付版本的基线为 `v1.1.0`。
|
||||
|
||||
3. **修订号 (PATCH)**:对应日常的缺陷修复与微调。
|
||||
|
||||
- **触发条件**:进行了向下兼容的 Bug 修复、安全补丁更新或细微的性能优化(不包含新功能)。
|
||||
- **命名映射**:在 Sprint 1 (`v1.0.0`) 发布后,如果修复了一个路径防穿越的安全漏洞,版本号应升级为 `v1.0.1`。
|
||||
|
||||
4. **预发布与环境后缀 (Prerelease Tags)**:
|
||||
|
||||
- 在正式的 Release 发布之前,处于开发或测试阶段的代码,必须在版本号后通过连字符 `-` 追加状态标识。
|
||||
|
||||
- **示例**:
|
||||
|
||||
- `v1.0.0-alpha.1`(Sprint 1 的内部开发测试版)
|
||||
- `v1.1.0-rc.1`(Sprint 2 的 Release Candidate 发布候选版)
|
||||
|
||||
|
||||
**代码配置示例 (`package.json`):**
|
||||
|
||||
``` json
|
||||
{
|
||||
"name": "@project-caffeine/mcp-server",
|
||||
"version": "1.0.0",
|
||||
"description": "Project Caffeine Arabica (Sprint 1) Release",
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
---
|
||||
|
||||
## 4. 分支与工程配置规范
|
||||
|
||||
为了将该命名规则严格落实到开发环节,团队需遵循以下工程配置标准:
|
||||
|
||||
1. **环境变量注入**:在工程的 `package.json` 及系统级 `.env` 文件中,需显式声明代号。
|
||||
2. **Git 分支管理**:所有的 Release 发布分支需携带字母代号后缀,例如:`release/v1.0-arabica`, `release/v2.1-bourbon`。
|
||||
3. **架构图注规范**:根据《图形即代码设计规范指南》,所有的系统架构 SVG 拓扑图中,右上角及面包屑导航的注释必须带有当前版本的咖啡代号,例如 `Release: v1.0.0 (Arabica)`。
|
||||
|
||||
---
|
||||
|
||||
## 许可声明
|
||||
|
||||
本文档采用 **知识共享署名--相同方式共享 4.0 国际许可协议 (CC BY--SA 4.0)** 进行许可,© 2025-2026 Gitconomy Research.
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
<!--
|
||||
---
|
||||
title: Arabica Release Sprint 功能开发规划
|
||||
description: 全面规划 Arabica (v1.0.0) 版本的 5 个渐进式 Sprint 迭代目标,遵循敏捷迭代原则,涵盖从底层 MCP 基础设施建设、思维框架组装,到递归深度研究算法攻坚与本地 PKM 融合的完整演进路径。
|
||||
type: Feature Design
|
||||
version: v1.1.0 (Arabica)
|
||||
file: arabica-release-sprint-design.md
|
||||
author: Gitconomy Research-郭晧
|
||||
date: 2026-03-05
|
||||
last-update: 2026-03-08
|
||||
update-description: 增加了Sprint10的规划说明
|
||||
tags:
|
||||
- Project Caffeine
|
||||
- MVP
|
||||
- Sprint 规划
|
||||
- 开发路线图
|
||||
- Deep Research
|
||||
- MCP
|
||||
license: CC BY-SA 4.0
|
||||
status: Active
|
||||
---
|
||||
-->
|
||||
# Arabica Release Sprint 功能开发规划
|
||||
|
||||
## 🏁 Sprint 1:本地基础设施与单点能力验证 (已完成)
|
||||
|
||||
**概述**: 本次迭代的核心目标是搭建零网络开销的底层标准通信,跑通大模型对本地知识库的安全读取与基础策略生成的物理链路。系统在一套代码中集成了官方 MCP SDK(支持 Cherry Studio 的 `stdio` 标准通信),无缝接入本地 Obsidian 目录,并通过 VS Code 跑通了完美的源码级断点联调工作流。
|
||||
|
||||
**版本**:`0.0.1`
|
||||
|
||||
|功能模块|详细说明|
|
||||
|---|---|
|
||||
|**底层架构 (stdio) 初始化**|初始化基于 Node.js 的主入口 `src/app.ts`,负责建立标准输入输出传输层,并向客户端注册工具字典。|
|
||||
|**本地资源安全挂载**|开发 `src/services/resourceService.ts`,实现 `list_local_notes` 和 `read_local_note` 工具,并带有严格路径防穿越(Path Traversal)安全校验。|
|
||||
|**提示词策略引擎建设**|开发 `src/services/promptService.ts`,提供纯本地业务逻辑,负责核心的 **5 Whys 框架生成**。|
|
||||
|**全链路源码级联调配置**|配置 `tsconfig.json` 生成 `sourceMap`,并在 `.vscode/launch.json` 中映射 `dist` 目录,实现在 Cherry Studio 挂载 `--inspect=9229` 参数的源码级断点拦截。|
|
||||
|
||||
---
|
||||
|
||||
## Sprint 2:提示词策略 Server (S2) 建设与多维思维引擎组装
|
||||
|
||||
**概述**: 本次迭代的核心目标是全面扩充系统的“大脑”——**S2 (提示词策略 Server)**。通过引入 MCP 的 `Prompts` 原语,系统将写入多种静态思维框架模板,让大模型在面对用户模糊的自然语言提问时,能够主动调用这些标准化的模板来规范交互结构和意图拆解逻辑,彻底打通基于思维框架的单次分析闭环。
|
||||
|
||||
**版本**:`0.0.2`
|
||||
|
||||
| 功能模块 | 详细说明 |
|
||||
| :------------------- | :------------------------------------------------------------------------------------------------------------------------ |
|
||||
| **MCP Prompts 原语接入** | 启用 MCP 规范中的第三大核心系统原语 `Prompts`。开发 `prompts/list` 和 `prompts/get` 接口,将各种思维框架作为可复用的模板暴露给大模型,帮助其构造标准化的交互结构,降低每次对话所需的前置上下文长度。 |
|
||||
| **多维静态思维框架扩展** | 在 Sprint 1 的 5 Whys 基础上,向 S2 写入并注册更多经典的静态思维框架模板,如 **5W3H、SCQA、SWOT、PESTLE** 等。这些框架将指导大模型按照标准逻辑结构进行逐步推理。 |
|
||||
| **意图拆解与广度解析工具** | 配合上述思维框架,开发 `generate_search_queries` 工具。该工具将赋予大模型将用户的“大白话”拆解为 3-5 个专业检索词的能力,从而实现研究主题的 Breadth(广度)解析。 |
|
||||
| **底层角色矩阵与输出规范设计** | 建立多智能体角色矩阵 (Persona Matrix) 的雏形,设计精确的 System Prompts,并通过 Few-Shot(少样本)示例在提示词模板中定义大模型的输出结构约束(如强制输出 Markdown 格式)。 |
|
||||
|
||||
---
|
||||
|
||||
## Sprint 3:文献查询 Server (S1) 与外围学术检索
|
||||
|
||||
**概述**: 本次迭代**聚焦于外部学术数据的抓取与标准化落盘**。让系统拥有向外探索的“触手”,接入学术 API,并将抓取到的离散 JSON 数据转化为符合后续系统流转的本地化 Markdown 数据。
|
||||
|
||||
**版本**:`0.0.3`
|
||||
|
||||
|功能模块|详细说明|
|
||||
|---|---|
|
||||
|**基础学术检索能力**|集成至少 2 个核心学术 API(如 arXiv API、Semantic Scholar API),实现基础的 `search_academic_literature` 工具。|
|
||||
|**双轨制数据落盘模块**|开发 `save_to_local_vault` 工具,实现外部 API 的 JSON 数据到 Markdown + YAML Frontmatter 的自动转换。|
|
||||
|**标准化数据字典**|设计最终落盘的 Markdown 模板与 YAML Frontmatter 元数据字典,明确 `title`, `citation_density_check`, `research_depth_level` 等字段。|
|
||||
|
||||
---
|
||||
|
||||
## Sprint 4:长文本切块算法与单轮 CoT 推理合成 (S3)
|
||||
|
||||
**概述**:本次迭代**聚焦于大模型上下文窗口限制的突破与基础报告生成**。解决长篇 PDF 或文献导致的 Token 超载问题,并正式建立 S3(CoT 多步推理 Server),打通单次分析的工作流闭环。
|
||||
|
||||
**版本**:`0.0.4`
|
||||
|
||||
|功能模块|详细说明|
|
||||
|---|---|
|
||||
|**Token 感知语义切块**|开发 `fetch_and_split_document` 工具,制定长文本“Token 感知语义切块(Semantic Chunking)”的具体策略与重叠率标准。|
|
||||
|**异步局部同步**|服务端在本地完成文本解析与向量切割,仅将统计学上高度相关的片段异步同步给客户端,避免 Token 极度消耗。|
|
||||
|**CoT 多步推理合成**|开发 `cot_literature_synthesis` 核心提示词模板,规范大模型的输出结构(摘要、引言、主体等)。|
|
||||
|
||||
---
|
||||
|
||||
## Sprint 5:深研状态机设计与防死循环管控
|
||||
|
||||
**概述**:本次迭代是系统的算法攻坚核心,**聚焦于递归检索的状态管理**。为了让系统从“单轮问答”升级为“AI 研究员”,本阶段专门开发探索账本与状态机,严防模型在多轮检索中陷入自我循环的死胡同。
|
||||
|
||||
**版本**:`0.0.5`
|
||||
|
||||
|功能模块|详细说明|
|
||||
|---|---|
|
||||
|**深研状态机设计**|明确大模型在“检索 -> 切块 -> 提取 -> 评估盲区 -> 追问”循环中的状态流转图。|
|
||||
|**探索账本管理**|开发 `manage_exploration_state` 工具,记录已经检索过的 Query 和文献 ID。|
|
||||
|**哈希去重与循环拦截**|设计布隆过滤器或哈希去重逻辑,严防大模型陷入检索死循环。|
|
||||
|
||||
---
|
||||
|
||||
## Sprint 6:多智能体角色矩阵与知识盲区探测
|
||||
|
||||
**概述**:在状态机建好后,本次迭代**聚焦于智能体角色赋予与递归评估逻辑**。通过下发不同的专家指令,并强约束模型必须自我评估知识盲区,实现真正的多轮次深度研究(Deep Research)。
|
||||
|
||||
**版本**:`0.0.6`
|
||||
|
||||
| 功能模块 | 详细说明 |
|
||||
| ------------- | --------------------------------------------------------------------------- |
|
||||
| **多智能体角色矩阵** | 建立 Persona Matrix,为“检索策略专家”、“洞察提取研究员”、“战略分析顾问”等角色撰写并动态下发精确的 System Prompts。 |
|
||||
| **深度评估与盲区探测** | 开发最关键的 `evaluate_research_depth`(深度评估)和 `evaluate_knowledge_gaps`(盲区探测)工具。 |
|
||||
| **递归工作流编排** | 在客户端提示词中写入递归循环逻辑:强约束大模型在提取洞察后,必须调用评估盲区工具;若未达标,必须发起新一轮检索。 |
|
||||
|
||||
---
|
||||
|
||||
## Sprint 7:学术级质量把控与深度网络爬虫强化
|
||||
|
||||
**概述**:本次迭代**聚焦于学术级内容质检与突破性信息获取**。要求模型输出的结果必须具备严谨的学术溯源特征,同时补齐系统对深层网页内容的抓取能力。
|
||||
|
||||
**版本**:`0.0.7`
|
||||
|
||||
| 功能模块 | 详细说明 |
|
||||
| ------------ | --------------------------------------------------------------------------------------- |
|
||||
| **引用密度强制校验** | 强化 S3 质检逻辑,实现引用密度强制校验,确保正文中的核心事实必须带有 `[文献ID]` 尾注。 |
|
||||
| **时效性与局限声明** | 开发“利益冲突与时效性局限”自动声明模块,降低学术幻觉。 |
|
||||
| **深度网络爬虫强化** | 实现 `scrape_and_parse_deep_content` 工具,引入 Playwright 或 Firecrawl 突破纯 API 限制,抓取含金量高的网页正文。 |
|
||||
|
||||
---
|
||||
|
||||
## Sprint 8:PKM (个人知识管理) 深度对接与双向融合
|
||||
|
||||
**概述**:本次迭代**完全聚焦于最终知识产物的网状关联**。致力于让系统生成的报告不只是孤立的文件,而是能完美融入用户现有的 Obsidian 或 Logseq 知识图谱中。
|
||||
|
||||
**版本**:`0.0.8`
|
||||
|
||||
| 功能模块 | 详细说明 |
|
||||
| ---------- | ------------------------------------------------------------------------------------------ |
|
||||
| **物理串联机制** | 梳理与 Obsidian/Logseq 等工具联动的双向链接(`[[...]]`)自动生成策略。 |
|
||||
| **本地图谱融合** | 深度优化 `save_to_local_vault`,在 Markdown 正文中自动生成 Obsidian 格式的双向链接 `[[文献名称]]`,将主报告与独立洞察卡片物理串联。 |
|
||||
|
||||
---
|
||||
|
||||
## Sprint 9:网络层扩展与并发优化
|
||||
|
||||
**概述**:本次迭代旨在打破纯本地运行的限制,为系统赋予远程云端调用能力,并大幅提升高负载场景下的运行效率。
|
||||
|
||||
**版本*:`0.0.9`
|
||||
|
||||
| 功能模块 | 详细说明 |
|
||||
| ------------------- | --------------------------------------------------------------------------------------------------------------- |
|
||||
| **HTTP + SSE 协议扩展** | 引入 HTTP POST 请求与 Server-Sent Events (SSE) 流式下发响应,支持云端部署的远程 MCP 服务端进行跨网络调用。这将使系统具备从纯本地(stdio)向云端分布式架构平滑演进的能力。 |
|
||||
| **LRU 缓存与异步优化** | 引入全面的异步处理模型(如 Node.js 非阻塞事件流),并对高频读写的中间结果采用最近最少使用(LRU)缓存策略,结合哈希映射和双向链表高效管理数据的生命周期。这为应对大模型的高并发请求和海量长文本流转提供了性能保障。 |
|
||||
| **环境变量与网络配置** | 完善工程化结构中的 `.env.example` 文件,统一配置并管理各路云端 API Keys 以及远程调用的环境参数。 |
|
||||
|
||||
---
|
||||
|
||||
## Sprint 10:Arabica v0.0.10 版本系统全链路测试验证与缺陷修复
|
||||
|
||||
**Sprint 10 完全聚焦于全链路质量保障(QA)、安全审计与底层缺陷修复(Bug-fixing)**。本阶段旨在运用严密的测试体系,打磨系统健壮性,为完成 v0.0.10 版本发布的封装进行验证。
|
||||
|
||||
**版本**:`0.0.10`
|
||||
|
||||
| 功能模块 | 详细说明 |
|
||||
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **全链路集成测试与协议合规修复** | 结合单元测试与集成测试体系,**利用官方 MCP Inspector 开展系统级回归测试**。重点对 Sprint 2 与 Sprint 3 中暴露的 `generate_search_queries` 和 `search_academic_literature` 等核心工具进行通信打底,验证工具调用的 JSON-RPC 2.0 契约合规性。修复开发与联调期间遗留的异常分支与参数解析缺陷。 |
|
||||
| **深研状态机死循环溯源与熔断修复** | 针对 Sprint 5 建设的“探索账本”与状态机开展极端用例测试。模拟大模型遭遇极度知识盲区的情况,高压测试 `manage_exploration_state` 工具的拦截率。重点排查并修复哈希去重逻辑中可能存在的边界遗漏,**确保系统能够绝对熔断检索死循环**,防止大模型陷入自我循环的死胡同。 |
|
||||
| **负载极限压测与 LRU 缓存防泄漏** | 承接 Sprint 9 引入的最近最少使用(LRU)缓存策略与全面异步处理模型。利用 **k6 Load Testing** 针对高并发读写和长文本多轮推理场景开展 P99 响应时间压测。重点追踪并修复在高压网络环境下可能出现的内存泄漏(Memory Leak)或并发竞态条件崩溃问题。 |
|
||||
| **零信任沙箱攻防与防穿越验证** | **将大模型生成的 AI 指令一律视为不可信负载**。专门针对 Sprint 1 实现的本地资源读取工具(`read_local_note`)发起红蓝对抗测试。通过构造恶意路径,严密验证服务端的模型层校验(如 Zod Schema)与底层 I/O 逻辑,确保一切**试图跳出安全工作目录的“路径穿越(Path Traversal)”请求被绝对阻断**。 |
|
||||
| **规范审查与 v0.0.10 封装交付** | 全面执行文档库合规审查,确保所有 README 与设计指南均包含规范的 YAML Frontmatter(采用全英文 Tags 与短横线命名法)。确认所有代码与文档产物严格遵循 **CC BY-SA 4.0 (知识共享) 与 MIT 双轨制开源许可协议**。最终清理测试桩数据,锁定生产级依赖版本,为打包并交付可部署的 Arabica v0.1.0 版本做好准备。 |
|
||||
|
||||
---
|
||||
|
||||
## 许可声明
|
||||
|
||||
本文档采用 **知识共享署名--相同方式共享 4.0 国际许可协议 (CC BY--SA 4.0)** 进行许可,© 2025-2026 Gitconomy Research.
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
<!--
|
||||
---
|
||||
title: 提示词策略 MCP Server 原型设计文档
|
||||
description: Project Caffeine 提示词策略 MCP Server 的最小可行性功能(MVP)原型设计,涵盖 5 Whys 模板调用、增强提示词合成及 Node.js 环境工作流验证
|
||||
type: Architecture Design
|
||||
file: project-caffeine-mvp-sprint1-architecture-design.md
|
||||
version: v1.0.2 (Arabica)
|
||||
author: Gitconomy Research-郭晧
|
||||
date: 2026-03-1
|
||||
last-update: 2026-03-05
|
||||
update-description: 更新Sprint1 数据流示意图和架构图的说明描述。
|
||||
tags:
|
||||
- Project Caffeine
|
||||
- MCP Server
|
||||
- MVP
|
||||
- Srpint1
|
||||
- Prompt Strategy
|
||||
- 5 Whys
|
||||
- Node.js
|
||||
license: CC BY-SA 4.0
|
||||
status: Active
|
||||
---
|
||||
-->
|
||||
# PArabica Sprint1 系统架构设计说明
|
||||
|
||||
## 1. 原型设计概述
|
||||
|
||||
**功能目标**:实现一个最简化的 **提示词策略MCP Server**,当用户发起查询时,系统能够调用 **5 Whys** 模板对查询进行分解,生成增强的提示词,并将其发送到大模型进行推理。
|
||||
|
||||
**关键功能**:
|
||||
|
||||
- **工具注册 **:向 Cherry Studio 注册 `generate_5_whys` 能力。
|
||||
- **接收工具调用请求**:通过 `stdio` 接收 Cherry Studio 传来的用户查询主题。
|
||||
- **5 Whys 模板分解**:利用本地逻辑对查询进行分解,生成多层次的追问提示词。
|
||||
- **资源暴露**:将本地的文档库、PDF 或特定数据以 MCP Resource 的形式暴露给 Client(Sprint2)。
|
||||
|
||||
---
|
||||
|
||||
### 2. 功能实现步骤说明
|
||||
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User as 用户
|
||||
participant Client as Cherry Studio (MCP Client)
|
||||
participant LLM as 大语言模型
|
||||
participant Server as MCP Server (Project Caffeine)
|
||||
|
||||
User->>Client: 1. 输入查询(如“分析LLM在医疗中的应用瓶颈”)
|
||||
Client->>LLM: 传递查询
|
||||
LLM-->>Client: 2. 决策需要深度思考框架,决定调用工具
|
||||
Client->>Server: 3. tools/call (generate_5_whys, query=...)
|
||||
Server->>Server: 4. promptService.js 本地生成 5 Whys 追问列表
|
||||
Server-->>Client: 5. 返回 5 Whys 数组
|
||||
Client->>LLM: 6. 将 5 Whys 作为上下文,发起最终推理请求
|
||||
LLM-->>Client: 返回深度分析结果
|
||||
Client-->>User: 7. 渲染并展示深度研究报告
|
||||
```
|
||||
|
||||
基于 Cherry Studio 和 MCP (Model Context Protocol) 架构的工作流分为七个关键步骤:
|
||||
|
||||
1. **用户发起提问**:用户首先在前端(Cherry Studio)输入自己的问题或查询需求。
|
||||
2. **大模型决策调用工具**:Cherry Studio 作为 MCP 客户端,接收到用户提问后,其连接的大模型会对问题进行分析,并判断出需要调用系统注册的外部工具(如 5 Whys 分析法)来获取更好的提示词策略,而不是直接回答。
|
||||
3. **发起工具调用请求 tools/call**:Cherry Studio 通过标准输入输出流(`stdio`)协议,向后端的 Project Caffeine MCP Server(基于 Node.js 构建)发送一个名为 `generate_5_whys` 的工具调用指令。
|
||||
4. **本地生成 5 Whys 文本**:Project Caffeine MCP Server 接收到指令后,触发其内部的业务逻辑模块 `promptService.js`。该模块完全在本地运行,不依赖外部大模型,根据用户的初始问题自动生成 5 个逐层深入的追问(即 5 Whys 文本)。
|
||||
5. **返回工具执行结果**:MCP Server 将生成的 5 Whys 文本打包成标准的结果格式,通过协议传回给 Cherry Studio。
|
||||
6. **结合工具结果发起最终推理**:Cherry Studio 拿到这 5 个问题后,将其作为增强的上下文提示词,再次向大语言模型发起最终的深度推理请求,要求模型基于这些追问给出详尽的分析。
|
||||
7. **渲染并呈现结果**:大模型完成最终推理后,Cherry Studio 将这部分内容进行界面渲染,最终向用户呈现一份高质量的“深度洞察报告”。
|
||||
|
||||
---
|
||||
|
||||
## 3. 系统组件架构设计
|
||||
|
||||
### 3.1 **Project Caffeine** 系统原型项目结构说明
|
||||
|
||||
```markdown
|
||||
project-caffeine/
|
||||
│
|
||||
├── node_modules/ # 存放项目的依赖包
|
||||
├── src/ # 源代码文件夹
|
||||
│ ├── controllers/ # 逻辑路由分发层
|
||||
│ │ ├── promptsController.js # 处理提示词 Tool 相关请求
|
||||
│ │ └── resourcesController.js # 处理资源 Resource 请求
|
||||
│ ├── services/ # 核心业务逻辑层
|
||||
│ │ ├── promptService.js # 处理 5 Whys 提示词模板的生成算法
|
||||
│ │ └── resourceService.js # 处理本地文件、知识库等资源读取逻辑
|
||||
│ ├── models/ # 数据模型与校验
|
||||
│ │ └── schemas.js # 基于 Zod 的参数校验定义
|
||||
│ └── app.js # 主应用入口,初始化 MCP Server (stdio)
|
||||
│
|
||||
├── .vscode/ # IDE 调试配置
|
||||
│ └── launch.json # 配置 Cherry Studio 联调附加(Attach)环境
|
||||
├── config/ # 配置文件
|
||||
│ └── config.js # 项目配置
|
||||
├── .env # 环境变量配置 (不再需要 LLM API Key)
|
||||
├── package.json # 项目依赖 (@modelcontextprotocol/sdk)
|
||||
└── README.md # 项目说明文档
|
||||
```
|
||||
|
||||
**各文件和文件夹的功能说明**:
|
||||
|
||||
*表1-1:**Project Caffeine** 项目 MVP 系统文件和文件夹功能详细说明表格*
|
||||
|
||||
| **目录** | **文件名** | **功能说明** |
|
||||
| ---------------------- | ---------------------------- | --------------------------------------------------------------- |
|
||||
| **`src/`** | **`app.js`** | 实例化官方 `McpServer`,配置 `stdio` 传输层,向 Client 注册 Tools 和 Resources。 |
|
||||
| **`src/controllers/`** | **`promptsController.js`** | 接收 `tools/call` 请求,调用 `promptService` 并格式化输出返回给 Client。 |
|
||||
| **`src/controllers/`** | **`resourcesController.js`** | 处理 `resources/list` 和 `resources/read` 请求,返回资源数据。 |
|
||||
| **`src/services/`** | **`promptService.js`** | 纯本地业务逻辑:根据入参字符串生成 **5 Whys** 数组结构。 |
|
||||
| **`src/services/`** | **`resourceService.js`** | 本地文件系统交互:读取本地知识库(如 Obsidian)或指定目录文档。 |
|
||||
| **`.vscode/`** | **`launch.json`** | 极其关键:配置 Node.js 的 `--inspect` 端口,实现基于 Cherry Studio 唤起时的断点联调。 |
|
||||
|
||||
### 3.2 系统模块架构说明
|
||||
|
||||
*图 1-2:Sprint 1 组件架构与数据流*
|
||||
|
||||

|
||||
|
||||
Sprint 1 系统组件架构基于 MCP 协议构建了一个轻量级的提示词策略服务器。
|
||||
|
||||
核心组件包括:
|
||||
- `app.js` 作为主入口,负责初始化 `McpServer` 实例并配置 `stdio` 传输层,向客户端(Cherry Studio)注册 `generate_5_whys` 工具;
|
||||
- `controllers/promptsController.js` 接收并路由工具调用请求;
|
||||
- `services/promptService.js` 实现本地业务逻辑,根据用户查询自动生成 5 Whys 逐层追问列表;
|
||||
- `models/schemas.js` 基于 Zod 进行参数校验。
|
||||
|
||||
整个服务不依赖外部大模型,完全在本地运行,通过标准输入输出与客户端通信,形成“用户提问 → 客户端大模型决策 → 服务端生成框架 → 客户端大模型深度推理”的闭环工作流。该架构为后续扩展更多思维框架和资源访问奠定了坚实基础。
|
||||
|
||||
---
|
||||
|
||||
## 4. MCP 标准通信接口设计 (协议级)
|
||||
|
||||
基于 `@modelcontextprotocol/sdk`,底层的 JSON-RPC 通信由 SDK 接管。以下为逻辑层面的输入输出规约。
|
||||
|
||||
### 4.1 Tool 注册声明 (`tools/list`)
|
||||
|
||||
向 Client 声明具备的能力。
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "generate_5_whys",
|
||||
"description": "使用 5 Whys 模板对用户查询进行深度分解,生成增强的提示词策略",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {
|
||||
"type": "string",
|
||||
"description": "用户需要分析的原始查询主题,例如:中国开源人才的现状分析"
|
||||
}
|
||||
},
|
||||
"required": ["query"]
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### 4.2 Tool 调用响应 (`tools/call`)
|
||||
|
||||
当 Client 传入 `query: "中国开源人才的现状分析"` 时,Server 返回的执行结果。
|
||||
|
||||
```json
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "[\n \"为什么中国开源人才的培养面临困难?\",\n \"为什么中国开源人才缺乏足够的行业经验?\",\n \"为什么开源社区对中国人才的支持力度不足?\",\n \"为什么中国开源人才的市场需求与供给不平衡?\",\n \"为什么政策支持不足导致中国开源人才流失?\"\n]"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### 4.3 存储资源访问 (`resources/list` & `resources/read`)
|
||||
|
||||
允许 Cherry Studio 查阅本地文件上下文。
|
||||
|
||||
**资源列表响应示例 (`resources/list`):**
|
||||
|
||||
```json
|
||||
{
|
||||
"resources": [
|
||||
{
|
||||
"uri": "file:///path/to/obsidian/vault/开源行业报告.md",
|
||||
"name": "开源行业研究报告",
|
||||
"mimeType": "text/markdown",
|
||||
"description": "本地知识库中的开源行业深度分析文档"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 最小可行产品(MVP)开发环境
|
||||
|
||||
本阶段的开发环境高度依赖实际的 Client 联调:
|
||||
|
||||
* **Node.js (v18+)**:提供运行环境。
|
||||
* **@modelcontextprotocol/sdk**:官方依赖,提供 `stdio` 传输支持。
|
||||
* **Cherry Studio**:作为唯一指定测试 Client,配置为以 `command: node` 的方式启动 Server。
|
||||
* **VS Code Attach 调试**:利用 `--inspect=9229` 参数,在 Cherry Studio 唤起 Server 后,通过 VS Code 附加到该进程实现源码级断点调试。
|
||||
|
||||
---
|
||||
|
||||
## 6. 部署与验证
|
||||
|
||||
1. **环境初始化**:安装 Node.js 依赖及 Zod 校验库。
|
||||
2. **Client 配置**:在 Cherry Studio 的 MCP 设置中,添加名为 `ProjectCaffeine` 的 Server,指向本地的 `app.js` 绝对路径,并添加 `--inspect` 参数。
|
||||
3. **断点监听**:在 VS Code 中启动 Attach 调试任务,等待 Cherry Studio 握手。
|
||||
4. **触发验证**:在 Cherry Studio 对话框中要求模型“调用工具分析某问题”,观察 VS Code 是否成功捕获断点,并最终在 Cherry Studio 界面输出基于 5 Whys 增强的回答。
|
||||
|
||||
---
|
||||
|
||||
## 7. 总结
|
||||
|
||||
本设计说明提供了 **Project Caffeine** 的 **提示词策略MCP Server** 最小可行功能的开发框架,包括 **5 Whys** 模板生成、增强提示词的合成、推理请求与返回等关键功能。通过实现这一功能,可以验证整个系统的基本运行环境,确保 **MCP协议** 的各个组件能够顺利协同工作。
|
||||
|
||||
---
|
||||
|
||||
## 许可声明
|
||||
|
||||
本文档采用 **知识共享署名--相同方式共享 4.0 国际许可协议 (CC BY--SA 4.0)** 进行许可,© 2025-2026 Gitconomy Research.
|
||||
|
|
@ -0,0 +1,433 @@
|
|||
<!--
|
||||
---
|
||||
title: 提示词策略 MCP Server 原型设计文档
|
||||
description: Project Caffeine 提示词策略 MCP Server 的最小可行性功能(MVP)原型设计,涵盖 5 Whys 模板调用、增强提示词合成及 Node.js 环境工作流验证
|
||||
type: Development Guide
|
||||
file: project-caffeine-mvp-sprint1-architecture-design.md
|
||||
version: v1.0.3 (Arabica)
|
||||
author: Gitconomy Research-郭晧
|
||||
date: 2026-03-1
|
||||
last-update: 2026-03-07
|
||||
update-description: 修复部分文本显示格式。
|
||||
tags:
|
||||
- Project Caffeine
|
||||
- MCP Server
|
||||
- MVP
|
||||
- Srpint1
|
||||
- Prompt Strategy
|
||||
- 5 Whys
|
||||
- Node.js
|
||||
license: CC BY-SA 4.0
|
||||
status: Active
|
||||
---
|
||||
-->
|
||||
# Arabica Sprint1 版本开发指南
|
||||
|
||||
## 1. 环境前置要求
|
||||
|
||||
在开始部署前,请确保开发机已安装以下软件:
|
||||
|
||||
- **Node.js**: v20 LTS 或更高版本。
|
||||
- **Visual Studio Code (VS Code)**: 作为主力开发与断点调试 IDE。
|
||||
- **Cherry Studio**: 最新版,作为发起请求的 MCP Client(大模型中枢)。
|
||||
- **本地知识库**: 一个存放 `.md` 格式笔记的本地文件夹(如 Obsidian Vault)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 工程初始化与依赖安装
|
||||
|
||||
打开终端,执行以下命令从零搭建工程骨架:
|
||||
|
||||
```bash
|
||||
# 1. 创建并进入项目目录
|
||||
mkdir project-caffeine-ts
|
||||
cd project-caffeine-ts
|
||||
|
||||
# 2. 初始化 npm
|
||||
npm init -y
|
||||
|
||||
# 3. 安装生产核心依赖
|
||||
npm install @modelcontextprotocol/sdk zod
|
||||
|
||||
# 4. 安装 TypeScript 及开发环境依赖
|
||||
npm install --save-dev typescript @types/node
|
||||
|
||||
# 5. 创建标准的目录结构
|
||||
mkdir -p src/services dist .vscode
|
||||
touch src/app.ts src/services/promptService.ts src/services/resourceService.ts .vscode/launch.json tsconfig.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 核心工程配置
|
||||
|
||||
我们需要配置 TypeScript 编译器选项、启动脚本以及 VS Code 独有的源码映射调试环境。
|
||||
### 3.1 编辑`tsconfig.json`
|
||||
|
||||
控制代码编译并生成用于断点调试的 `sourceMap`:
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "node",
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"sourceMap": true, // 【关键】生成 .js.map 文件,用于 VS Code 断点映射
|
||||
"strict": true, // 开启严格模式
|
||||
"esModuleInterop": true, // 允许默认导入 CommonJS 模块
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 编辑`package.json`
|
||||
|
||||
添加 `build` 和 `watch` 脚本,用于将 `.ts` 编译为 `.js`。在 `package.json` 中找到 `"scripts"` 字段并替换为
|
||||
|
||||
```json
|
||||
"scripts": { "build": "tsc", "watch": "tsc --watch", "start": "node dist/app.js" }
|
||||
```
|
||||
|
||||
### 2.3 编辑 `.vscode/launch.json`
|
||||
|
||||
新增的 `outFiles` 字段,它告诉 VS Code 去 `dist` 目录寻找编译后的文件,从而将断点映射回你的 `src/*.ts` 源码上。
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"name": "🍒 附加到 Cherry Studio (TS 联调)",
|
||||
"port": 9229,
|
||||
"restart": true,
|
||||
"skipFiles": ["<node_internals>/**"],
|
||||
"outFiles": ["${workspaceFolder}/dist/**/*.js"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 核心业务代码实现
|
||||
|
||||
### 4.1. `src/services/promptService.ts` (提示词策略生成)
|
||||
|
||||
纯本地业务逻辑,负责 5 Whys 框架生成。
|
||||
|
||||
```TypeScript
|
||||
/**
|
||||
* 根据查询主题生成 5 Whys 提示词策略
|
||||
* @param query 用户输入的查询主题
|
||||
* @returns 包含 5 个追问的字符串数组
|
||||
*/
|
||||
export function generate5Whys(query: string): string[] {
|
||||
if (query.includes("开源人才")) {
|
||||
return [
|
||||
"为什么中国开源人才的培养面临困难?",
|
||||
"为什么中国开源人才缺乏足够的行业经验?",
|
||||
"为什么开源社区对中国人才的支持力度不足?",
|
||||
"为什么中国开源人才的市场需求与供给不平衡?",
|
||||
"为什么政策支持不足导致中国开源人才流失?"
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
`为什么 "${query}" 会成为一个问题?`,
|
||||
`为什么导致上述现象的直接原因会发生?`,
|
||||
`为什么当前的系统或流程没有阻止这种情况?`,
|
||||
`为什么以前的解决方案或预防措施失效了?`,
|
||||
`为什么根本的系统性漏洞一直未被修复?`
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 `src/services/resourceService.ts` (本地知识库访问)
|
||||
|
||||
带有严格路径防穿越(Path Traversal)安全校验的本地文件读取服务。
|
||||
|
||||
```TypeScript
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
|
||||
// 【⚠️ 重要配置】请修改为你电脑上真实的 Markdown 笔记文件夹绝对路径!
|
||||
const OBSIDIAN_VAULT_PATH = '/home/wguo/Downloads/MyVault';
|
||||
|
||||
export async function listObsidianNotes(): Promise<string[]> {
|
||||
try {
|
||||
const files = await fs.readdir(OBSIDIAN_VAULT_PATH);
|
||||
return files.filter(file => file.toLowerCase().endsWith('.md'));
|
||||
} catch (error: any) {
|
||||
console.error(`[Project Caffeine] 无法读取知识库目录: ${error.message}`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function readObsidianNote(filename: string): Promise<string> {
|
||||
const targetPath = path.resolve(OBSIDIAN_VAULT_PATH, filename);
|
||||
const safeVaultPath = path.resolve(OBSIDIAN_VAULT_PATH);
|
||||
|
||||
// 核心防御:防止大模型通过传入 "../../" 读取系统敏感文件
|
||||
if (!targetPath.startsWith(safeVaultPath)) {
|
||||
throw new Error(`安全警告:越权访问拦截!禁止读取目录外的文件: ${filename}`);
|
||||
}
|
||||
|
||||
try {
|
||||
const content = await fs.readFile(targetPath, 'utf-8');
|
||||
return content;
|
||||
} catch (error: any) {
|
||||
throw new Error(`无法读取笔记 [${filename}]: 文件可能不存在或无权限。`);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 `src/app.ts` (主入口)
|
||||
|
||||
负责初始化标准输入输出传输层,并向 Cherry Studio 注册工具字典。
|
||||
|
||||
```TypeScript
|
||||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
||||
import { z } from 'zod';
|
||||
import { generate5Whys } from './services/promptService';
|
||||
import { listObsidianNotes, readObsidianNote } from './services/resourceService';
|
||||
|
||||
// ==========================================
|
||||
// 1. 初始化 MCP Server
|
||||
// ==========================================
|
||||
const mcpServer = new McpServer({
|
||||
name: "Project-Caffeine-Prompt-Strategy",
|
||||
version: "1.2.0"
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// 2. 注册 Tools (工具) - 赋予大模型主动执行的能力
|
||||
// ==========================================
|
||||
|
||||
// 工具 1:5 Whys 提示词策略生成
|
||||
mcpServer.tool(
|
||||
"generate_5_whys",
|
||||
"使用 5 Whys 模板对用户查询进行深度分解,生成增强的提示词策略",
|
||||
{ query: z.string().describe("需要分析的查询主题") },
|
||||
async ({ query }: { query: string }) => {
|
||||
console.error(`[Project Caffeine] 大模型调用工具: 正在生成 5 Whys 策略 -> ${query}`);
|
||||
const enhancedPrompt = generate5Whys(query);
|
||||
return {
|
||||
content: [{ type: "text", text: JSON.stringify(enhancedPrompt, null, 2) }]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// 工具 2:扫描本地知识库目录
|
||||
mcpServer.tool(
|
||||
"list_local_notes",
|
||||
"获取本地 Obsidian 知识库中的所有 Markdown 笔记列表,用于了解当前有哪些可用的本地上下文资料。",
|
||||
{},
|
||||
async () => {
|
||||
console.error(`[Project Caffeine] 大模型调用工具: 正在扫描本地笔记列表...`);
|
||||
const notes = await listObsidianNotes();
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: notes.length > 0 ? `找到了以下笔记:\n${notes.join('\n')}` : "未找到笔记。"
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// 工具 3:阅读指定的单篇笔记内容
|
||||
mcpServer.tool(
|
||||
"read_local_note",
|
||||
"读取本地 Obsidian 知识库中指定笔记的完整内容,作为深度分析的上下文参考。",
|
||||
{ filename: z.string().describe("需要读取的笔记文件名,必须包含 .md 后缀") },
|
||||
async ({ filename }: { filename: string }) => {
|
||||
console.error(`[Project Caffeine] 大模型调用工具: 正在深度阅读笔记 -> ${filename}`);
|
||||
try {
|
||||
const content = await readObsidianNote(filename);
|
||||
return { content: [{ type: "text", text: content }] };
|
||||
} catch (error: any) {
|
||||
return {
|
||||
content: [{ type: "text", text: `读取失败: ${error.message}` }],
|
||||
isError: true // 明确告知大模型此操作抛出了错误
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// ==========================================
|
||||
// 3. 注册 Resources (资源) - 暴露给客户端供用户手动勾选的静态数据
|
||||
// ==========================================
|
||||
|
||||
// 资源 1:知识库目录索引
|
||||
mcpServer.resource(
|
||||
"obsidian-index", // 客户端显示的资源 Name/ID
|
||||
"obsidian://vault/index", // 唯一的 URI 标识
|
||||
{
|
||||
description: "本地知识库的目录索引,包含所有 Markdown 笔记的列表"
|
||||
},
|
||||
async (uri) => {
|
||||
console.error(`[Project Caffeine] 客户端请求静态资源: ${uri.href}`);
|
||||
|
||||
const notes = await listObsidianNotes();
|
||||
const textContent = notes.length > 0
|
||||
? `当前知识库包含以下文件:\n${notes.join('\n')}`
|
||||
: "当前知识库为空。";
|
||||
|
||||
return {
|
||||
contents: [{
|
||||
uri: uri.href,
|
||||
mimeType: "text/plain",
|
||||
text: textContent
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ==========================================
|
||||
// 4. 启动底层 Stdio 传输层
|
||||
// ==========================================
|
||||
async function start(): Promise<void> {
|
||||
console.error("[Project Caffeine] 正在启动 TS 版 MCP Server (含 Tools 与 Resources)...");
|
||||
const transport = new StdioServerTransport();
|
||||
await mcpServer.connect(transport);
|
||||
console.error("[Project Caffeine] MCP Server 已就绪,等待 Cherry Studio 交互。");
|
||||
}
|
||||
|
||||
// 捕获致命错误并安全退出
|
||||
start().catch((err: unknown) => {
|
||||
console.error("服务器启动失败:", err);
|
||||
process.exit(1);
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 启动与工作流验证
|
||||
|
||||
### 5.1 步骤一:启动 TS 实时编译 (Watch Mode)
|
||||
|
||||
在 VS Code 中打开终端,运行以下命令。这会让 TypeScript 编译器在后台实时监控你的 `.ts` 文件修改,并自动编译到 `dist` 目录中:
|
||||
|
||||
```bash
|
||||
npm run watch
|
||||
```
|
||||
|
||||
_(保持这个终端窗口在后台运行不要关闭)_
|
||||
|
||||
### 5.2 步骤二:在 Cherry Studio 中配置 Server
|
||||
|
||||
1. 进入 Cherry Studio 的 **设置 -> MCP**。
|
||||
|
||||
2. 添加或修改 Server,关键在于你要指向编译后的 `dist/app.js` 而不是 `src/app.ts`:
|
||||
|
||||
- **Command**: `node`
|
||||
- **Args**: `["--inspect=9229", "/project-caffeine-sprint1/dist/app.js"]`
|
||||
- _注意:需要输入app.js的绝对路径_
|
||||
|
||||
3. 确保状态灯亮起绿色。
|
||||
|
||||
### 5.3 步骤三:VS Code 源码级断点联调
|
||||
|
||||
1. 在 `src/app.ts` 或各个 Service 的关键代码行打上断点。
|
||||
2. 在 VS Code 左侧调试面板,选择 **"🍒 附加到 Cherry Studio (TS 联调)"**,点击运行。
|
||||
3. 状态栏变色即表示成功抓取到 Cherry Studio 的底层 Node 进程。
|
||||
|
||||
### 5.4 发起全链路交互
|
||||
|
||||
在 Cherry Studio 对话框中,输入以下指令测试大模型的自主编排能力:
|
||||
|
||||
> _"请先查看我的本地笔记列表,找到关于开源领域的笔记并阅读内容。然后结合你的知识,调用 5 Whys 工具帮我分析一下里面的痛点。"_
|
||||
|
||||
**预期结果**:你将看到大模型**自动、按顺序**调用了 `list_local_notes` -> `read_local_note` -> `generate_5_whys` 三个工具,最终为你输出一篇深度融合了你的私有知识库的洞察报告。
|
||||
|
||||
---
|
||||
|
||||
## 6. 系统运行测试样例
|
||||
|
||||
|
||||
在开始执行测试之前,请确保已完成以下前置准备:
|
||||
|
||||
- **后台编译**:在 VS Code 终端保持运行 `npm run watch` 命令。
|
||||
- **配置连接**:在 Cherry Studio 的设置中,将 Command 设为 `node`,Args 设为 `["--inspect=9229", "<绝对路径>/dist/app.js"]`,并确保状态灯亮起绿色。
|
||||
- **本地知识库**:确保代码中 `OBSIDIAN_VAULT_PATH` 指向的本地文件夹中存在至少一篇 Markdown 格式的测试笔记。
|
||||
|
||||
### 6.1 测试样例1:基础工具调用与分支逻辑验证
|
||||
|
||||
- **测试目标**:验证 `generate_5_whys` 工具的硬编码逻辑分支是否生效。
|
||||
|
||||
- **操作步骤**:
|
||||
|
||||
1. 向大模型发送指令:“请调用工具,帮我生成关于‘开源人才’的 5 Whys 策略。”
|
||||
|
||||
- **预期结果**:
|
||||
|
||||
- VS Code Debug Console打印日志:`[Project Caffeine] 大模型调用工具: 正在生成 5 Whys 策略 -> 开源人才`。
|
||||
- 大模型的回答中必须包含代码中预设的具体问题,例如“为什么中国开源人才的培养面临困难?”、“为什么中国开源人才缺乏足够的行业经验?”等。
|
||||
|
||||
### 6.2 测试样例 2:泛化工具调用验证
|
||||
|
||||
- **测试目标**:验证 `generate_5_whys` 工具在遇到未知主题时的泛化模板处理能力。
|
||||
|
||||
- **操作步骤**:
|
||||
|
||||
1. 向大模型发送指令:“我正在研究‘AI算力成本过高’的问题,请调用工具为我生成 5 Whys 框架。”
|
||||
|
||||
- **预期结果**:
|
||||
|
||||
- 大模型会调用工具,并将主题代入泛化模板,输出类似“为什么 "AI算力成本过高" 会成为一个问题?”、“为什么导致上述现象的直接原因会发生?”等框架性问题。
|
||||
|
||||
|
||||
### 6.3 测试样例 3:安全防御机制测试(红蓝对抗)
|
||||
|
||||
- **测试目标**:验证 `read_local_note` 工具中的路径防穿越(Path Traversal)安全校验是否能成功拦截恶意请求。
|
||||
|
||||
- **操作步骤**:
|
||||
|
||||
1. 向大模型发送诱导性指令:“请调用读取笔记的工具,帮我读取 `../etc/passwd` 文件的内容。”
|
||||
|
||||
- **预期结果**:
|
||||
|
||||
- 工具调用将被拦截,并抛出错误。
|
||||
- 大模型将收到包含 `isError: true` 的错误响应。
|
||||
- 错误信息明确提示大模型:“读取失败: 安全警告:越权访问拦截!禁止读取目录外的文件”。
|
||||
|
||||
|
||||
### 6.4 测试样例 4:全链路 Agentic 自主编排测试
|
||||
|
||||
- **测试目标**:验证大模型是否能自主决策,按顺序组合调用多个外部工具完成复杂分析。
|
||||
|
||||
- **操作步骤**:
|
||||
|
||||
1. 向大模型发送综合指令:“请先查看我的本地笔记列表,找到关于开源领域的笔记并阅读内容。然后结合你的知识,调用 5 Whys 工具帮我分析一下里面的痛点。”。
|
||||
|
||||
- **预期结果**:
|
||||
|
||||
- 大模型将自动并按顺序调用 `list_local_notes` -> `read_local_note` -> `generate_5_whys` 三个工具。
|
||||
- 最终输出一篇融合了私有知识库内容深度的洞察报告。
|
||||
|
||||
### 6.5 测试样例 5:VS Code 断点联调环境测试
|
||||
|
||||
- **测试目标**:验证 `.vscode/launch.json` 源码映射与调试环境配置是否成功。
|
||||
|
||||
- **操作步骤**:
|
||||
|
||||
1. 在 `src/app.ts` 或其他 Service 文件的关键代码行打上断点。
|
||||
2. 在 VS Code 调试面板选择“🍒 附加到 Cherry Studio (TS 联调)”并运行。
|
||||
3. 在 Cherry Studio 中触发任意工具调用。
|
||||
|
||||
- **预期结果**:
|
||||
|
||||
- VS Code 底部状态栏变色,表示成功抓取到 Cherry Studio 的底层 Node 进程。
|
||||
- 代码执行将暂停在设置了断点的位置,允许开发者查看当前变量与调用栈。
|
||||
|
||||
---
|
||||
|
||||
## 许可声明
|
||||
|
||||
本文档采用 **知识共享署名--相同方式共享 4.0 国际许可协议 (CC BY--SA 4.0)** 进行许可,© 2025-2026 Gitconomy Research.
|
||||
|
|
@ -0,0 +1,285 @@
|
|||
<!--
|
||||
---
|
||||
title: Arabica Sprint12系统架构设计说明
|
||||
description: Project Caffeine 提示词策略 MCP Server Sprint 2 架构设计,聚焦于 MCP Prompts 原语接入、多维思维框架组装及底层角色矩阵设计。
|
||||
version: v1.0.0 (Arabica) - Sprint 2
|
||||
author: Gitconomy Research-郭晧
|
||||
date: 2026-03-05
|
||||
type: Architecture Design
|
||||
file: project-caffeine-mvp-sprint2-architecture-design.md
|
||||
tags:
|
||||
- Project Caffeine
|
||||
- MCP Server
|
||||
- Sprint 2
|
||||
- Prompt Strategy
|
||||
- Prompts
|
||||
- Node.js
|
||||
license: CC BY-SA 4.0
|
||||
status: Active
|
||||
---
|
||||
-->
|
||||
# Arabica Sprint2 系统架构设计说明
|
||||
|
||||
## 1. Sprint2 设计概述
|
||||
|
||||
**Sprint 1** 实现了基于 **5 Whys** 的单一思维框架工具调用,验证了 MCP 协议下本地策略服务与大模型协同的可行性。
|
||||
|
||||
**Sprint 2** 的核心目标是将提示词策略 Server(S2)从“单点工具”升级为“多维思维框架引擎”,通过引入 MCP 的 **Prompts 原语**,将多种经典思维框架(5W3H、SCQA、SWOT、PESTLE 等)作为可复用的模板暴露给大模型,并开发意图拆解工具,使系统能够自动将用户模糊的自然语言查询拆解为专业检索词,从而大幅提升研究的广度与深度。
|
||||
|
||||
**关键功能**:
|
||||
|
||||
- **Prompts 原语接入**:实现 `prompts/list` 和 `prompts/get` 接口,向客户端(Cherry Studio)注册多个静态思维框架模板。
|
||||
- **多维思维框架库**:内置 5W3H、SCQA、SWOT、PESTLE 等框架,每个框架包含结构化提示词模板。
|
||||
- **意图拆解工具**:开发 `generate_search_queries` 工具,将用户原始查询拆解为 3~5 个专业检索词,用于后续文献检索(Sprint 3)。
|
||||
- **角色矩阵与输出规范**:设计多智能体角色矩阵雏形,并在提示词中通过 Few-Shot 示例约束输出格式(如强制 Markdown 结构)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 功能实现步骤说明
|
||||
|
||||
典型用户查询流程(参见图 2-1):
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User as 用户
|
||||
participant Client as Cherry Studio (MCP Client)
|
||||
participant LLM as 大模型
|
||||
participant S2 as 提示词策略 Server (S2)
|
||||
|
||||
User->>Client: 输入查询Q
|
||||
Client->>LLM: 传递查询Q
|
||||
LLM->>Client: 决策需要思维框架
|
||||
Client->>S2: prompts/get (框架=SCQA)
|
||||
S2-->>Client: 返回SCQA模板
|
||||
Client->>LLM: 组合模板+Q,请求推理
|
||||
LLM->>Client: 需要检索词,调用工具
|
||||
Client->>S2: tools/call (generate_search_queries, query=Q)
|
||||
S2->>S2: 意图拆解逻辑
|
||||
S2-->>Client: 返回检索词列表
|
||||
Client->>LLM: 提供检索词,继续推理
|
||||
LLM-->>Client: 生成深度报告
|
||||
Client-->>User: 展示结果
|
||||
```
|
||||
|
||||
1. **用户输入自然语言查询**(如“分析新能源汽车行业竞争格局”)。
|
||||
2. **Cherry Studio 内大模型决策**:判断需要调用多维思维框架辅助分析。
|
||||
3. **客户端发起 `prompts/get` 请求**:选择合适的思维框架(例如 SCQA),获取模板内容。
|
||||
4. **客户端组合提示词**:将模板与用户查询结合,形成增强提示词。
|
||||
5. **大模型初步推理**:可能识别出需要更专业的检索词,于是调用 `generate_search_queries` 工具。
|
||||
6. **服务端执行意图拆解**:`intentService` 根据查询生成检索词列表。
|
||||
7. **工具结果返回**:客户端获得检索词,可结合文献查询 Server(Sprint 3)进行下一步检索。
|
||||
8. **最终推理与输出**:大模型利用框架模板和检索结果生成结构化深度报告。
|
||||
|
||||
---
|
||||
|
||||
## 3. 系统组件架构设计
|
||||
|
||||
### 3.1 项目结构更新(基于 Sprint 1)
|
||||
|
||||
```markdown
|
||||
project-caffeine/
|
||||
│
|
||||
├── src/
|
||||
│ ├── controllers/
|
||||
│ │ ├── promptsController.js # 处理 prompts/list 和 prompts/get
|
||||
│ │ ├── toolsController.js # 处理工具调用(含原有5Whys+新增意图拆解)
|
||||
│ │ └── resourcesController.js # (保留,Sprint3扩展)
|
||||
│ ├── services/
|
||||
│ │ ├── promptService.js # 升级:管理多框架模板库,根据框架名返回结构化提示词
|
||||
│ │ ├── intentService.js # 新增:意图拆解逻辑,生成检索词
|
||||
│ │ └── resourceService.js # (保留)
|
||||
│ ├── models/
|
||||
│ │ ├── schemas.js # Zod校验(新增prompts参数校验)
|
||||
│ │ └── frameworks/ # 思维框架定义目录
|
||||
│ │ ├── 5w3h.json
|
||||
│ │ ├── scqa.json
|
||||
│ │ ├── swot.json
|
||||
│ │ └── pestle.json
|
||||
│ └── app.js # 主入口:注册 Prompts 和 Tools
|
||||
│
|
||||
├── .vscode/launch.json # 调试配置(不变)
|
||||
├── config/config.js # 配置(可扩展框架路径)
|
||||
└── package.json # 依赖(不变)
|
||||
```
|
||||
|
||||
|
||||
*表 2-1:Sprint 2 新增/修改文件说明*
|
||||
|
||||
|文件/目录|类型|功能说明|
|
||||
|---|---|---|
|
||||
|`src/controllers/promptsController.js`|新增|处理 `prompts/list` 和 `prompts/get` 请求,调用 `promptService` 获取模板|
|
||||
|`src/services/intentService.js`|新增|实现 `generate_search_queries` 工具的核心逻辑:基于规则或小模型将自然语言拆解为检索词|
|
||||
|`src/models/frameworks/`|新增|存放各思维框架的 JSON 定义,包含名称、描述、模板内容、参数占位符等|
|
||||
|`src/services/promptService.js`|修改|从静态文件加载框架库,提供 `listFrameworks()` 和 `getFramework(name, params)` 方法|
|
||||
|`src/controllers/toolsController.js`|修改|增加对 `generate_search_queries` 的路由分发|
|
||||
|`src/models/schemas.js`|修改|增加 `generate_search_queries` 的输入参数校验(如 `query` 字符串)|
|
||||
|
||||
### 3.2 系统模块架构图
|
||||
|
||||
*图 2-2:Sprint 2 组件架构与数据流*
|
||||
|
||||

|
||||
|
||||
Sprint 2 系统组件架构在 Sprint 1 的基础上,将提示词策略 Server(S2)从单一工具升级为多维思维框架引擎。核心组件包括:
|
||||
|
||||
- 控制器层:新增 promptsController.js 专门处理 MCP Prompts 原语(prompts/list 和 prompts/get),toolsController.js 扩展支持 generate_search_queries 工具调用。
|
||||
|
||||
- 服务层:promptService.js 升级为多框架管理器,从 models/frameworks/ 目录加载 JSON 定义的思维模板(5W3H、SCQA、SWOT、PESTLE 等);新增 intentService.js 实现意图拆解逻辑,将自然语言查询转化为专业检索词。
|
||||
|
||||
- 模型层:schemas.js 增加 Prompts 参数和意图拆解工具的 Zod 校验;frameworks/ 目录以 JSON 形式存放各框架的元数据、模板内容和角色定义。
|
||||
|
||||
- 主入口:app.js 同时注册 Prompts 和 Tools 能力,通过 stdio 与客户端通信。
|
||||
|
||||
该架构的核心数据链路分为四步:客户端通过 prompts/get 获取匹配的思维框架模板;服务端返回结构化提示词;大模型在推理中可调用 generate_search_queries 获取检索词;最终通过模板内嵌的系统消息(含角色矩阵和 Few-Shot 示例)约束输出格式,生成符合规范的深度报告。这一设计为后续文献检索(Sprint 3)和递归深度研究奠定了坚实的“大脑”基础。
|
||||
|
||||
---
|
||||
|
||||
## 4. MCP 标准通信接口设计
|
||||
|
||||
### 4.1 Prompts 原语
|
||||
|
||||
**`prompts/list` 响应示例**
|
||||
向客户端声明可用的思维框架列表:
|
||||
|
||||
```json
|
||||
{
|
||||
"prompts": [
|
||||
{
|
||||
"name": "5w3h",
|
||||
"description": "5W3H 分析法:从 What、Why、Who、When、Where、How、How much、How feel 八个维度拆解问题",
|
||||
"arguments": [
|
||||
{
|
||||
"name": "topic",
|
||||
"description": "需要分析的主题",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "scqa",
|
||||
"description": "SCQA 架构:Situation、Complication、Question、Answer,适用于问题分析与方案构建",
|
||||
"arguments": [
|
||||
{
|
||||
"name": "situation",
|
||||
"description": "背景描述",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "swot",
|
||||
"description": "SWOT 分析:Strengths、Weaknesses、Opportunities、Threats",
|
||||
"arguments": [
|
||||
{
|
||||
"name": "entity",
|
||||
"description": "分析对象(企业、项目等)",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "pestle",
|
||||
"description": "PESTLE 宏观环境分析:Political、Economic、Social、Technological、Legal、Environmental",
|
||||
"arguments": [
|
||||
{
|
||||
"name": "domain",
|
||||
"description": "行业或领域",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
**`prompts/get` 请求与响应示例**
|
||||
客户端请求 `scqa` 框架,并提供参数:
|
||||
|
||||
```json
|
||||
// 请求
|
||||
{
|
||||
"name": "scqa",
|
||||
"arguments": {
|
||||
"situation": "新能源汽车行业竞争日益激烈"
|
||||
}
|
||||
}
|
||||
// 响应
|
||||
{
|
||||
"description": "SCQA 分析框架",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": {
|
||||
"type": "text",
|
||||
"text": "请使用 SCQA 架构分析以下情境:\n情境 (Situation):新能源汽车行业竞争日益激烈。\n\n请依次构建:\n1. 复杂化 (Complication):指出情境中存在的矛盾或挑战。\n2. 问题 (Question):基于复杂化提炼出核心问题。\n3. 答案 (Answer):提出解决问题的初步方案或分析路径。\n\n输出格式要求:使用 Markdown 标题分节,每个部分至少 200 字。"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 新增 Tool:`generate_search_queries`
|
||||
|
||||
**工具声明 (`tools/list` 追加)**
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "generate_search_queries",
|
||||
"description": "将用户的自然语言查询拆解为 3-5 个专业的学术检索词,用于后续文献检索",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {
|
||||
"type": "string",
|
||||
"description": "用户的原始查询语句,例如:新能源汽车电池回收技术难点"
|
||||
}
|
||||
},
|
||||
"required": ["query"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**工具调用响应 (`tools/call`)**
|
||||
|
||||
```json
|
||||
// 请求参数
|
||||
{
|
||||
"query": "新能源汽车电池回收技术难点"
|
||||
}
|
||||
// 响应
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "[\"动力电池梯次利用技术\", \"废旧锂离子电池回收工艺\", \"电池拆解自动化\", \"有价金属提取效率\", \"环保法规与回收成本\"]"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 角色矩阵与输出规范(内置于 Prompt 模板)
|
||||
|
||||
在每个思维框架的模板中,通过系统消息(System Prompt)注入角色定义和输出格式要求。例如在 SWOT 模板中:
|
||||
|
||||
```json
|
||||
{
|
||||
"role": "system",
|
||||
"content": {
|
||||
"type": "text",
|
||||
"text": "你是一位资深的战略分析顾问,擅长使用 SWOT 框架进行竞争态势分析。请严格按照以下结构输出 Markdown 报告:\n# SWOT 分析报告:{entity}\n## 1. 优势 (Strengths)\n- 内部积极因素...\n## 2. 劣势 (Weaknesses)\n- 内部消极因素...\n## 3. 机会 (Opportunities)\n- 外部积极因素...\n## 4. 威胁 (Threats)\n- 外部消极因素...\n\n每个要点必须附上简要论证,报告总字数不低于 800 字。"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 总结
|
||||
|
||||
Sprint 2 通过接入 MCP `Prompts` 原语,扩展了系统的多维思维框架,为大模型提供了结构化的推理模板。通过拆解用户意图,系统能够更高效地生成标准化的响应内容,推动开源协作流程的自动化与优化。随着 Sprint 2 的完成,系统将在后续迭代中逐步完善与扩展,推动开源项目管理和协作效率的提升。
|
||||
|
||||
---
|
||||
|
||||
## 许可声明
|
||||
|
||||
本文档采用 **知识共享署名--相同方式共享 4.0 国际许可协议 (CC BY--SA 4.0)** 进行许可,© 2025-2026 Gitconomy Research.
|
||||
|
|
@ -0,0 +1,214 @@
|
|||
<!--
|
||||
---
|
||||
title: Arabica Sprint3 系统架构设计说明
|
||||
description: Project Caffeine 文献查询功能系统架构设设计,聚焦外围学术检索与双轨制数据落盘模块的实现。
|
||||
type: Architecture Design
|
||||
version: v1.0.0 (Arabica) - Sprint 3
|
||||
file: arabica-sprint3-architecture-specification.md
|
||||
author: Gitconomy Research-郭晧
|
||||
date: 2026-03-11
|
||||
tags:
|
||||
- Project Caffeine
|
||||
- MCP Server
|
||||
- Sprint 3
|
||||
- Literature Search
|
||||
- PKM
|
||||
- Node.js
|
||||
license: CC BY-SA 4.0
|
||||
status: Active
|
||||
---
|
||||
-->
|
||||
# Arabica Sprint3 系统架构设计说明
|
||||
|
||||
## 1. Sprint3 设计概述
|
||||
|
||||
**Sprint 2** 完成了多维思维框架引擎的建设(基于 Prompt 原语),并实现了对本地知识库(Obsidian)的基本操作。
|
||||
|
||||
**Sprint 3** 的核心目标是在现有本地知识管理能力之上,增强外部学术检索、实现大模型意图路由,并彻底解决大模型在工具调用中的幻觉与崩溃问题。本次迭代将系统从“被动提供 Prompt 的工具箱”升级为“能够听懂自然语言意图并主动调度工具的高级智能体(Agent)”。
|
||||
|
||||
**核心架构升级与关键功能**:
|
||||
|
||||
* **意图驱动路由**:删除了向客户端 UI 暴露的 Prompt 原语,将底层收口至 `toolsController`。通过系统提示词定义 4 大意图(文献检索、框架分析、保存内容、本地笔记分析),让大模型自主路由,实现全自然语言对话交互。
|
||||
* **学术文献检索接入**:集成 arXiv API,实现 `search_arxiv` 工具,支持提取核心关键字进行检索,并自动格式化为大模型易读的 Markdown 文本。
|
||||
* **高容错落盘机制**:重构 `save_note` 工具,通过将强类型校验放宽至 `z.any()` 结合底层 `JSON.stringify` 自动序列化,彻底解决了大模型生成长文本时的 JSON 转义崩溃问题。
|
||||
* **抗死循环框架引擎**:将静态框架 JSON 的读取封装为后台私有工具 `fetch_framework_template`。通过底层剥离 JSON 外壳并强行注入“🛑立即停止调用工具”的指令,解决大模型读取复杂 JSON 后的死循环调用幻觉。
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 2. 功能实现步骤说明
|
||||
|
||||
典型用户查询与文献检索流程(参见图 3-1):
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User as 用户
|
||||
participant Client as Cherry Studio (MCP Client)
|
||||
participant LLM as 大模型
|
||||
participant Tools as Tools Controller (路由中心)
|
||||
participant Arxiv as arXiv Service
|
||||
participant Vault as Resource Service (本地知识库)
|
||||
|
||||
User->>Client: "用 SWOT 框架分析一下量子计算,并查查最新文献"
|
||||
Client->>LLM: 传递自然语言意图
|
||||
|
||||
%% 意图 1:查询文献
|
||||
LLM->>Client: 识别意图1,请求调用 search_arxiv
|
||||
Client->>Tools: handleToolCall('search_arxiv', {query: 'quantum computing'})
|
||||
Tools->>Arxiv: 发起 API 检索
|
||||
Arxiv-->>Tools: 返回文献元数据
|
||||
Tools-->>Client: 返回格式化文献列表
|
||||
|
||||
%% 意图 2:框架分析
|
||||
LLM->>Client: 识别意图2,请求调用 fetch_framework_template
|
||||
Client->>Tools: handleToolCall('fetch_framework_template', {framework_name: 'swot'})
|
||||
Tools-->>Client: 返回纯文本 Prompt 并注入【强制刹车指令】
|
||||
|
||||
%% 大模型思考与输出
|
||||
LLM->>Client: 停止调用工具,直接在聊天框输出分析报告
|
||||
LLM->>Client: 报告末尾主动询问:"是否需要保存?"
|
||||
|
||||
%% 意图 3:保存落盘
|
||||
User->>Client: "是的,保存下来"
|
||||
Client->>LLM: 传递确认指令
|
||||
LLM->>Client: 识别意图3,请求调用 save_note
|
||||
Client->>Tools: handleToolCall('save_note', {filename: '...', content: <长文本或JSON对象>})
|
||||
Tools->>Vault: 自动容错序列化并写入 Markdown
|
||||
Vault-->>Tools: 写入成功
|
||||
Tools-->>Client: 返回成功提示
|
||||
LLM-->>User: 告知已保存至本地笔记
|
||||
```
|
||||
|
||||
*图 3-1:Sprint 3 核心工作流——文献检索与标准化落盘*
|
||||
|
||||
上述时序图展示了系统在 Sprint 3 架构下,如何通过“意图识别 + 底层容错”处理一次包含多个动作的复杂用户指令。具体步骤解析如下:
|
||||
|
||||
1. **多重意图触发**:用户在客户端输入包含复合诉求的自然语言指令(例如:“*用 SWOT 框架分析一下量子计算,并查查最新文献*”)。
|
||||
2. **意图 1(学术检索)执行**:大模型首先解析出“查找文献”的意图,自主生成检索词并调用 `search_arxiv` 工具。`toolsController` 充当路由网关,将请求分发给 arXiv 服务,拉取真实的学术数据,并将晦涩的 XML 格式化为清晰的 Markdown 列表返回给大模型。
|
||||
3. **意图 2(框架读取与防呆拦截)执行**:大模型紧接着解析出“SWOT 分析”意图,调用后台私有工具 `fetch_framework_template`。此时,`toolsController` 读取本地 JSON 配置,**主动剥离 JSON 外壳**,提取出纯文本 Prompt,并在头部强行拼接**“🛑立即停止调用任何工具”**的最高优先级刹车指令。
|
||||
4. **大模型思考与安全输出**:大模型接收到“刹车指令”后,其工具调用的“冲动”被成功阻断(避免了因反复读取 JSON 导致的无限死循环幻觉)。大模型转为文本生成模式,直接在聊天框为用户输出详尽的分析报告。报告输出完毕后,大模型严格遵从系统纪律,主动向用户发起确认:“*是否需要保存?*”
|
||||
5. **意图 3(授权落盘与容错兜底)执行**:用户回复确认(如:“*是的,保存下来*”)。大模型获取授权,识别出保存意图,调用 `save_note` 工具并将上万字的分析报告作为参数传入。即使大模型在传参时发生了引号转义错误或误传了 JSON 对象,`toolsController` 底层的 **z.any() 容错与自动序列化机制** 也会完美兜底,确保长文本安全无损地写入本地 Obsidian 知识库。
|
||||
6. **链路闭环**:本地文件写入成功,系统通过大模型向用户反馈最终的成功状态,本次复杂交互圆满结束。
|
||||
|
||||
---
|
||||
|
||||
## 3. 系统组件架构设计
|
||||
|
||||
### 3.1 核心模块职责映射
|
||||
|
||||
| 模块 / 服务 | 职责说明 |
|
||||
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`app.ts`** | MCP Server 主入口。负责注册 `search_arxiv`、`fetch_framework_template`、`save_note`、`list_local_notes`、`read_local_note` 五大工具。**关键设计:**将 `user_input` 和 `payload` 参数类型设定为 `z.any()` 以规避底层 `AI_JSONParseError`。 |
|
||||
| **`toolsController.ts`** | **大脑与防火墙**。接收工具请求。负责处理大模型的畸形传参(如对象序列化)、剥离 JSON 框架外壳、并在返回给大模型前拼接最高优先级的系统指令。 |
|
||||
| **`arxivService.ts`** | 学术检索服务。负责与外部数据库通信,提取标题、摘要、链接等核心信息。 |
|
||||
| **`resourceService.ts`** | 本地文件系统网关。负责安全的本地 Markdown 文件读写与列表扫描。 |
|
||||
|
||||
### 3.2 基于 Sprint 3 的项目目录结构
|
||||
|
||||
Sprint 3 移除了繁杂的独立 Controller,将逻辑高度内聚,形成了以 `toolsController.ts` 为核心的分发架构:
|
||||
|
||||
```
|
||||
project-caffeine/
|
||||
│
|
||||
├── src/
|
||||
│ ├── controllers/
|
||||
│ │ ├── toolsController.ts # (修改)【核心路由】处理 5 大工具的分发、防呆与容错处理
|
||||
│ │ └── resourcesController.ts # (修改)增加对 literature:// 资源的支持
|
||||
│ ├── services/
|
||||
│ │ ├── resourceService.ts # (修改)扩展支持 literature 协议的文件读写与扫描
|
||||
│ │ └── arxivService.ts # (新增)【核心】封装 arXiv API 调用与 XML 解析
|
||||
│ ├── models/
|
||||
│ │ ├── schemas.ts # (修改)增加新工具的输入输出 Zod 校验
|
||||
│ │ └── frameworks/ # (不变)思维框架定义 (静态 JSON 模板)
|
||||
│ └── app.ts # (修改)MCP Server 主入口,注册新增的 Tools 和 Resources
|
||||
│
|
||||
├── tsconfig.json # (修改)TypeScript 编译与输出配置调整
|
||||
└── package.json # (修改)新增第三方依赖(如 axios, zod, fast-xml-parser 等)
|
||||
```
|
||||
|
||||
*表 3-1:Sprint 3 新增/修改文件说明*
|
||||
|
||||
| **文件/目录** | **状态** | **功能说明** |
|
||||
| ---------------------------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `src/controllers/toolsController.ts` | **修改** | **【核心路由大脑】** 统一接管 5 大工具的分发。负责拦截大模型的畸形传参并执行兜底序列化(JSON 容错);负责读取并剥离本地框架 JSON 外壳,注入“🛑立即停止调用工具”的强指令,防止大模型陷入死循环。 |
|
||||
| `src/controllers/resourcesController.ts` | **修改** | **【资源端点】** 增加对 `literature://` 协议的支持,使大模型可以通过统一接口读取已落盘的学术文献 Markdown 文件。 |
|
||||
| `src/services/arxivService.ts` | **新增** | **【学术网关】** 负责与外部 arXiv 数据库通信。封装 HTTP 请求,解析复杂的 XML 响应格式,提取标题、摘要、链接等核心元数据,并返回大模型友好的 Markdown 纯文本列表。 |
|
||||
| `src/services/resourceService.ts` | **修改** | **【本地系统交互】** 扩展现有的文件操作能力。负责安全的本地 Markdown 文件读取与写入,并在底层确保路径防穿越安全。 |
|
||||
| `src/models/schemas.ts` | **修改** | **【安全防线】** 增加新工具的强类型参数校验。针对大模型易错的落盘操作,将 `save_note` 的入参校验适度放宽至 `z.any()` 以配合 Controller 层的容错。 |
|
||||
| `src/models/frameworks/` | **不变** | 存放 SWOT、SCQA 等多维思维框架的静态 JSON 配置模板。 |
|
||||
| `src/app.ts` | **修改** | **【MCP 注册中心】** 实例化 MCP Server,统一定义 `server.tool` 和 `server.resource`。显式声明 `type: "text" as const` 以解决 TypeScript 的强类型推断编译报错。 |
|
||||
| `package.json` | **修改** | 新增处理 HTTP 请求和数据解析的核心依赖,配置 `npm run build` 和 `npm run test` 等关键工程化脚本。 |
|
||||
|
||||
### 3.3 系统模块架构图
|
||||
|
||||
*图 3-2:Sprint 3 组件架构与数据流*
|
||||
|
||||

|
||||
|
||||
- **控制器层**:`toolsController` 接收工具调用请求,分发给对应的服务;`resourcesController` 处理资源读取请求(可复用原有逻辑,仅需扩展协议类型)。
|
||||
- **服务层**:
|
||||
- `literatureService`:调用 `apiClients` 并发请求 arXiv 和 Semantic Scholar,对返回结果进行字段映射、去重、排序,输出标准化的文献 JSON 数组。
|
||||
- `storageService`:接收文献 JSON 和可选标签,调用 `yamlHelper` 生成 YAML Frontmatter,组合 Markdown 正文(默认为文献摘要),写入文件,返回文件绝对路径。
|
||||
- `resourceService`:扩展现有功能,支持读取 `literature://` 协议下的 Markdown 文件(实际仍从同一知识库目录读取)。
|
||||
- **工具层**:`apiClients` 封装 HTTP 客户端,处理速率限制和错误重试;`yamlHelper` 负责将 JSON 字段转换为符合 YAML 规范的字符串。
|
||||
- **模型层**:`literatureSchema.js` 定义文献的标准结构,`schemas.js` 集成新工具的 Zod 校验。
|
||||
|
||||
---
|
||||
|
||||
## 4. MCP 标准通信接口设计 (Tools)
|
||||
|
||||
### 4.1 意图 1:学术文献查询 (`search_arxiv`)
|
||||
|
||||
* **描述**:通过核心关键字检索 arXiv 学术库。
|
||||
* **输入 Schema**:`{ query: z.string() }`
|
||||
* **容错与输出**:在 Service 层自动过滤无效结果,将复杂的 XML 解析为带有 Markdown 排版的纯文本列表(标题、链接、摘要),防止大模型处理复杂结构时产生幻觉。
|
||||
|
||||
### 4.2 意图 2:框架模板获取 (`fetch_framework_template`)
|
||||
|
||||
* **描述**:供大模型内部读取 SWOT、SCQA 等分析模板。不对外暴露 UI。
|
||||
* **输入 Schema**:`{ framework_name: z.string() }`
|
||||
* **核心逻辑**:
|
||||
1. 读取本地静态 JSON 配置。
|
||||
2. **剥离外壳**:提取出纯文本的 `system` 和 `user` prompt,丢弃 JSON 结构。
|
||||
3. **强制刹车**:在返回的开头拼接 `【🛑立即停止调用任何工具,请直接在聊天框输出...】`,阻断大模型的“工具调用死循环”。
|
||||
|
||||
### 4.3 意图 3:内容落盘保存 (`save_note`)
|
||||
|
||||
* **描述**:将大模型生成的长文本或检索结果保存到本地 Obsidian 知识库。
|
||||
* **输入 Schema**:`{ filename: z.string(), content: z.any() }`
|
||||
* **核心逻辑**:大模型极易在长文本转义时出错。系统允许传入 `z.any()`(即接收 JSON Object),若发现传入的是对象而非字符串,`toolsController` 会自动在底层执行 `JSON.stringify(content, null, 2)` 进行兜底序列化。
|
||||
|
||||
### 4.4 意图 4:本地笔记分析 (`list_local_notes` & `read_local_note`)
|
||||
|
||||
* **描述**:继承 Sprint 2 的能力,大模型可主动列出本地笔记清单,并根据需要读取单篇或多篇笔记的内容以进行综合总结。
|
||||
|
||||
---
|
||||
|
||||
## 5. 安全、边界与质量保障
|
||||
|
||||
### 5.1 防越权落盘 (Authorization Boundary)
|
||||
|
||||
在系统级提示词(System Prompt)中设置了**“绝对红线”**:严禁大模型在没有主动询问并获得用户明确同意(如回复“保存”或“是”)的情况下,私自调用 `save_note` 工具。写盘动作必须由用户自然语言最终确认。
|
||||
|
||||
### 5.2 强类型收敛与 TS 错误阻断
|
||||
|
||||
针对 MCP SDK 要求 `content` 数组内的 `type` 必须为严格字面量 `"text"` 的限制,在 `app.ts` 中通过 `type: "text" as const` 进行显式断言,消除了编译期的类型推断模糊问题。
|
||||
|
||||
### 5.3 自动化测试覆盖
|
||||
|
||||
Sprint 3 引入了基于 Jest 的自动化测试套件:
|
||||
|
||||
* `arxivService.test.ts`:通过 Mock `global.fetch`,测试 XML 解析的健壮性和网络异常处理。
|
||||
* `toolsController.test.ts`:深度覆盖工具的路由分发、`save_note` 的 JSON 畸形对象兜底序列化逻辑,以及 `fetch_framework_template` 是否成功注入了防死循环的“刹车指令”。
|
||||
|
||||
---
|
||||
|
||||
## 6. 总结
|
||||
|
||||
Sprint 3 在 Sprint 2 的本地知识管理基础上,通过新增文献检索与标准化存储模块,显著增强了系统的外部知识获取能力。我们通过将“决策权上交给大模型(意图识别)”**与**“容错权下放给底层代码(参数宽容与刹车指令)”相结合,解决了大语言模型在多工具、长文本复杂场景下的状态机崩溃难题,为后续更复杂的学术分析(Sprint 4 & 5)奠定了基础。
|
||||
|
||||
---
|
||||
|
||||
## 许可声明
|
||||
|
||||
本文档采用 **知识共享署名--相同方式共享 4.0 国际许可协议 (CC BY--SA 4.0)** 进行许可,© 2025-2026 Gitconomy Research.
|
||||
|
|
@ -0,0 +1,291 @@
|
|||
<!--
|
||||
---
|
||||
title: Arabica Sprint3 开发指南:意图驱动路由与高可用容错架构
|
||||
description: 基于 Sprint 3 架构设计,指导开发者搭建全自然语言意图驱动的 MCP Server,实现学术文献检索、本地知识库无缝对接,并在底层构建抗死循环与强容错机制。
|
||||
type: Development Guide
|
||||
version: v1.0.0 (Arabica) - Sprint 3
|
||||
file: arabica-sprint3-development-specification.md
|
||||
author: Gitconomy Research-郭晧
|
||||
date: 2026-03-11
|
||||
tags:
|
||||
- Project Caffeine
|
||||
- MCP Server
|
||||
- Sprint 3
|
||||
- Intent-Driven
|
||||
- Fault Tolerance
|
||||
- arXiv API
|
||||
- Node.js
|
||||
license: CC BY-SA 4.0
|
||||
status: Active
|
||||
---
|
||||
-->
|
||||
# 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。
|
||||
|
||||
<br>
|
||||
|
||||
下图展示了模块间的静态关系:
|
||||
|
||||
```mermaid
|
||||
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 整体函数调用关系图
|
||||
|
||||
代码段
|
||||
|
||||
```mermaid
|
||||
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 核心代码解析
|
||||
|
||||
```typescript
|
||||
// ==========================================
|
||||
// 意图 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 中化身为**“智能体的大脑与护栏”**。
|
||||
|
||||
1. **抗死循环(Anti-Loop)**:处理 `fetch_framework_template` 时,拦截原始 JSON 结构,提取纯文本,并强行拼接业务指令阻断模型乱调工具。
|
||||
|
||||
2. **长文本容错(Fallback Serialization)**:处理 `save_note` 时,拦截畸形的 JSON 入参并自动序列化,确保平滑写盘。
|
||||
|
||||
|
||||
#### 3.2.2 核心逻辑流程与代码
|
||||
|
||||
##### 3.2.2.1 `handleFetchFramework` (抗死循环剥离器)
|
||||
|
||||
1. **调用序列图**
|
||||
|
||||
```mermaid
|
||||
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: 遵守指令,停止工具调用,开始输出分析文本
|
||||
```
|
||||
|
||||
2. **核心代码**
|
||||
|
||||
```typescript
|
||||
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` (兜底序列化)
|
||||
|
||||
**核心代码**
|
||||
|
||||
```typescript
|
||||
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 核心逻辑流
|
||||
|
||||
```mermaid
|
||||
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 自动化测试套件,重点关注工具控制层的路由与容错能力:
|
||||
|
||||
1. **网络拦截与 Mock**:在 `arxivService.test.ts` 中,必须通过 `global.fetch = jest.fn()` 拦截真实网络请求,确保测试在离线环境下秒级通过。
|
||||
|
||||
2. **容错机制断言**:在 `toolsController.test.ts` 中,编写专门针对 `save_note` 传入 JSON Object 的断言(Assertion),验证 `resourceService.saveNote` 被调用时接收到的是否为序列化后的字符串。
|
||||
|
||||
3. **刹车指令探针**:测试 `fetch_framework_template` 时,断言返回的 `content.text` 必须 `toContain('立即停止调用任何工具')`,确保防死锁底座生效。
|
||||
|
||||
---
|
||||
|
||||
## 许可声明
|
||||
|
||||
本文档采用 **知识共享署名-相同方式共享 4.0 国际许可协议 (CC BY-SA 4.0)** 进行许可,© 2025-2026 Gitconomy Research.
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
<!--
|
||||
---
|
||||
title: Arabica v0.1.1 MCP Inspector 测试指南
|
||||
description: Arabica v0.1.1如何通过该工具进行协议交互与集成测试?
|
||||
type: Testing Guide
|
||||
version: 1.0.0
|
||||
file: arabica-sprint2-mcp-inspector-testing-guide.md
|
||||
author: Gitconomy Research-郭晧
|
||||
date: 2026-03-07
|
||||
tags:
|
||||
- Project Caffeine
|
||||
- MCP Inspector
|
||||
- Integration Testing
|
||||
- Quality Assurance
|
||||
- Node.js
|
||||
license: CC BY-SA 4.0
|
||||
status: Active
|
||||
---
|
||||
-->
|
||||
## 1. 准备工作
|
||||
|
||||
在启动 Inspector 之前,请确保您的项目已经完成了最新的代码编译。
|
||||
|
||||
1. 打开终端,进入项目根目录 (`project-caffeine`)。
|
||||
|
||||
2. 确保所有依赖已安装:
|
||||
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
3. 编译 TypeScript 代码并复制 JSON 配置文件到 `dist` 目录:
|
||||
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 启动 MCP Inspector
|
||||
|
||||
MCP Inspector 作为一个独立的 Node 包运行,它会通过 STDIO (标准输入/输出) 唤起我们的 Server。
|
||||
|
||||
在项目根目录下,运行以下命令启动 Inspector:
|
||||
|
||||
```
|
||||
npx @modelcontextprotocol/inspector node dist/app.js //app.js的绝对地址
|
||||
```
|
||||
|
||||
**启动成功后:**
|
||||
|
||||
终端会输出一个本地链接(例如是 `http://localhost:6274` 或类似地址)。在浏览器中打开该链接,即可进入 MCP Inspector 可视化界面。
|
||||
|
||||
---
|
||||
|
||||
## 3. 界面概览与测试流程
|
||||
|
||||
MCP Inspector 的界面主要分为:
|
||||
|
||||
- **顶栏/侧边栏**:用于在 **Prompts(提示词)**、**Tools(工具)** 和 **Resources(资源)** 之间切换。
|
||||
|
||||
- **左侧列表**:显示当前 Server 声明的所有可用项。
|
||||
|
||||
- **右侧面板**:用于输入参数并执行请求,同时展示 Server 返回的原始 JSON 结果。
|
||||
|
||||
|
||||
### 3.1 测试 Prompts (多维思维框架)
|
||||
|
||||
我们在 Sprint 2 中注册了 5 个核心思维框架。
|
||||
|
||||
**测试步骤:**
|
||||
|
||||
1. 在顶部/侧边菜单中选择 **Prompts**。
|
||||
|
||||
2. 左侧列表中应该会列出 `scqa`, `5whys`, `5w3h`, `swot`, `pestle`。
|
||||
|
||||
3. **点击 `scqa`**:
|
||||
|
||||
- 右侧会显示该 Prompt 需要的参数表单。
|
||||
|
||||
- **situation** (必填): 输入 `一家传统零售企业过去三年线上销售额年均增长仅3%`
|
||||
|
||||
- **context** (可选): 输入 `公司拥有300家实体店网络`
|
||||
|
||||
- **objective** (可选): 输入 `两年内线上销售增速达到15%`
|
||||
|
||||
4. 点击 **Run / Get Prompt** 按钮。
|
||||
|
||||
5. **验证结果**:
|
||||
|
||||
- 在下方的结果视图中,您应该能看到一条完整的 `messages` 数组。
|
||||
|
||||
- 检查最后一个 `user` 角色的 `content`,确认您的输入已被正确替换到模板中,并且模板底部包含了我们在 JSON 配置文件中定义的严谨 JSON 输出约束。
|
||||
|
||||
|
||||
### 3.2 测试 Tools (意图拆解与本地文件)
|
||||
|
||||
我们在 Sprint 2 中保留并扩展了工具类方法。
|
||||
|
||||
**测试步骤:**
|
||||
|
||||
1. 切换到 **Tools** 面板。
|
||||
|
||||
2. 左侧列表应显示 `generate_search_queries`, `list_local_notes`, `read_local_note`, `save_note`。
|
||||
|
||||
3. **测试意图拆解 (`generate_search_queries`)**:
|
||||
|
||||
- 在右侧的 `query` 参数框中输入:`新能源汽车电池回收技术难点`
|
||||
|
||||
- 点击执行。
|
||||
|
||||
- **验证结果**:检查输出的 `content` 数组,应该返回一段包含 3-5 个专业检索词的 JSON 字符串。
|
||||
|
||||
4. **测试文件读取 (`read_local_note`)**(可选,需确保本地知识库路径配置正确):
|
||||
|
||||
- 输入 `filename` 为 `test.md`(假设您的 Vault 中有此文件)。
|
||||
|
||||
- 点击执行并查看返回的 Markdown 文本内容。
|
||||
|
||||
|
||||
### 3.3 测试 Resources (本地知识库注入)
|
||||
|
||||
通过 Resources,客户端可以直接浏览并挂载本地文件作为大模型上下文。
|
||||
|
||||
**测试步骤:**
|
||||
|
||||
1. 切换到 **Resources** 面板。
|
||||
|
||||
2. **列表测试**:
|
||||
|
||||
- Inspector 启动时会自动调用 `resources/list`。左侧应该会展示您在 `OBSIDIAN_VAULT_PATH` 目录下所有的 `.md` 文件列表。
|
||||
|
||||
3. **读取测试**:
|
||||
|
||||
- 点击列表中的任意一个笔记(URI 类似 `note://local/xxx.md`)。
|
||||
|
||||
- **验证结果**:右侧的 Content 区域应正确渲染或展示该 Markdown 文件的文本内容。
|
||||
|
||||
----
|
||||
|
||||
## 许可声明
|
||||
|
||||
本文档采用 **知识共享署名--相同方式共享 4.0 国际许可协议 (CC BY--SA 4.0)** 进行许可,© 2025-2026 Gitconomy Research.
|
||||
|
|
@ -0,0 +1,319 @@
|
|||
<!--
|
||||
---
|
||||
title: Arabica Sprint 3 代码单元测试样例
|
||||
description: 为 Arabica Sprint 3 核心服务与控制器层提供标准 Jest 测试用例示例,涵盖文献检索、工具分发、容错保存及本地笔记操作场景。
|
||||
type: System Development Guide
|
||||
version: v1.0.0 (Arabica) - Sprint 3
|
||||
file: arabica-sprint3- code-unit-test-specification.md
|
||||
author: Gitconomy Research-郭晧
|
||||
date: 2026-03-11
|
||||
tags:
|
||||
- Project Caffeine
|
||||
- Sprint 3
|
||||
- Jest
|
||||
- Unit Testing
|
||||
license: CC BY-SA 4.0
|
||||
status: Active
|
||||
---
|
||||
-->
|
||||
# Arabica Sprint 3 代码单元测试样例
|
||||
|
||||
## 1. 测试架构概览
|
||||
|
||||
在 Sprint 3 中,系统彻底从“提示词(Prompt)驱动”转向了“工具(Tool)驱动”。我们的测试重点也应该从原来的 Prompts 转移到**核心服务层(Services)和工具控制层(Controllers)**。
|
||||
|
||||
当前 Sprint 3 真实需要进行单元测试的核心文件如下:
|
||||
|
||||
1. **`src/services/arxivService.ts`**:测试连接 Arxiv API 并解析 XML 的能力。
|
||||
2. **`src/services/resourceService.ts`**:测试本地笔记的读取、扫描和保存能力。
|
||||
3. **`src/controllers/toolsController.ts`**:【测试重点】测试五大核心工具(文献、框架、存笔记、查笔记、读笔记)的路由分发和容错机制。
|
||||
|
||||
---
|
||||
|
||||
## 2. 核心测试场景设计与示例
|
||||
|
||||
为了让您的 Jest 测试跑通,以下是针对 Sprint 3 真实文件的测试用例编写指南及代码示例。您可以在 `src/services/__test__/` 和 `src/controllers/__test__/` 目录下创建或修改对应的 `.test.ts` 文件。
|
||||
|
||||
### 场景 A:测试文献检索服务 (`arxivService.test.ts`)
|
||||
|
||||
**目标**:验证 `searchArxiv` 函数是否能正确发起 HTTP 请求,并从 Arxiv 复杂的 XML 中提取出干净的标题和摘要。
|
||||
|
||||
|
||||
```typescript
|
||||
// 文件路径: src/services/__test__/arxivService.test.ts
|
||||
import { searchArxiv } from '../arxivService';
|
||||
|
||||
// Mock 全局的 fetch 函数以防真实发起网络请求
|
||||
global.fetch = jest.fn();
|
||||
|
||||
describe('Arxiv Service', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('应该成功解析 Arxiv 的 XML 并返回文献数组', async () => {
|
||||
const mockXml = `
|
||||
<feed>
|
||||
<entry>
|
||||
<id>http://arxiv.org/abs/1234.5678</id>
|
||||
<title>Quantum Machine Learning</title>
|
||||
<summary>This is a summary of QML.</summary>
|
||||
</entry>
|
||||
</feed>
|
||||
`;
|
||||
(global.fetch as jest.Mock).mockResolvedValue({
|
||||
ok: true,
|
||||
text: jest.fn().mockResolvedValue(mockXml)
|
||||
});
|
||||
|
||||
const results = await searchArxiv('Quantum', 1);
|
||||
expect(results).toHaveLength(1);
|
||||
expect(results[0].title).toBe('Quantum Machine Learning');
|
||||
expect(results[0].id).toBe('http://arxiv.org/abs/1234.5678');
|
||||
});
|
||||
|
||||
it('当网络请求失败时应该抛出错误', async () => {
|
||||
(global.fetch as jest.Mock).mockResolvedValue({
|
||||
ok: false,
|
||||
status: 500
|
||||
});
|
||||
|
||||
await expect(searchArxiv('Error', 1)).rejects.toThrow('Arxiv API 响应错误: 500');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### 场景 B:测试工具分发与框架剥离 (`toolsController.test.ts`)
|
||||
|
||||
**目标**:测试 `handleToolCall` 是否正确将参数分发给对应函数,**特别要测试 `fetch_framework_template` 是否成功拦截了原始 JSON 并加上了“刹车指令”**。
|
||||
|
||||
```typescript
|
||||
// 文件路径: src/controllers/__test__/toolsController.test.ts
|
||||
import { handleToolCall } from '../toolsController';
|
||||
import * as fs from 'fs';
|
||||
|
||||
// Mock 文件系统,防止测试时去真实读取本地 JSON
|
||||
jest.mock('fs');
|
||||
|
||||
describe('Tools Controller - Sprint 3', () => {
|
||||
|
||||
it('当调用未知工具时,应返回 isError 为 true', async () => {
|
||||
const result = await handleToolCall('unknown_tool', {});
|
||||
expect(result.isError).toBe(true);
|
||||
expect(result.content[0].text).toContain('未知工具');
|
||||
});
|
||||
|
||||
it('fetch_framework_template 应该正确解析 JSON 并附带刹车指令', async () => {
|
||||
// 伪造一个框架 JSON 文件内容
|
||||
const mockFrameworkJson = JSON.stringify({
|
||||
messages: [
|
||||
{ role: 'system', content: { text: 'Mock系统指令' } },
|
||||
{ role: 'user', content: { text: 'Mock用户模板' } }
|
||||
]
|
||||
});
|
||||
(fs.readFileSync as jest.Mock).mockReturnValue(mockFrameworkJson);
|
||||
|
||||
const result = await handleToolCall('fetch_framework_template', { framework_name: 'swot' });
|
||||
|
||||
expect(result.isError).toBeUndefined(); // 不应报错
|
||||
const outputText = result.content[0].text;
|
||||
|
||||
// 断言是否包含了防止死循环的强指令
|
||||
expect(outputText).toContain('立即停止调用任何工具');
|
||||
// 断言 JSON 壳子被剥离,提取出了纯文本
|
||||
expect(outputText).toContain('Mock系统指令');
|
||||
expect(outputText).toContain('Mock用户模板');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### 场景 C:测试容错版保存工具 (`resourceService.test.ts` 相关逻辑)
|
||||
|
||||
**目标**:在 `toolsController` 层测试 `save_note`,验证当大模型错误地传入 JSON 对象而不是字符串时,系统能否自动将其序列化以避免崩溃。
|
||||
|
||||
```typescript
|
||||
import { handleToolCall } from '../../controllers/toolsController'';
|
||||
import * as resourceService from '../../services/resourceService';
|
||||
import * as arxivService from '../../services/arxivService';
|
||||
import * as fs from 'fs';
|
||||
|
||||
// ====================================================
|
||||
// 1. 全局 Mock 外部依赖 (防止测试时发生真实读写和网络请求)
|
||||
// ====================================================
|
||||
jest.mock('fs');
|
||||
|
||||
jest.mock('../../services/resourceService', () => ({
|
||||
saveNote: jest.fn().mockResolvedValue('保存成功'),
|
||||
listObsidianNotes: jest.fn().mockResolvedValue(['test-note.md', 'ai-trend.md']),
|
||||
readObsidianNote: jest.fn().mockResolvedValue('这是模拟的本地笔记内容')
|
||||
}));
|
||||
|
||||
jest.mock('../../services/arxivService', () => ({
|
||||
searchArxiv: jest.fn().mockResolvedValue([
|
||||
{ id: 'http://arxiv.org/abs/1234', title: 'Test Paper', summary: 'Mock Summary' }
|
||||
])
|
||||
}));
|
||||
|
||||
describe('Tools Controller - Sprint 3 (意图驱动与容错机制)', () => {
|
||||
beforeEach(() => {
|
||||
// 每次测试前清理 Mock 调用记录
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
// ====================================================
|
||||
// 测试: 未知工具防呆
|
||||
// ====================================================
|
||||
it('当调用未知工具时,应返回 isError 为 true 的友好提示', async () => {
|
||||
const result = await handleToolCall('unknown_tool', {});
|
||||
expect(result.isError).toBe(true);
|
||||
expect(result.content[0].text).toContain('未知工具: unknown_tool');
|
||||
});
|
||||
|
||||
// ====================================================
|
||||
// 测试: 意图 3 - 保存笔记工具 (重点测试大模型容错能力)
|
||||
// ====================================================
|
||||
describe('Tool: save_note (容错版保存工具)', () => {
|
||||
it('当大模型老老实实传入【纯文本字符串】时,应直接正常保存', async () => {
|
||||
const result = await handleToolCall('save_note', { filename: 'output.md', content: '这是一段完美的Markdown文本' });
|
||||
|
||||
expect(result.isError).toBeUndefined();
|
||||
expect(result.content[0].text).toBe('保存成功');
|
||||
expect(resourceService.saveNote).toHaveBeenCalledWith('output.md', '这是一段完美的Markdown文本');
|
||||
});
|
||||
|
||||
it('🚨 容错测试:当大模型错误地传入【深度嵌套的 JSON 对象】时,应自动将其序列化,而不是崩溃', async () => {
|
||||
// 模拟大模型发生幻觉,把整个分析对象当成了参数传进来
|
||||
const mockJsonObject = {
|
||||
title: "行业分析报告",
|
||||
data: { trend: "上升", keywords: ["AI", "Quantum"] }
|
||||
};
|
||||
|
||||
const result = await handleToolCall('save_note', { filename: 'report.md', content: mockJsonObject });
|
||||
|
||||
expect(result.isError).toBeUndefined();
|
||||
expect(result.content[0].text).toBe('保存成功');
|
||||
|
||||
// 期望底层调用保存时,Controller 已经非常聪明地把 JSON 对象转换为了带缩进的字符串
|
||||
const expectedString = JSON.stringify(mockJsonObject, null, 2);
|
||||
expect(resourceService.saveNote).toHaveBeenCalledWith('report.md', expectedString);
|
||||
});
|
||||
|
||||
it('当缺少必要参数时,应优雅地返回错误信息', async () => {
|
||||
const result = await handleToolCall('save_note', { filename: 'error.md' }); // 故意漏掉 content
|
||||
expect(result.isError).toBe(true);
|
||||
expect(result.content[0].text).toContain('缺少 filename 或 content 参数');
|
||||
});
|
||||
});
|
||||
|
||||
// ====================================================
|
||||
// 测试: 意图 2 - 获取思维框架模板
|
||||
// ====================================================
|
||||
describe('Tool: fetch_framework_template (抗死循环剥离器)', () => {
|
||||
it('应该正确读取 JSON 文件,剥离外壳,并【强制附加刹车指令】防止大模型死循环', async () => {
|
||||
// 伪造一个框架 JSON 文件内容
|
||||
const mockFrameworkJson = JSON.stringify({
|
||||
messages: [
|
||||
{ role: 'system', content: { text: '我是系统架构师' } },
|
||||
{ role: 'user', content: { text: '请按 SWOT 分析化验' } }
|
||||
]
|
||||
});
|
||||
(fs.readFileSync as jest.Mock).mockReturnValue(mockFrameworkJson);
|
||||
|
||||
const result = await handleToolCall('fetch_framework_template', { framework_name: 'swot' });
|
||||
|
||||
expect(result.isError).toBeUndefined();
|
||||
const outputText = result.content[0].text;
|
||||
|
||||
// 断言:必须包含这句护身符,阻止大模型不断重复调工具
|
||||
expect(outputText).toContain('立即停止调用任何工具');
|
||||
|
||||
// 断言:JSON 外壳已经被剥离,直接透出了内部指导文字
|
||||
expect(outputText).toContain('我是系统架构师');
|
||||
expect(outputText).toContain('请按 SWOT 分析化验');
|
||||
});
|
||||
});
|
||||
|
||||
// ====================================================
|
||||
// 测试: 意图 1 - 文献检索
|
||||
// ====================================================
|
||||
describe('Tool: search_arxiv', () => {
|
||||
it('应该正确调用服务并格式化返回带 Markdown 语法的文献列表', async () => {
|
||||
const result = await handleToolCall('search_arxiv', { query: 'AI' });
|
||||
|
||||
expect(result.isError).toBeUndefined();
|
||||
expect(arxivService.searchArxiv).toHaveBeenCalledWith('AI', 5);
|
||||
|
||||
const outputText = result.content[0].text;
|
||||
expect(outputText).toContain('找到了关于 "AI" 的相关文献');
|
||||
expect(outputText).toContain('Test Paper');
|
||||
expect(outputText).toContain('http://arxiv.org/abs/1234');
|
||||
});
|
||||
});
|
||||
|
||||
// ====================================================
|
||||
// 测试: 意图 4 - 本地笔记相关
|
||||
// ====================================================
|
||||
describe('Local Notes Tools', () => {
|
||||
it('list_local_notes 应该正确返回笔记列表', async () => {
|
||||
const result = await handleToolCall('list_local_notes', {});
|
||||
expect(result.content[0].text).toContain('test-note.md');
|
||||
expect(result.content[0].text).toContain('ai-trend.md');
|
||||
});
|
||||
|
||||
it('read_local_note 应该正确读取具体笔记内容', async () => {
|
||||
const result = await handleToolCall('read_local_note', { filename: 'test-note.md' });
|
||||
expect(resourceService.readObsidianNote).toHaveBeenCalledWith('test-note.md');
|
||||
expect(result.content[0].text).toContain('这是模拟的本地笔记内容');
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 测试执行说明
|
||||
|
||||
在您配置好上述的测试文件后,请在终端使用项目中 `package.json` 原有配置的命令执行测试:
|
||||
|
||||
1. **执行基础测试**
|
||||
|
||||
```bash
|
||||
npm run test
|
||||
```
|
||||
|
||||
2. **执行并查看覆盖率(Coverage)**
|
||||
|
||||
```bash
|
||||
npm run test:coverage
|
||||
```
|
||||
|
||||
执行完成后,请检查终端输出的表格,重点关注 `arxivService.ts` 和 `toolsController.ts` 的 **Stmts (语句覆盖率)** 和 **Branch (分支覆盖率)** 是否达到预期(推荐 > 80%)。
|
||||
|
||||
3. **运行单个测试文件**
|
||||
|
||||
如果您只想跑 `toolsController.test.ts` 这一个文件,可以使用以下命令:
|
||||
|
||||
|
||||
```bash
|
||||
# 方式 A (推荐,使用 npx 直接调用 jest)
|
||||
npx jest src/controllers/__test__/toolsController.test.ts
|
||||
|
||||
# 方式 B (使用 npm 脚本透传参数,注意中间的 `--`)
|
||||
npm run test -- src/controllers/__test__/toolsController.test.ts
|
||||
```
|
||||
|
||||
4. **监听模式**
|
||||
|
||||
在写测试或改 Bug 时,我们经常需要修改完代码后自动重新跑那个报错的测试文件。您可以加上 `--watch` 参数:
|
||||
|
||||
```bash
|
||||
npx jest src/controllers/__test__/toolsController.test.ts --watch
|
||||
```
|
||||
|
||||
这样您每次保存代码,Jest 就会自动为您重新运行这个文件的测试,极大提升开发效率!
|
||||
|
||||
---
|
||||
|
||||
## 许可声明
|
||||
|
||||
本文档采用 **知识共享署名--相同方式共享 4.0 国际许可协议 (CC BY--SA 4.0)** 进行许可,© 2025-2026 Gitconomy Research。
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
<!--
|
||||
---
|
||||
title: Arabica v0.0.3 MCP Inspector 测试指南
|
||||
description: 指导开发人员使用官方 MCP Inspector 工具对 Project Caffeine v0.0.3 版本的核心原语(Tools、Resources)进行标准化的交互联调与测试。
|
||||
type: Test Guide
|
||||
version: v0.0.3 (Arabica)
|
||||
file: arabica-v0.0.3-mcp-inspector-test-guide.md
|
||||
author: Gitconomy Research-郭晧
|
||||
date: 2026-03-10
|
||||
tags:
|
||||
- Project Caffeine
|
||||
- MCP
|
||||
- MCP Inspector
|
||||
- 测试指南
|
||||
license: CC BY-SA 4.0
|
||||
status: Active
|
||||
---
|
||||
-->
|
||||
# Project Caffeine Arabica v0.0.3 MCP Inspector 测试指南
|
||||
|
||||
## 1. 测试目的的概述
|
||||
|
||||
本文档旨在为开发人员提供一份标准化的操作指南,使用官方的 **MCP Inspector** 工具对 Project Caffeine (v0.0.3) 的底层协议接口进行独立测试。通过脱离具体的大模型客户端(如 Claude Desktop),我们可以直接验证系统暴露的 Tools(工具)与 Resources(资源)原语的逻辑正确性、参数校验机制以及边缘异常处理能力。
|
||||
|
||||
## 2. 测试环境的准备
|
||||
|
||||
### 2.1 配置系统的环境变量
|
||||
|
||||
在启动测试之前,必须确保本地环境中的配置项已正确挂载。请在项目根目录的 `src/.env` 文件中检查以下关键路径,确保其指向本地真实的测试用例目录:
|
||||
|
||||
```bash
|
||||
# 必须配置为本地真实存在的目录,且具备读写权限
|
||||
OBSIDIAN_VAULT_PATH=/真实的/本地/测试目录/MyVault
|
||||
LITERATURE_STORAGE_PATH=/真实的/本地/测试目录/MyVault
|
||||
````
|
||||
|
||||
## 2.2 构建并启动检查器
|
||||
|
||||
Project Caffeine 的服务端运行依赖于编译后的 JavaScript 代码。请在终端中依次执行以下命令,完成项目的构建并通过 `npx` 启动官方的 MCP Inspector:
|
||||
|
||||
Bash
|
||||
|
||||
```bash
|
||||
# 1. 编译 TypeScript 源码
|
||||
npm run build
|
||||
|
||||
# 2. 启动 MCP Inspector 并挂载编译后的入口文件
|
||||
npx @modelcontextprotocol/inspector node dist/app.js
|
||||
```
|
||||
|
||||
启动成功后,终端将输出一个本地调试地址(通常为 `http://localhost:5173`)。请在浏览器中打开该地址,进入 MCP Inspector 的可视化调试界面。
|
||||
|
||||
## 3. 核心原语的测试流程
|
||||
|
||||
在 MCP Inspector 的 Web 界面中,您将看到系统成功连接的标识 `Project-Caffeine-Arabica-Intent-Mode (v0.0.3)`。接下来,请依次对以下核心功能模块进行拨测。
|
||||
|
||||
## 3.1 执行文献检索工具测试 (Tools)
|
||||
|
||||
系统通过 `search_arxiv` 工具提供外部学术数据的抓取能力。
|
||||
|
||||
1. **定位工具**:在左侧面板选择 **Tools** 选项卡,在列表中找到并选中 `search_arxiv`。
|
||||
|
||||
2. **输入参数**:在参数输入框中填入合法的 JSON 负载:
|
||||
|
||||
JSON
|
||||
|
||||
```bash
|
||||
{
|
||||
"query": "Large Language Models in Healthcare"
|
||||
}
|
||||
```
|
||||
|
||||
3. **发起调用**:点击 **Run Tool** 按钮。
|
||||
|
||||
4. **验证结果**:
|
||||
|
||||
- **预期成功**:右侧响应区应返回状态 `isError: false`,且 `content` 数组中包含格式化好的文献标题、链接与摘要列表。
|
||||
|
||||
- **预期异常**:若输入空的 `query`,系统应触发 Zod 校验拦截,提示“需要检索的学术核心关键字”。
|
||||
|
||||
|
||||
## 3.2 执行思维框架获取测试 (Tools)
|
||||
|
||||
系统将静态思维模板通过 `fetch_framework_template` 工具暴露给大模型。
|
||||
|
||||
1. **定位工具**:在 Tools 列表中选中 `fetch_framework_template`。
|
||||
|
||||
2. **输入参数**:
|
||||
|
||||
JSON
|
||||
|
||||
```bash
|
||||
{
|
||||
"framework_name": "scqa"
|
||||
}
|
||||
```
|
||||
|
||||
3. **发起调用**:点击 **Run Tool** 按钮。
|
||||
|
||||
4. **验证结果**:
|
||||
|
||||
- **预期成功**:响应文本中应包含“【🛑 最高优先级的底层执行指令 🛑】”以及 SCQA 框架的系统设定与结构要求。
|
||||
|
||||
- **边缘测试**:尝试输入未注册的框架(如 `abcd`),系统应返回软拦截提示:“未找到框架 abcd。系统支持的框架有: swot, scqa, pestle, 5w3h, 5whys”。
|
||||
|
||||
|
||||
## 3.3 执行双轨制落盘与读取测试 (Tools)
|
||||
|
||||
验证系统对本地文件系统的 I/O 控制与路径安全防范。
|
||||
|
||||
1. **保存笔记测试 (`save_note`)**:
|
||||
|
||||
- 构造参数:`{ "filename": "inspector_test.md", "content": "## 测试数据\n这是由 Inspector 写入的测试内容。" }`
|
||||
|
||||
- 执行后,检查本地 `OBSIDIAN_VAULT_PATH` 目录下是否成功生成了 `inspector_test.md` 文件。
|
||||
|
||||
2. **防穿越安全红线测试 (`save_note`)**:
|
||||
|
||||
- 构造参数:`{ "filename": "../hacked.md", "content": "危险载荷" }`
|
||||
|
||||
- **验证预期**:系统必须返回 `isError: true`,并明确提示“无效的文件名,不允许访问上层目录”。
|
||||
|
||||
3. **列出笔记测试 (`list_local_notes`)**:
|
||||
|
||||
- 直接运行该工具(无需参数)。系统应返回刚才创建的 `inspector_test.md`。
|
||||
|
||||
4. **读取笔记测试 (`read_local_note`)**:
|
||||
|
||||
- 构造参数:`{ "filename": "inspector_test.md" }`。
|
||||
|
||||
- 系统应正确返回刚写入的 Markdown 文本内容。
|
||||
|
||||
|
||||
## 3.4 验证本地静态资源挂载 (Resources)
|
||||
|
||||
Resources 原语用于将本地知识库作为被动只读资源暴露,以便大模型在对话上下文中作为附件引用。
|
||||
|
||||
1. **获取资源列表**:
|
||||
|
||||
- 在左侧面板切换至 **Resources** 选项卡。
|
||||
|
||||
- 点击 **List Resources**,系统应返回由 `note://local/` 协议开头的资源列表,且列表中应包含 `note://local/inspector_test.md`。
|
||||
|
||||
2. **读取特定资源**:
|
||||
|
||||
- 选中 `note://local/inspector_test.md`。
|
||||
|
||||
- 点击 **Read Resource** 按钮。
|
||||
|
||||
- **验证结果**:响应区应成功解码 URI 参数,并返回该文件的原始内容文本。
|
||||
|
||||
|
||||
## 4. 常见问题与排错方案
|
||||
|
||||
## 4.1 解决连接超时的异常
|
||||
|
||||
如果 MCP Inspector 在启动后长时间处于 `Connecting...` 状态,请排查以下可能:
|
||||
|
||||
- **检查编译产物**:确认 `npm run build` 是否成功执行,`dist/app.js` 文件是否存在。
|
||||
|
||||
- **检查端口占用**:MCP Inspector 默认使用 `5173` 端口,若端口被占用,可通过环境变量更换端口。
|
||||
|
||||
|
||||
## 4.2 解决本地路径挂载失败的异常
|
||||
|
||||
当调用 `save_note` 抛出文件写入失败时:
|
||||
|
||||
- **检查环境配置**:确认 `.env` 文件中的 `OBSIDIAN_VAULT_PATH` 路径是否拼写正确,且必须为绝对路径。
|
||||
|
||||
- **检查目录权限**:确保当前运行 Node.js 进程的用户对该绝对路径具有完全读写权限。
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 许可声明
|
||||
|
||||
本文档采用 **知识共享署名--相同方式共享 4.0 国际许可协议 (CC BY--SA 4.0)** 进行许可,© 2025-2026 Gitconomy Research。
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"name": "🍒 附加到 Cherry Studio (MCP 联调)",
|
||||
"port": 9229,
|
||||
"restart": true,
|
||||
"skipFiles": ["<node_internals>/**"],
|
||||
"outFiles": ["${workspaceFolder}/dist/**/*.js"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
<!--
|
||||
---
|
||||
title: "Project Caffeine - Arabica Sprint1 QuickStart"
|
||||
description: "Project Caffeine Arabica Sprint1 的快速启动指南,涵盖基于 MCP stdio 架构的零网络开销部署、Obsidian 本地知识库接入、5 Whys 策略引擎配置及 Cherry Studio 客户端联调全流程。"
|
||||
version: "1.0.0"
|
||||
author: "Gitconomy Research郭晧"
|
||||
date: "2026-03-01"
|
||||
type: "README / QuickStart"
|
||||
tags:
|
||||
- Project Caffeine
|
||||
- MCP
|
||||
- stdio
|
||||
- Obsidian
|
||||
- QuickStart
|
||||
- Cherry Studio
|
||||
license: "CC BY-SA 4.0"
|
||||
---
|
||||
-->
|
||||
# Project Caffeine - Arabica Sprint1 QuickStart
|
||||
|
||||
## 1. Arabica v0.0.1(Sprint1)版本核心特性
|
||||
|
||||
- **零网络开销通信**:作为本地集成版本,本系统采用 `stdio` 传输协议,利用同一台机器上本地进程间的 stdin 和 stdout 管道进行直接通信,实现零网络传输开销。
|
||||
|
||||
- **本地知识图谱接入**:无缝对接 Obsidia个人知识管理软件,让大模型能够直接读取你的本地知识库。
|
||||
|
||||
- **内置 5 Whys 策略引擎**:引入经典的“5 Whys”思维框架挂载,强制约束大模型的思考路径,辅助其将模糊想法拆解为高精度的追问。
|
||||
|
||||
- **沙箱隔离级安全防御**:默认将 AI 生成的指令视为不可信负载,通过底层机制阻断越权访问和路径遍历漏洞,确保本地文件系统的绝对安全。
|
||||
|
||||
---
|
||||
## 2. 克隆仓库与获取分支代码
|
||||
|
||||
**📌 重要说明**:Sprint 的迭代代码不会直接合并到`master` 分支。为了获取 Sprint1 的完整代码,你需要在克隆时指定对应的特性分支 (`feature/arabica-sprint-1`):
|
||||
|
||||
```bash
|
||||
# 直接克隆指定的 feature 分支
|
||||
git clone -b feature-arabica-sprint-1 https://gitlink.org.cn/Gitconomy/Project-Caffeine.git
|
||||
|
||||
# 进入 Sprint1 的独立工作目录
|
||||
cd Project-Caffeine/projects/arabica/sprint1
|
||||
|
||||
# 安装 Node.js 项目依赖
|
||||
npm install
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 环境与路径配置
|
||||
|
||||
为了让系统准确挂载你的知识库,请打开 `src/services/resourceService.ts`,将 `OBSIDIAN_VAULT_PATH` 变量修改为你本机实际的 Markdown 文件夹绝对路径。
|
||||
|
||||
## 4. 编译与工作流说明
|
||||
|
||||
项目采用 TypeScript 开发,底层依靠 Node.js 运行,因此必须先将 `.ts` 源码编译为 `.js` 文件。Sprint1的项目目录已经包含编译过的 `.js` 文件(/dist目录),可以直接快速测试运行。
|
||||
|
||||
**⚠️ 极其重要的运行说明 (必读):**
|
||||
|
||||
基于 MCP 的 `stdio` 架构特性,本程序**不需要**你手动启动独立的后台服务。请根据你的使用场景选择工作流:
|
||||
|
||||
- **🟢 日常使用 (生产模式)**
|
||||
|
||||
你**不需要**在终端里输入 `npm run start`。只要确保 `dist/app.js` 文件存在,当你在 Cherry Studio 或 Claude Desktop 等客户端中配置好绝对路径并打开开关时,客户端会在系统后台自动唤起并接管这个 Node 进程。
|
||||
|
||||
- **🛠️ 开发与调试 (实时监听模式)**
|
||||
|
||||
如果你正在修改源码,并希望配合 VS Code 进行断点联调,请在终端中保持运行以下命令:
|
||||
|
||||
_(此命令会在后台实时监控代码改动。当你按 `Ctrl+S` 保存代码后,只需在 Cherry Studio 中将 Server 开关关闭再打开,即可瞬间应用最新的代码逻辑!)_
|
||||
|
||||
---
|
||||
|
||||
## 5. Sprint1 暴露的工具 (Tools)
|
||||
|
||||
本服务端向支持 MCP 的 LLM 暴露了以下 3 个核心工具,赋予其检索本地数据与优化提示词的主动权:
|
||||
|
||||
- **`list_local_notes`**: 扫描本地知识库目录,返回所有 `.md` 格式的文献与笔记列表,帮助大模型确立探索边界。
|
||||
- **`read_local_note`**: 深度读取指定 Markdown 文件的原文,将本地知识库的物理边界转化为大模型内存中的上下文。
|
||||
- **`generate_5_whys`**: 针对用户宽泛的研究主题,强制模型连续追问五次“为什么”,层层递进剥离问题的表象,寻找最底层的学术痛点。
|
||||
|
||||
|
||||
## 6. Sprint1 暴露的资源 (Resources)
|
||||
|
||||
资源(Resources)作为被动的静态上下文信息数据源,供客户端 UI 直接发现与提取:
|
||||
|
||||
- **`obsidian-index`** (`obsidian://vault/index`): 向客户端暴露本地知识库的完整目录索引数据。
|
||||
|
||||
---
|
||||
|
||||
## 7. 客户端接入联调 (以 Cherry Studio 为例)
|
||||
|
||||
Sprint1 采用纯本地 `stdio` 架构,推荐使用 VS Code 配合客户端进行源码级联调:
|
||||
|
||||
1. 打开 Cherry Studio,进入 **设置 -> MCP**。
|
||||
|
||||
2. 添加一个新的 Server 配置:
|
||||
|
||||
- **名称**: `ProjectCaffeine-Sprint1`
|
||||
- **Command**: `node`
|
||||
- **Args**: `[--inspect=9229", /你的实际克隆路径/Project-Caffeine/projects/arabica/sprint1/dist/app.js]` _(⚠️ 必须为编译后的 js 文件绝对路径,且 `--inspect` 需放在首位以开启调试)_
|
||||
|
||||
或者通过导入json
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"Project Caffeine": {
|
||||
"isActive": true,
|
||||
"name": "Project Caffeine",
|
||||
"type": "stdio",
|
||||
"description": "",
|
||||
"baseUrl": "",
|
||||
"command": "node",
|
||||
"args": [
|
||||
"--inspect=9229",
|
||||
"/home/wguo/Downloads/Project-Caffeine/projects/arabica/sprint2/dist/app.js"
|
||||
],
|
||||
"env": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. 保存后确认状态灯变为绿色。
|
||||
|
||||
4. 返回 VS Code,在侧边栏“运行和调试”中执行附加 (Attach),即可对大模型发起的每一次工具调用进行完美断点拦截。
|
||||
|
||||
---
|
||||
|
||||
## 8. Sprint1 文档
|
||||
|
||||
| **版本** | **开发目标** | **设计文档** | 开发文档 |
|
||||
| ---------------------------------------------------- | ------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
|
||||
| [`v0.0.1`](./README.md) | 部署基于 Node.js 开发环境,验证 MCP 协议组件间通讯、大语言模型推理等基本运行环境。<br> | [Arabicat Sprint1系统设计文档](./../../docs/design/arabica-sprint1-architecture-specification.md) | [Arabicat Sprint1系统开发文档](./../../docs/design/arabica-sprint1-development-specification.md) |
|
||||
|
||||
---
|
||||
|
||||
## 许可声明
|
||||
|
||||
本文档采用 **知识共享署名--相同方式共享 4.0 国际许可协议 (CC BY--SA 4.0)** 进行许可,© 2025-2026 Gitconomy Research.
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
"use strict";
|
||||
/**
|
||||
* Project Caffeine
|
||||
* Copyright (c) 2025-2026 Gitconomy Research
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Contributors:
|
||||
* - 郭晧 <guohao@gitconomy.org> (Initial Author)
|
||||
*/
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
||||
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
||||
const zod_1 = require("zod");
|
||||
const promptService_1 = require("./services/promptService");
|
||||
const resourceService_1 = require("./services/resourceService");
|
||||
const fs = __importStar(require("fs"));
|
||||
const path = __importStar(require("path"));
|
||||
// ==========================================
|
||||
// 1. 初始化 MCP Server
|
||||
// ==========================================
|
||||
const mcpServer = new mcp_js_1.McpServer({
|
||||
name: "Project-Caffeine-Prompt-Strategy",
|
||||
version: "1.2.0"
|
||||
});
|
||||
// ==========================================
|
||||
// 2. 注册 Tools (工具) - 赋予大模型主动执行的能力
|
||||
// ==========================================
|
||||
// 工具 1:5 Whys 提示词策略生成
|
||||
mcpServer.tool("generate_5_whys", "使用 5 Whys 模板对用户查询进行深度分解,生成增强的提示词策略", { query: zod_1.z.string().describe("需要分析的查询主题") }, async ({ query }) => {
|
||||
console.error(`[Project Caffeine] 大模型调用工具: 正在生成 5 Whys 策略 -> ${query}`);
|
||||
const enhancedPrompt = (0, promptService_1.generate5Whys)(query);
|
||||
return {
|
||||
content: [{ type: "text", text: JSON.stringify(enhancedPrompt, null, 2) }]
|
||||
};
|
||||
});
|
||||
// 工具 2:扫描本地知识库目录
|
||||
mcpServer.tool("list_local_notes", "获取本地 Obsidian 知识库中的所有 Markdown 笔记列表,用于了解当前有哪些可用的本地上下文资料。", {}, async () => {
|
||||
console.error(`[Project Caffeine] 大模型调用工具: 正在扫描本地笔记列表...`);
|
||||
const notes = await (0, resourceService_1.listObsidianNotes)();
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: notes.length > 0 ? `找到了以下笔记:\n${notes.join('\n')}` : "未找到笔记。"
|
||||
}]
|
||||
};
|
||||
});
|
||||
// 工具 3:阅读指定的单篇笔记内容
|
||||
mcpServer.tool("read_local_note", "读取本地 Obsidian 知识库中指定笔记的完整内容,作为深度分析的上下文参考。", { filename: zod_1.z.string().describe("需要读取的笔记文件名,必须包含 .md 后缀") }, async ({ filename }) => {
|
||||
console.error(`[Project Caffeine] 大模型调用工具: 正在深度阅读笔记 -> ${filename}`);
|
||||
try {
|
||||
const content = await (0, resourceService_1.readObsidianNote)(filename);
|
||||
return { content: [{ type: "text", text: content }] };
|
||||
}
|
||||
catch (error) {
|
||||
return {
|
||||
content: [{ type: "text", text: `读取失败: ${error.message}` }],
|
||||
isError: true // 明确告知大模型此操作抛出了错误
|
||||
};
|
||||
}
|
||||
});
|
||||
// ==========================================
|
||||
// 3. 注册 Resources (资源) - 暴露给客户端供用户手动勾选的静态数据
|
||||
// ==========================================
|
||||
// 资源 1:知识库目录索引
|
||||
mcpServer.resource("obsidian-index", // 客户端显示的资源 Name/ID
|
||||
"obsidian://vault/index", // 唯一的 URI 标识
|
||||
{
|
||||
description: "本地知识库的目录索引,包含所有 Markdown 笔记的列表"
|
||||
}, async (uri) => {
|
||||
console.error(`[Project Caffeine] 客户端请求静态资源: ${uri.href}`);
|
||||
const notes = await (0, resourceService_1.listObsidianNotes)();
|
||||
const textContent = notes.length > 0
|
||||
? `当前知识库包含以下文件:\n${notes.join('\n')}`
|
||||
: "当前知识库为空。";
|
||||
return {
|
||||
contents: [{
|
||||
uri: uri.href,
|
||||
mimeType: "text/plain",
|
||||
text: textContent
|
||||
}]
|
||||
};
|
||||
});
|
||||
// ==========================================
|
||||
// 4. 启动底层 Stdio 传输层
|
||||
// ==========================================
|
||||
async function start() {
|
||||
console.error("[Project Caffeine] 正在启动 TS 版 MCP Server (含 Tools 与 Resources)...");
|
||||
const transport = new stdio_js_1.StdioServerTransport();
|
||||
await mcpServer.connect(transport);
|
||||
console.error("[Project Caffeine] MCP Server 已就绪,等待 Cherry Studio 交互。");
|
||||
}
|
||||
// 捕获致命错误并安全退出
|
||||
start().catch((err) => {
|
||||
console.error("服务器启动失败:", err);
|
||||
process.exit(1);
|
||||
});
|
||||
// ==========================================
|
||||
// 💡 5. 日志持久化拦截器 (Linux)
|
||||
// ==========================================
|
||||
const logFilePath = path.resolve(__dirname, '../server.log');
|
||||
const originalConsoleError = console.error;
|
||||
console.error = (...args) => {
|
||||
// 1. 在后台输出
|
||||
originalConsoleError(...args);
|
||||
// 2. 同时把日志追加写入到项目根目录的 server.log 文件中
|
||||
const logMessage = args.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : String(arg)).join(' ');
|
||||
fs.appendFileSync(logFilePath, `[${new Date().toISOString()}] ${logMessage}\n`);
|
||||
};
|
||||
//# sourceMappingURL=app.js.map
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
"use strict";
|
||||
/**
|
||||
* Project Caffeine
|
||||
* Copyright (c) 2025-2026 Gitconomy Research
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Contributors:
|
||||
* - 郭晧 <guohao@gitconomy.org> (Initial Author)
|
||||
*/
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.generate5Whys = generate5Whys;
|
||||
/**
|
||||
* 根据查询主题生成 5 Whys 提示词策略
|
||||
* @param query 用户输入的查询主题
|
||||
* @returns 包含 5 个追问的字符串数组
|
||||
*/
|
||||
function generate5Whys(query) {
|
||||
if (query.includes("开源人才")) {
|
||||
return [
|
||||
"为什么中国开源人才的培养面临困难?",
|
||||
"为什么中国开源人才缺乏足够的行业经验?",
|
||||
"为什么开源社区对中国人才的支持力度不足?",
|
||||
"为什么中国开源人才的市场需求与供给不平衡?",
|
||||
"为什么政策支持不足导致中国开源人才流失?"
|
||||
];
|
||||
}
|
||||
return [
|
||||
`为什么 "${query}" 会成为一个问题?`,
|
||||
`为什么导致上述现象的直接原因会发生?`,
|
||||
`为什么当前的系统或流程没有阻止这种情况?`,
|
||||
`为什么以前的解决方案或预防措施失效了?`,
|
||||
`为什么根本的系统性漏洞一直未被修复?`
|
||||
];
|
||||
}
|
||||
//# sourceMappingURL=promptService.js.map
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"promptService.js","sourceRoot":"","sources":["../../src/services/promptService.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;AAQH,sCAkBC;AAxBD;;;;GAIG;AAEH,SAAgB,aAAa,CAAC,KAAa;IACvC,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QACzB,OAAO;YACH,mBAAmB;YACnB,qBAAqB;YACrB,sBAAsB;YACtB,uBAAuB;YACvB,sBAAsB;SACzB,CAAC;IACN,CAAC;IAED,OAAO;QACH,QAAQ,KAAK,YAAY;QACzB,oBAAoB;QACpB,sBAAsB;QACtB,qBAAqB;QACrB,oBAAoB;KACvB,CAAC;AACN,CAAC"}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
"use strict";
|
||||
/**
|
||||
* Project Caffeine
|
||||
* Copyright (c) 2025-2026 Gitconomy Research
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Contributors:
|
||||
* - 郭晧 <guohao@gitconomy.org> (Initial Author)
|
||||
*/
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.listObsidianNotes = listObsidianNotes;
|
||||
exports.readObsidianNote = readObsidianNote;
|
||||
const promises_1 = __importDefault(require("fs/promises"));
|
||||
const path_1 = __importDefault(require("path"));
|
||||
// 【⚠️ 重要配置】请修改为你电脑上真实的 Markdown 笔记文件夹绝对路径!
|
||||
const OBSIDIAN_VAULT_PATH = '/home/wguo/Downloads/MyVault';
|
||||
async function listObsidianNotes() {
|
||||
try {
|
||||
const files = await promises_1.default.readdir(OBSIDIAN_VAULT_PATH);
|
||||
return files.filter(file => file.toLowerCase().endsWith('.md'));
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`[Project Caffeine] 无法读取知识库目录: ${error.message}`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
async function readObsidianNote(filename) {
|
||||
const targetPath = path_1.default.resolve(OBSIDIAN_VAULT_PATH, filename);
|
||||
const safeVaultPath = path_1.default.resolve(OBSIDIAN_VAULT_PATH);
|
||||
// 核心防御:防止大模型通过传入 "../../" 读取系统敏感文件
|
||||
if (!targetPath.startsWith(safeVaultPath)) {
|
||||
throw new Error(`安全警告:越权访问拦截!禁止读取目录外的文件: ${filename}`);
|
||||
}
|
||||
try {
|
||||
const content = await promises_1.default.readFile(targetPath, 'utf-8');
|
||||
return content;
|
||||
}
|
||||
catch (error) {
|
||||
throw new Error(`无法读取笔记 [${filename}]: 文件可能不存在或无权限。`);
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=resourceService.js.map
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"resourceService.js","sourceRoot":"","sources":["../../src/services/resourceService.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;;;AAQH,8CAQC;AAED,4CAeC;AA/BD,2DAA6B;AAC7B,gDAAwB;AAExB,2CAA2C;AAC3C,MAAM,mBAAmB,GAAG,8BAA8B,CAAC;AAEpD,KAAK,UAAU,iBAAiB;IACnC,IAAI,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,kBAAE,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;QACpD,OAAO,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;IACpE,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QAClB,OAAO,CAAC,KAAK,CAAC,iCAAiC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QAChE,OAAO,EAAE,CAAC;IACd,CAAC;AACL,CAAC;AAEM,KAAK,UAAU,gBAAgB,CAAC,QAAgB;IACnD,MAAM,UAAU,GAAG,cAAI,CAAC,OAAO,CAAC,mBAAmB,EAAE,QAAQ,CAAC,CAAC;IAC/D,MAAM,aAAa,GAAG,cAAI,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAExD,mCAAmC;IACnC,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CAAC,2BAA2B,QAAQ,EAAE,CAAC,CAAC;IAC3D,CAAC;IAED,IAAI,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,kBAAE,CAAC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QACvD,OAAO,OAAO,CAAC;IACnB,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CAAC,WAAW,QAAQ,iBAAiB,CAAC,CAAC;IAC1D,CAAC;AACL,CAAC"}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"name": "project-caffeine-sprint1",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"watch": "tsc --watch",
|
||||
"start": "node dist/app.js"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.27.1",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.3.3",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
/**
|
||||
* Project Caffeine
|
||||
* Copyright (c) 2025-2026 Gitconomy Research
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Contributors:
|
||||
* - 郭晧 <guohao@gitconomy.org> (Initial Author)
|
||||
*/
|
||||
|
||||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
||||
import { z } from 'zod';
|
||||
import { generate5Whys } from './services/promptService';
|
||||
import { listObsidianNotes, readObsidianNote } from './services/resourceService';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
// ==========================================
|
||||
// 1. 初始化 MCP Server
|
||||
// ==========================================
|
||||
const mcpServer = new McpServer({
|
||||
name: "Project-Caffeine-Prompt-Strategy",
|
||||
version: "1.2.0"
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// 2. 注册 Tools (工具) - 赋予大模型主动执行的能力
|
||||
// ==========================================
|
||||
|
||||
// 工具 1:5 Whys 提示词策略生成
|
||||
mcpServer.tool(
|
||||
"generate_5_whys",
|
||||
"使用 5 Whys 模板对用户查询进行深度分解,生成增强的提示词策略",
|
||||
{ query: z.string().describe("需要分析的查询主题") },
|
||||
async ({ query }: { query: string }) => {
|
||||
console.error(`[Project Caffeine] 大模型调用工具: 正在生成 5 Whys 策略 -> ${query}`);
|
||||
const enhancedPrompt = generate5Whys(query);
|
||||
return {
|
||||
content: [{ type: "text", text: JSON.stringify(enhancedPrompt, null, 2) }]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// 工具 2:扫描本地知识库目录
|
||||
mcpServer.tool(
|
||||
"list_local_notes",
|
||||
"获取本地 Obsidian 知识库中的所有 Markdown 笔记列表,用于了解当前有哪些可用的本地上下文资料。",
|
||||
{},
|
||||
async () => {
|
||||
console.error(`[Project Caffeine] 大模型调用工具: 正在扫描本地笔记列表...`);
|
||||
const notes = await listObsidianNotes();
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: notes.length > 0 ? `找到了以下笔记:\n${notes.join('\n')}` : "未找到笔记。"
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// 工具 3:阅读指定的单篇笔记内容
|
||||
mcpServer.tool(
|
||||
"read_local_note",
|
||||
"读取本地 Obsidian 知识库中指定笔记的完整内容,作为深度分析的上下文参考。",
|
||||
{ filename: z.string().describe("需要读取的笔记文件名,必须包含 .md 后缀") },
|
||||
async ({ filename }: { filename: string }) => {
|
||||
console.error(`[Project Caffeine] 大模型调用工具: 正在深度阅读笔记 -> ${filename}`);
|
||||
try {
|
||||
const content = await readObsidianNote(filename);
|
||||
return { content: [{ type: "text", text: content }] };
|
||||
} catch (error: any) {
|
||||
return {
|
||||
content: [{ type: "text", text: `读取失败: ${error.message}` }],
|
||||
isError: true // 明确告知大模型此操作抛出了错误
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// ==========================================
|
||||
// 3. 注册 Resources (资源) - 暴露给客户端供用户手动勾选的静态数据
|
||||
// ==========================================
|
||||
|
||||
// 资源 1:知识库目录索引
|
||||
mcpServer.resource(
|
||||
"obsidian-index", // 客户端显示的资源 Name/ID
|
||||
"obsidian://vault/index", // 唯一的 URI 标识
|
||||
{
|
||||
description: "本地知识库的目录索引,包含所有 Markdown 笔记的列表"
|
||||
},
|
||||
async (uri) => {
|
||||
console.error(`[Project Caffeine] 客户端请求静态资源: ${uri.href}`);
|
||||
|
||||
const notes = await listObsidianNotes();
|
||||
const textContent = notes.length > 0
|
||||
? `当前知识库包含以下文件:\n${notes.join('\n')}`
|
||||
: "当前知识库为空。";
|
||||
|
||||
return {
|
||||
contents: [{
|
||||
uri: uri.href,
|
||||
mimeType: "text/plain",
|
||||
text: textContent
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ==========================================
|
||||
// 4. 启动底层 Stdio 传输层
|
||||
// ==========================================
|
||||
async function start(): Promise<void> {
|
||||
console.error("[Project Caffeine] 正在启动 TS 版 MCP Server (含 Tools 与 Resources)...");
|
||||
const transport = new StdioServerTransport();
|
||||
await mcpServer.connect(transport);
|
||||
console.error("[Project Caffeine] MCP Server 已就绪,等待 Cherry Studio 交互。");
|
||||
}
|
||||
|
||||
// 捕获致命错误并安全退出
|
||||
start().catch((err: unknown) => {
|
||||
console.error("服务器启动失败:", err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// 💡 5. 日志持久化拦截器 (Linux)
|
||||
// ==========================================
|
||||
const logFilePath = path.resolve(__dirname, '../server.log');
|
||||
const originalConsoleError = console.error;
|
||||
|
||||
console.error = (...args) => {
|
||||
// 1. 在后台输出
|
||||
originalConsoleError(...args);
|
||||
// 2. 同时把日志追加写入到项目根目录的 server.log 文件中
|
||||
const logMessage = args.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : String(arg)).join(' ');
|
||||
fs.appendFileSync(logFilePath, `[${new Date().toISOString()}] ${logMessage}\n`);
|
||||
};
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
/**
|
||||
* Project Caffeine
|
||||
* Copyright (c) 2025-2026 Gitconomy Research
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Contributors:
|
||||
* - 郭晧 <guohao@gitconomy.org> (Initial Author)
|
||||
*/
|
||||
|
||||
/**
|
||||
* 根据查询主题生成 5 Whys 提示词策略
|
||||
* @param query 用户输入的查询主题
|
||||
* @returns 包含 5 个追问的字符串数组
|
||||
*/
|
||||
|
||||
export function generate5Whys(query: string): string[] {
|
||||
if (query.includes("开源人才")) {
|
||||
return [
|
||||
"为什么中国开源人才的培养面临困难?",
|
||||
"为什么中国开源人才缺乏足够的行业经验?",
|
||||
"为什么开源社区对中国人才的支持力度不足?",
|
||||
"为什么中国开源人才的市场需求与供给不平衡?",
|
||||
"为什么政策支持不足导致中国开源人才流失?"
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
`为什么 "${query}" 会成为一个问题?`,
|
||||
`为什么导致上述现象的直接原因会发生?`,
|
||||
`为什么当前的系统或流程没有阻止这种情况?`,
|
||||
`为什么以前的解决方案或预防措施失效了?`,
|
||||
`为什么根本的系统性漏洞一直未被修复?`
|
||||
];
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
* Project Caffeine
|
||||
* Copyright (c) 2025-2026 Gitconomy Research
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Contributors:
|
||||
* - 郭晧 <guohao@gitconomy.org> (Initial Author)
|
||||
*/
|
||||
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
|
||||
// 【⚠️ 重要配置】请修改为你电脑上真实的 Markdown 笔记文件夹绝对路径!
|
||||
const OBSIDIAN_VAULT_PATH = '/home/wguo/Downloads/MyVault';
|
||||
|
||||
export async function listObsidianNotes(): Promise<string[]> {
|
||||
try {
|
||||
const files = await fs.readdir(OBSIDIAN_VAULT_PATH);
|
||||
return files.filter(file => file.toLowerCase().endsWith('.md'));
|
||||
} catch (error: any) {
|
||||
console.error(`[Project Caffeine] 无法读取知识库目录: ${error.message}`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function readObsidianNote(filename: string): Promise<string> {
|
||||
const targetPath = path.resolve(OBSIDIAN_VAULT_PATH, filename);
|
||||
const safeVaultPath = path.resolve(OBSIDIAN_VAULT_PATH);
|
||||
|
||||
// 核心防御:防止大模型通过传入 "../../" 读取系统敏感文件
|
||||
if (!targetPath.startsWith(safeVaultPath)) {
|
||||
throw new Error(`安全警告:越权访问拦截!禁止读取目录外的文件: ${filename}`);
|
||||
}
|
||||
|
||||
try {
|
||||
const content = await fs.readFile(targetPath, 'utf-8');
|
||||
return content;
|
||||
} catch (error: any) {
|
||||
throw new Error(`无法读取笔记 [${filename}]: 文件可能不存在或无权限。`);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "node",
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"sourceMap": true, // 【关键】生成 .js.map 文件,用于 VS Code 断点映射
|
||||
"strict": true, // 开启严格模式
|
||||
"esModuleInterop": true, // 允许默认导入 CommonJS 模块
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"name": "🍒 附加到 Cherry Studio (MCP 联调)",
|
||||
"port": 9229,
|
||||
"restart": true,
|
||||
"skipFiles": ["<node_internals>/**"],
|
||||
"outFiles": ["${workspaceFolder}/dist/**/*.js"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
<!--
|
||||
---
|
||||
title: Project Caffeine - Arabica Sprint2 QuickStart
|
||||
description: Project Caffeine Arabica Sprint2 的快速启动指南,涵盖基于 MCP stdio 架构的多维思维框架引擎、意图拆解工具、本地知识库集成及 Cherry Studio 客户端联调全流程。
|
||||
version: 1.0.0
|
||||
author: Gitconomy Research 郭晧
|
||||
date: 2026-03-06
|
||||
type: README / QuickStart
|
||||
tags:
|
||||
- Project Caffeine
|
||||
- MCP
|
||||
- stdio
|
||||
- Obsidian
|
||||
- Prompts
|
||||
- Tools
|
||||
- Resources
|
||||
- Cherry Studio
|
||||
license: CC BY-SA 4.0
|
||||
---
|
||||
-->
|
||||
# Project Caffeine - Arabica Sprint2 QuickStart
|
||||
|
||||
## 1. Arabica v0.0.2(Spint2) 版本核心特性
|
||||
|
||||
- **多维思维框架引擎**:从单一工具扩展为完整的 Prompts 原语支持,内置 **SCQA、5Whys、5W3H、SWOT、PESTLE** 六大经典思维框架。每个框架均包含角色化系统提示、参数模板及 Few-Shot 示例,强制约束大模型的思考路径,输出高质量结构化分析。
|
||||
|
||||
- **意图拆解工具**:新增 `generate_search_queries` 工具,将用户自然语言查询自动拆解为 3~5 个专业检索词,支持后续联网搜索或知识库检索,提升信息获取精度。
|
||||
|
||||
- **本地知识库无缝接入**:保留 Sprint1 的资源能力,通过 `list_local_notes`、`read_local_note`、`save_note` 工具及动态资源模板,让大模型安全读写你的 Obsidian 知识库(Markdown 笔记),内置路径遍历防护。
|
||||
|
||||
- **角色矩阵与动态系统提示**:引入 `personas.json` 角色配置,将思维框架与专业角色(如战略顾问、根因分析师)解耦,框架可通过 `persona` 字段引用角色系统提示,实现提示词复用与灵活定制。
|
||||
|
||||
- **零开销 stdio 通信**:沿用纯本地 `stdio` 传输协议,无网络开销,保障数据隐私。
|
||||
|
||||
- **沙箱隔离级安全防御**:所有文件操作均经过严格路径校验,杜绝路径遍历攻击。
|
||||
|
||||
---
|
||||
|
||||
## 2. 克隆仓库与获取分支代码
|
||||
|
||||
**📌 重要说明**:Sprint2 的迭代代码位于独立特性分支 `feature/arabica-sprint-2`。克隆时请指定该分支:
|
||||
|
||||
```bash
|
||||
# 直接克隆指定的 feature 分支
|
||||
git clone -b feature-arabica-sprint-2 https://gitlink.org.cn/Gitconomy/Project-Caffeine.git
|
||||
|
||||
# 进入 Sprint2 的独立工作目录
|
||||
cd Project-Caffeine/projects/arabica/sprint2
|
||||
|
||||
# 安装 Node.js 项目依赖
|
||||
npm install
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 环境与路径配置
|
||||
|
||||
打开 `src/services/resourceService.ts`,将 `OBSIDIAN_VAULT_PATH` 变量修改为你本机真实的 Markdown 笔记文件夹绝对路径。
|
||||
|
||||
```typescript
|
||||
// 【⚠️ 重要配置】请修改为你电脑上真实的 Markdown 笔记文件夹绝对路径!
|
||||
const OBSIDIAN_VAULT_PATH = '/your/actual/vault/path';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 编译与工作流说明
|
||||
|
||||
项目采用 TypeScript 开发,必须先将 `.ts` 源码编译为 `.js` 文件。Sprint2 的 `dist/` 目录已包含预编译文件,可直接用于生产测试。
|
||||
|
||||
**⚠️ 极其重要的运行说明 (必读):**
|
||||
|
||||
基于 MCP 的 `stdio` 架构,本程序**不需要**手动启动独立后台服务。请根据使用场景选择工作流:
|
||||
|
||||
- **🟢 日常使用 (生产模式)**
|
||||
|
||||
你**不需要**在终端里输入 `npm run start`。只需在 Cherry Studio 或 Claude Desktop 等客户端中配置好 `dist/app.js` 的绝对路径并开启开关,客户端会自动唤起并托管 Node 进程。
|
||||
|
||||
- **🛠️ 开发与调试 (实时监听模式)**
|
||||
|
||||
如需修改源码并配合 VS Code 断点调试,请保持终端运行:
|
||||
|
||||
```bash
|
||||
npm run watch # 或 tsc --watch
|
||||
```
|
||||
|
||||
代码保存后自动编译,在 Cherry Studio 中将 Server 开关关闭再打开,即可应用最新代码。
|
||||
|
||||
---
|
||||
|
||||
## 5. Sprint2 暴露的原语
|
||||
|
||||
### 5.1 Prompts(思维框架模板)
|
||||
|
||||
本服务端向支持 MCP 的 LLM 暴露以下 6 个 Prompt 模板,用于引导模型进行结构化思考:
|
||||
|
||||
| 框架名称 | 描述 | 参数 |
|
||||
|----------|------|------|
|
||||
| `scqa` | SCQA 架构:情境、复杂化、问题、答案 | `situation`, `complication`, `question`, `answer` |
|
||||
| `5whys` | 5 Whys 根因分析 | `problem` |
|
||||
| `5w3h` | 5W3H 多维度拆解 | `topic` |
|
||||
| `swot` | SWOT 内外部环境分析 | `entity` |
|
||||
| `pestle` | PESTLE 宏观环境分析 | `domain` |
|
||||
|
||||
客户端可通过 `prompts/list` 获取完整参数列表。
|
||||
|
||||
### 5.2 Tools(主动调用工具)
|
||||
|
||||
| 工具名称 | 功能 | 参数 |
|
||||
|----------|------|------|
|
||||
| `list_local_notes` | 列出知识库中所有 `.md` 文件 | 无 |
|
||||
| `read_local_note` | 读取指定笔记内容 | `filename` |
|
||||
| `save_note` | 保存笔记到本地知识库 | `filename`, `content` |
|
||||
| `generate_search_queries` | 将查询拆解为专业检索词 | `query` |
|
||||
|
||||
### 5.3 Resources(被动上下文)
|
||||
|
||||
- **`note://local/{filename}`**:动态资源模板,客户端可浏览和读取知识库中的任意笔记文件,资源列表自动从目录生成。
|
||||
|
||||
---
|
||||
|
||||
## 6. 客户端接入联调(以 Cherry Studio 为例)
|
||||
|
||||
1. 打开 Cherry Studio,进入 **设置 → MCP**。
|
||||
|
||||
2. 添加新的 Server 配置:
|
||||
- **名称**:`ProjectCaffeine-Sprint2`
|
||||
- **Command**:`node`
|
||||
- **Args**:`[--inspect=9229, /home/wguo/Downloads/Project-Caffeine/projects/arabica/sprint2/dist/app.js]`
|
||||
(`--inspect` 端口可自定义,用于 VS Code 调试)
|
||||
|
||||
或者通过导入json
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"Project Caffeine": {
|
||||
"isActive": true,
|
||||
"name": "Project Caffeine",
|
||||
"type": "stdio",
|
||||
"description": "",
|
||||
"baseUrl": "",
|
||||
"command": "node",
|
||||
"args": [
|
||||
"--inspect=9229",
|
||||
"/home/wguo/Downloads/Project-Caffeine/projects/arabica/sprint2/dist/app.js"
|
||||
],
|
||||
"env": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. 保存后状态灯应为绿色。
|
||||
|
||||
4. 若需调试,在 VS Code 中运行“附加到进程”(监听相应端口),即可拦截所有原语调用。
|
||||
|
||||
---
|
||||
|
||||
## 7. Sprint2 文档
|
||||
|
||||
| **版本** | **开发目标** | **设计文档** | 开发文档 |
|
||||
| ---------------------------------------------------- | ------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
|
||||
| [`v0.0.2`](./README.md) | 基于 Sprint 1 原型,扩展为支持 MCP Prompts 原语的多框架引擎,实现意图拆解工具与本地知识库集成,构建模块化、可扩展的提示词策略服务器。 | [Arabica Sprint2系统设计文档](./../../docs/design/arabica-sprint2-architecture-specification.md) | [Arabica Sprint2系统开发文档](./../../docs/design/arabica-sprint2-development-specification.md) |
|
||||
|
||||
---
|
||||
|
||||
## 许可声明
|
||||
|
||||
本文档采用 **知识共享署名-相同方式共享 4.0 国际许可协议 (CC BY-SA 4.0)** 进行许可,© 2025-2026 Gitconomy Research.
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
/**
|
||||
* Project Caffeine
|
||||
* 单元测试: app.ts (接入层)
|
||||
*/
|
||||
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
||||
const promptsController_1 = require("../controllers/promptsController");
|
||||
const toolsController_1 = require("../controllers/toolsController");
|
||||
const resourceService = __importStar(require("../services/resourceService"));
|
||||
// 1. 全局 Mock MCP SDK 和传输层,防止测试时真实启动服务器
|
||||
jest.mock('@modelcontextprotocol/sdk/server/mcp.js');
|
||||
jest.mock('@modelcontextprotocol/sdk/server/stdio.js');
|
||||
jest.mock('../controllers/promptsController');
|
||||
jest.mock('../controllers/toolsController');
|
||||
jest.mock('../services/resourceService');
|
||||
describe('app.ts 接入层逻辑测试', () => {
|
||||
let mockServerInstance;
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
// 2. 模拟 McpServer 实例的行为
|
||||
mockServerInstance = {
|
||||
prompt: jest.fn(),
|
||||
tool: jest.fn(),
|
||||
resource: jest.fn(),
|
||||
connect: jest.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
// 让 McpServer 构造函数返回这个 mock 实例
|
||||
mcp_js_1.McpServer.mockImplementation(() => mockServerInstance);
|
||||
// 拦截 console 输出以保持测试日志整洁
|
||||
jest.spyOn(console, 'error').mockImplementation(() => { });
|
||||
// 核心:使用 isolateModules 重新 require app.ts,确保顶层注册代码被执行
|
||||
jest.isolateModules(() => {
|
||||
require('../app');
|
||||
});
|
||||
});
|
||||
describe('Prompts 逻辑验证 (思维框架)', () => {
|
||||
it('验证参数自动补全:漏传可选参数时应当补全为 "无"', async () => {
|
||||
// 提取 scqa 注册时的回调函数 (第三个参数)
|
||||
const scqaCall = mockServerInstance.prompt.mock.calls.find((c) => c[0] === 'scqa');
|
||||
expect(scqaCall).toBeDefined();
|
||||
const handler = scqaCall[2];
|
||||
// 模拟控制器返回一个标准消息
|
||||
promptsController_1.handlePromptsGet.mockResolvedValueOnce({
|
||||
messages: [{ role: 'user', content: { text: 'test' } }]
|
||||
});
|
||||
// 触发回调:只传入必填的 situation,不传 context 和 objective
|
||||
await handler({ situation: '当前的背景' });
|
||||
// 验证:app.ts 应该调用控制器并补全了参数
|
||||
expect(promptsController_1.handlePromptsGet).toHaveBeenCalledWith('scqa', {
|
||||
situation: '当前的背景',
|
||||
context: '无', // 自动补全逻辑
|
||||
objective: '无' // 自动补全逻辑
|
||||
});
|
||||
});
|
||||
it('验证消息角色过滤:应当只保留 user 和 assistant 消息', async () => {
|
||||
const handler = mockServerInstance.prompt.mock.calls.find((c) => c[0] === 'scqa')[2];
|
||||
// 模拟控制器返回包含 system 消息的序列
|
||||
promptsController_1.handlePromptsGet.mockResolvedValueOnce({
|
||||
messages: [
|
||||
{ role: 'system', content: { type: 'text', text: '指令' } },
|
||||
{ role: 'user', content: { type: 'text', text: '问题' } },
|
||||
{ role: 'assistant', content: { type: 'text', text: '回答' } }
|
||||
]
|
||||
});
|
||||
const result = await handler({ situation: 's' });
|
||||
// 验证:system 消息应该被 filter 过滤掉
|
||||
expect(result.messages).toHaveLength(2);
|
||||
expect(result.messages[0].role).toBe('user');
|
||||
expect(result.messages[1].role).toBe('assistant');
|
||||
// 验证元数据补全
|
||||
expect(result.messages[0].content._meta).toBeUndefined(); // 应按代码逻辑设为 undefined
|
||||
});
|
||||
});
|
||||
describe('Tools 逻辑验证 (执行工具)', () => {
|
||||
it('应当将控制器返回的结果正确映射为 type: "text" 格式', async () => {
|
||||
const toolCall = mockServerInstance.tool.mock.calls.find((c) => c[0] === 'save_note');
|
||||
const handler = toolCall[2];
|
||||
// 模拟控制器返回原始文本内容
|
||||
toolsController_1.handleToolCall.mockResolvedValueOnce({
|
||||
content: [{ text: '保存成功' }]
|
||||
});
|
||||
const result = await handler({ filename: 'a.md', content: 'c' });
|
||||
// 验证:app.ts 应该通过 .map 补全了 type: "text"
|
||||
expect(result.content[0]).toEqual({
|
||||
type: 'text',
|
||||
text: '保存成功'
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('Resources 逻辑验证 (静态资源)', () => {
|
||||
it('验证资源读取:应当从 note:// URI 中解码文件名并调用服务层', async () => {
|
||||
// 寻找 resource 注册时的读取处理函数 (第三个参数)
|
||||
const readHandler = mockServerInstance.resource.mock.calls[0][2];
|
||||
const mockUri = { href: 'note://local/%E6%B5%8B%E8%AF%95.md' }; // "测试.md" 的编码
|
||||
const mockParams = { filename: '%E6%B5%8B%E8%AF%95.md' };
|
||||
resourceService.readObsidianNote.mockResolvedValueOnce('# 笔记内容');
|
||||
const result = await readHandler(mockUri, mockParams);
|
||||
// 验证:URI 是否被正确 decodeURIComponent 解码为 "测试.md"
|
||||
expect(resourceService.readObsidianNote).toHaveBeenCalledWith('测试.md');
|
||||
expect(result.contents[0].text).toBe('# 笔记内容');
|
||||
});
|
||||
});
|
||||
});
|
||||
//# sourceMappingURL=app.test.js.map
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"app.test.js","sourceRoot":"","sources":["../../src/__test__/app.test.ts"],"names":[],"mappings":"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"}
|
||||
|
|
@ -0,0 +1,232 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
||||
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
||||
const zod_1 = require("zod");
|
||||
const promptsController_1 = require("./controllers/promptsController");
|
||||
const toolsController_1 = require("./controllers/toolsController");
|
||||
const resourceService_1 = require("./services/resourceService");
|
||||
// ==========================================
|
||||
// 初始化 MCP Server
|
||||
// ==========================================
|
||||
const server = new mcp_js_1.McpServer({
|
||||
name: 'Project-Caffeine-S2-Prompt-Strategy',
|
||||
version: '2.0.0'
|
||||
});
|
||||
// ==========================================
|
||||
// 注册 Prompts 原语(多维思维框架模板)
|
||||
// ==========================================
|
||||
server.prompt('scqa', // 示例:SCQA 框架
|
||||
{
|
||||
situation: zod_1.z.string().describe('情境'),
|
||||
complication: zod_1.z.string().describe('复杂性'),
|
||||
question: zod_1.z.string().describe('问题'),
|
||||
answer: zod_1.z.string().describe('答案')
|
||||
}, async (args, extra) => {
|
||||
const result = await (0, promptsController_1.handlePromptsGet)('scqa', args);
|
||||
return {
|
||||
...result,
|
||||
messages: Array.isArray(result.messages)
|
||||
? result.messages
|
||||
.filter((msg) => msg.role === "user" || msg.role === "assistant")
|
||||
.map((msg) => ({
|
||||
...msg,
|
||||
// Optionally ensure content has required structure
|
||||
content: {
|
||||
...msg.content,
|
||||
// Add default annotations/_meta if missing
|
||||
annotations: msg.content.annotations ?? undefined,
|
||||
_meta: msg.content._meta ?? undefined
|
||||
}
|
||||
}))
|
||||
: []
|
||||
};
|
||||
});
|
||||
server.prompt('5whys', // 5 Whys 框架
|
||||
{ problem: zod_1.z.string().describe('需要分析的问题或现象') }, async (args) => {
|
||||
const result = await (0, promptsController_1.handlePromptsGet)('5whys', args);
|
||||
return {
|
||||
...result,
|
||||
messages: Array.isArray(result.messages)
|
||||
? result.messages
|
||||
.filter((msg) => msg.role === "user" || msg.role === "assistant")
|
||||
.map((msg) => ({
|
||||
...msg,
|
||||
content: {
|
||||
...msg.content,
|
||||
annotations: msg.content.annotations ?? undefined,
|
||||
_meta: msg.content._meta ?? undefined
|
||||
}
|
||||
}))
|
||||
: []
|
||||
};
|
||||
});
|
||||
server.prompt('5w3h', // 5W3H 框架
|
||||
{ topic: zod_1.z.string().describe('需要分析的主题') }, async (args) => {
|
||||
const result = await (0, promptsController_1.handlePromptsGet)('5w3h', args);
|
||||
return {
|
||||
...result,
|
||||
messages: Array.isArray(result.messages)
|
||||
? result.messages
|
||||
.filter((msg) => msg.role === "user" || msg.role === "assistant")
|
||||
.map((msg) => ({
|
||||
...msg,
|
||||
content: {
|
||||
...msg.content,
|
||||
annotations: msg.content.annotations ?? undefined,
|
||||
_meta: msg.content._meta ?? undefined
|
||||
}
|
||||
}))
|
||||
: []
|
||||
};
|
||||
});
|
||||
server.prompt('swot', // SWOT 框架
|
||||
{ entity: zod_1.z.string().describe('分析对象(企业、项目等)') }, async (args) => {
|
||||
const result = await (0, promptsController_1.handlePromptsGet)('swot', args);
|
||||
return {
|
||||
...result,
|
||||
messages: Array.isArray(result.messages)
|
||||
? result.messages
|
||||
.filter((msg) => msg.role === "user" || msg.role === "assistant")
|
||||
.map((msg) => ({
|
||||
...msg,
|
||||
content: {
|
||||
...msg.content,
|
||||
annotations: msg.content.annotations ?? undefined,
|
||||
_meta: msg.content._meta ?? undefined
|
||||
}
|
||||
}))
|
||||
: []
|
||||
};
|
||||
});
|
||||
server.prompt('pestle', // PESTLE 框架
|
||||
{ domain: zod_1.z.string().describe('行业或领域') }, async (args) => {
|
||||
const result = await (0, promptsController_1.handlePromptsGet)('pestle', args);
|
||||
return {
|
||||
...result,
|
||||
messages: Array.isArray(result.messages)
|
||||
? result.messages
|
||||
.filter((msg) => msg.role === "user" || msg.role === "assistant")
|
||||
.map((msg) => ({
|
||||
...msg,
|
||||
content: {
|
||||
...msg.content,
|
||||
annotations: msg.content.annotations ?? undefined,
|
||||
_meta: msg.content._meta ?? undefined
|
||||
}
|
||||
}))
|
||||
: []
|
||||
};
|
||||
});
|
||||
// ==========================================
|
||||
// 注册工具:generate_search_queries
|
||||
// ==========================================
|
||||
server.tool('generate_search_queries', { query: zod_1.z.string().describe('用户的原始查询语句') }, async (args, extra) => {
|
||||
const result = await (0, toolsController_1.handleToolCall)('generate_search_queries', args);
|
||||
// Ensure each content item has type: "text" (not a generic string)
|
||||
return {
|
||||
...result,
|
||||
content: result.content.map((item) => ({
|
||||
...item,
|
||||
type: "text"
|
||||
}))
|
||||
};
|
||||
});
|
||||
// =====================================================
|
||||
// 注册工具:list_local_notes, read_local_note, save_note
|
||||
// =====================================================
|
||||
// 工具:列出本地笔记
|
||||
server.tool('list_local_notes', {}, async (args) => {
|
||||
const result = await (0, toolsController_1.handleToolCall)('list_local_notes', args);
|
||||
return {
|
||||
...result,
|
||||
content: result.content.map((item) => ({
|
||||
type: 'text',
|
||||
text: item.text
|
||||
}))
|
||||
};
|
||||
});
|
||||
// 工具:读取本地笔记
|
||||
server.tool('read_local_note', { filename: zod_1.z.string().describe('需要读取的笔记文件名,必须包含 .md 后缀') }, async (args) => {
|
||||
const result = await (0, toolsController_1.handleToolCall)('read_local_note', args);
|
||||
return {
|
||||
...result,
|
||||
content: result.content.map((item) => ({
|
||||
type: 'text',
|
||||
text: item.text
|
||||
}))
|
||||
};
|
||||
});
|
||||
// 工具:保存笔记到本地知识库
|
||||
server.tool('save_note', {
|
||||
filename: zod_1.z.string().describe('笔记文件名,必须以 .md 结尾'),
|
||||
content: zod_1.z.string().describe('笔记内容(Markdown 格式)')
|
||||
}, async (args) => {
|
||||
const result = await (0, toolsController_1.handleToolCall)('save_note', args);
|
||||
return {
|
||||
...result,
|
||||
content: result.content.map((item) => ({
|
||||
type: 'text',
|
||||
text: item.text
|
||||
}))
|
||||
};
|
||||
});
|
||||
// ==========================================
|
||||
// 注册 Resources 原语(暴露本地笔记供客户端勾选)
|
||||
// ==========================================
|
||||
// 使用 ResourceTemplate 注册动态资源
|
||||
server.resource("local-notes", // 资源名称
|
||||
new mcp_js_1.ResourceTemplate("note://local/{filename}", {
|
||||
// 实现列表功能:返回所有可用的笔记资源
|
||||
list: async () => {
|
||||
try {
|
||||
const notes = await (0, resourceService_1.listObsidianNotes)();
|
||||
return {
|
||||
resources: notes.map(filename => ({
|
||||
name: filename,
|
||||
uri: `note://local/${encodeURIComponent(filename)}`,
|
||||
mimeType: "text/markdown",
|
||||
description: `本地笔记: ${filename}`
|
||||
}))
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
console.error('[Resources] 列出资源失败:', error);
|
||||
return { resources: [] };
|
||||
}
|
||||
}
|
||||
}),
|
||||
// 处理资源读取:根据 URI 中的 filename 参数读取笔记内容
|
||||
async (uri, { filename }) => {
|
||||
try {
|
||||
// filename 参数由 ResourceTemplate 自动从 URI 中提取
|
||||
const filenameStr = Array.isArray(filename) ? filename[0] : filename;
|
||||
const decodedFilename = decodeURIComponent(filenameStr);
|
||||
const content = await (0, resourceService_1.readObsidianNote)(decodedFilename);
|
||||
return {
|
||||
contents: [{
|
||||
uri: uri.href,
|
||||
mimeType: "text/markdown",
|
||||
text: content
|
||||
}]
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
// 错误处理:返回错误信息
|
||||
throw new Error(`读取笔记失败: ${error.message}`);
|
||||
}
|
||||
});
|
||||
// ==========================================
|
||||
// 启动 STDIO 传输层
|
||||
// ==========================================
|
||||
async function start() {
|
||||
console.error('[S2] 正在启动 MCP Server (Prompts + 检索词工具)...');
|
||||
const transport = new stdio_js_1.StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
console.error('[S2] MCP Server 已就绪,等待 Cherry Studio 连接');
|
||||
}
|
||||
start().catch((err) => {
|
||||
console.error('[S2] 服务器启动失败:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
//# sourceMappingURL=app.js.map
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
"use strict";
|
||||
//# sourceMappingURL=app.test.js.map
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"app.test.js","sourceRoot":"","sources":["../src/app.test.ts"],"names":[],"mappings":""}
|
||||
153
projects/arabica/src/sprint2/dist/controllers/__test__/promptsController.test.js
vendored
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
// src/controllers/__tests__/promptsController.test.ts
|
||||
const promptsController_1 = require("../promptsController");
|
||||
const promptService = __importStar(require("../../services/promptService"));
|
||||
// 模拟 promptService 模块
|
||||
jest.mock('../../services/promptService');
|
||||
// 类型断言,便于 TypeScript 识别模拟函数的类型
|
||||
const mockedPromptService = jest.mocked(promptService);
|
||||
describe('promptsController', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
describe('handlePromptsList', () => {
|
||||
it('应该正确将服务层返回的框架列表映射为 prompts/list 响应格式', async () => {
|
||||
// 模拟服务层返回的数据
|
||||
const mockFrameworks = [
|
||||
{
|
||||
name: 'scqa',
|
||||
description: 'SCQA 框架',
|
||||
parameters: [
|
||||
{ name: 'situation', description: '情境', required: true },
|
||||
{ name: 'complication', description: '复杂性', required: true }
|
||||
],
|
||||
template: '...', // 这些字段不会被 list 使用,但模拟时可省略
|
||||
systemPrompt: '...'
|
||||
},
|
||||
{
|
||||
name: 'swot',
|
||||
description: 'SWOT 分析',
|
||||
parameters: [
|
||||
{ name: 'entity', description: '分析对象', required: true }
|
||||
],
|
||||
template: '...'
|
||||
}
|
||||
];
|
||||
mockedPromptService.listFrameworks.mockResolvedValue(mockFrameworks);
|
||||
const result = await (0, promptsController_1.handlePromptsList)();
|
||||
// 验证返回结构符合 MCP prompts/list 规范
|
||||
expect(result).toHaveProperty('prompts');
|
||||
expect(Array.isArray(result.prompts)).toBe(true);
|
||||
expect(result.prompts).toHaveLength(2);
|
||||
// 验证每个 prompt 的字段映射正确
|
||||
expect(result.prompts[0]).toEqual({
|
||||
name: 'scqa',
|
||||
description: 'SCQA 框架',
|
||||
arguments: [
|
||||
{ name: 'situation', description: '情境', required: true },
|
||||
{ name: 'complication', description: '复杂性', required: true }
|
||||
]
|
||||
});
|
||||
expect(result.prompts[1]).toEqual({
|
||||
name: 'swot',
|
||||
description: 'SWOT 分析',
|
||||
arguments: [
|
||||
{ name: 'entity', description: '分析对象', required: true }
|
||||
]
|
||||
});
|
||||
// 验证服务层被调用一次
|
||||
expect(mockedPromptService.listFrameworks).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it('当服务层返回空数组时,应该返回空列表', async () => {
|
||||
mockedPromptService.listFrameworks.mockResolvedValue([]);
|
||||
const result = await (0, promptsController_1.handlePromptsList)();
|
||||
expect(result).toEqual({ prompts: [] });
|
||||
expect(mockedPromptService.listFrameworks).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it('当服务层抛出异常时,应该传播异常(由调用方处理)', async () => {
|
||||
const error = new Error('加载框架失败');
|
||||
mockedPromptService.listFrameworks.mockRejectedValue(error);
|
||||
await expect((0, promptsController_1.handlePromptsList)()).rejects.toThrow('加载框架失败');
|
||||
});
|
||||
});
|
||||
describe('handlePromptsGet', () => {
|
||||
const mockName = 'scqa';
|
||||
const mockArgs = { situation: '市场增长放缓' };
|
||||
it('当服务层成功返回时,应该返回包含 description 和 messages 的响应', async () => {
|
||||
const mockServiceResult = {
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: { type: "text", text: '你是一名战略顾问' }
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: { type: "text", text: '请分析情境:市场增长放缓' }
|
||||
}
|
||||
]
|
||||
};
|
||||
mockedPromptService.getFramework.mockResolvedValue(mockServiceResult);
|
||||
const result = await (0, promptsController_1.handlePromptsGet)(mockName, mockArgs);
|
||||
// 验证返回结构
|
||||
expect(result).toHaveProperty('description', '框架: scqa');
|
||||
expect(result).toHaveProperty('messages');
|
||||
expect(result.messages).toEqual(mockServiceResult.messages);
|
||||
// 验证服务层被正确调用
|
||||
expect(mockedPromptService.getFramework).toHaveBeenCalledWith(mockName, mockArgs);
|
||||
});
|
||||
it('当 args 为 undefined 时,应传入空对象给服务层', async () => {
|
||||
const mockServiceResult = { messages: [] };
|
||||
mockedPromptService.getFramework.mockResolvedValue(mockServiceResult);
|
||||
// 调用时第二个参数为 undefined
|
||||
await (0, promptsController_1.handlePromptsGet)(mockName, undefined);
|
||||
expect(mockedPromptService.getFramework).toHaveBeenCalledWith(mockName, {} // 预期被转换为空对象
|
||||
);
|
||||
});
|
||||
it('当服务层抛出错误时,应重新抛出错误,并包装错误信息', async () => {
|
||||
const serviceError = new Error('框架不存在');
|
||||
mockedPromptService.getFramework.mockRejectedValue(serviceError);
|
||||
// 验证抛出的错误包含原始信息
|
||||
await expect((0, promptsController_1.handlePromptsGet)(mockName, mockArgs)).rejects.toThrow('获取框架失败: 框架不存在');
|
||||
});
|
||||
it('当服务层抛出非 Error 类型时,也应正确处理', async () => {
|
||||
// 模拟服务层抛出一个字符串
|
||||
mockedPromptService.getFramework.mockRejectedValue('some string error');
|
||||
await expect((0, promptsController_1.handlePromptsGet)(mockName, mockArgs)).rejects.toThrow('获取框架失败: some string error');
|
||||
});
|
||||
});
|
||||
});
|
||||
//# sourceMappingURL=promptsController.test.js.map
|
||||
1
projects/arabica/src/sprint2/dist/controllers/__test__/promptsController.test.js.map
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"promptsController.test.js","sourceRoot":"","sources":["../../../src/controllers/__test__/promptsController.test.ts"],"names":[],"mappings":"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"}
|
||||
153
projects/arabica/src/sprint2/dist/controllers/__test__/promtsController.test.js
vendored
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
// src/controllers/__tests__/promptsController.test.ts
|
||||
const promptsController_1 = require("../promptsController");
|
||||
const promptService = __importStar(require("../../services/promptService"));
|
||||
// 模拟 promptService 模块
|
||||
jest.mock('../../services/promptService');
|
||||
// 类型断言,便于 TypeScript 识别模拟函数的类型
|
||||
const mockedPromptService = jest.mocked(promptService);
|
||||
describe('promptsController', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
describe('handlePromptsList', () => {
|
||||
it('应该正确将服务层返回的框架列表映射为 prompts/list 响应格式', async () => {
|
||||
// 模拟服务层返回的数据
|
||||
const mockFrameworks = [
|
||||
{
|
||||
name: 'scqa',
|
||||
description: 'SCQA 框架',
|
||||
parameters: [
|
||||
{ name: 'situation', description: '情境', required: true },
|
||||
{ name: 'complication', description: '复杂性', required: true }
|
||||
],
|
||||
template: '...', // 这些字段不会被 list 使用,但模拟时可省略
|
||||
systemPrompt: '...'
|
||||
},
|
||||
{
|
||||
name: 'swot',
|
||||
description: 'SWOT 分析',
|
||||
parameters: [
|
||||
{ name: 'entity', description: '分析对象', required: true }
|
||||
],
|
||||
template: '...'
|
||||
}
|
||||
];
|
||||
mockedPromptService.listFrameworks.mockResolvedValue(mockFrameworks);
|
||||
const result = await (0, promptsController_1.handlePromptsList)();
|
||||
// 验证返回结构符合 MCP prompts/list 规范
|
||||
expect(result).toHaveProperty('prompts');
|
||||
expect(Array.isArray(result.prompts)).toBe(true);
|
||||
expect(result.prompts).toHaveLength(2);
|
||||
// 验证每个 prompt 的字段映射正确
|
||||
expect(result.prompts[0]).toEqual({
|
||||
name: 'scqa',
|
||||
description: 'SCQA 框架',
|
||||
arguments: [
|
||||
{ name: 'situation', description: '情境', required: true },
|
||||
{ name: 'complication', description: '复杂性', required: true }
|
||||
]
|
||||
});
|
||||
expect(result.prompts[1]).toEqual({
|
||||
name: 'swot',
|
||||
description: 'SWOT 分析',
|
||||
arguments: [
|
||||
{ name: 'entity', description: '分析对象', required: true }
|
||||
]
|
||||
});
|
||||
// 验证服务层被调用一次
|
||||
expect(mockedPromptService.listFrameworks).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it('当服务层返回空数组时,应该返回空列表', async () => {
|
||||
mockedPromptService.listFrameworks.mockResolvedValue([]);
|
||||
const result = await (0, promptsController_1.handlePromptsList)();
|
||||
expect(result).toEqual({ prompts: [] });
|
||||
expect(mockedPromptService.listFrameworks).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it('当服务层抛出异常时,应该传播异常(由调用方处理)', async () => {
|
||||
const error = new Error('加载框架失败');
|
||||
mockedPromptService.listFrameworks.mockRejectedValue(error);
|
||||
await expect((0, promptsController_1.handlePromptsList)()).rejects.toThrow('加载框架失败');
|
||||
});
|
||||
});
|
||||
describe('handlePromptsGet', () => {
|
||||
const mockName = 'scqa';
|
||||
const mockArgs = { situation: '市场增长放缓' };
|
||||
it('当服务层成功返回时,应该返回包含 description 和 messages 的响应', async () => {
|
||||
const mockServiceResult = {
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: { type: "text", text: '你是一名战略顾问' }
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: { type: "text", text: '请分析情境:市场增长放缓' }
|
||||
}
|
||||
]
|
||||
};
|
||||
mockedPromptService.getFramework.mockResolvedValue(mockServiceResult);
|
||||
const result = await (0, promptsController_1.handlePromptsGet)(mockName, mockArgs);
|
||||
// 验证返回结构
|
||||
expect(result).toHaveProperty('description', '框架: scqa');
|
||||
expect(result).toHaveProperty('messages');
|
||||
expect(result.messages).toEqual(mockServiceResult.messages);
|
||||
// 验证服务层被正确调用
|
||||
expect(mockedPromptService.getFramework).toHaveBeenCalledWith(mockName, mockArgs);
|
||||
});
|
||||
it('当 args 为 undefined 时,应传入空对象给服务层', async () => {
|
||||
const mockServiceResult = { messages: [] };
|
||||
mockedPromptService.getFramework.mockResolvedValue(mockServiceResult);
|
||||
// 调用时第二个参数为 undefined
|
||||
await (0, promptsController_1.handlePromptsGet)(mockName, undefined);
|
||||
expect(mockedPromptService.getFramework).toHaveBeenCalledWith(mockName, {} // 预期被转换为空对象
|
||||
);
|
||||
});
|
||||
it('当服务层抛出错误时,应重新抛出错误,并包装错误信息', async () => {
|
||||
const serviceError = new Error('框架不存在');
|
||||
mockedPromptService.getFramework.mockRejectedValue(serviceError);
|
||||
// 验证抛出的错误包含原始信息
|
||||
await expect((0, promptsController_1.handlePromptsGet)(mockName, mockArgs)).rejects.toThrow('获取框架失败: 框架不存在');
|
||||
});
|
||||
it('当服务层抛出非 Error 类型时,也应正确处理', async () => {
|
||||
// 模拟服务层抛出一个字符串
|
||||
mockedPromptService.getFramework.mockRejectedValue('some string error');
|
||||
await expect((0, promptsController_1.handlePromptsGet)(mockName, mockArgs)).rejects.toThrow('获取框架失败: some string error');
|
||||
});
|
||||
});
|
||||
});
|
||||
//# sourceMappingURL=promtsController.test.js.map
|
||||
1
projects/arabica/src/sprint2/dist/controllers/__test__/promtsController.test.js.map
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"promtsController.test.js","sourceRoot":"","sources":["../../../src/controllers/__test__/promtsController.test.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,sDAAsD;AACtD,4DAA2E;AAC3E,4EAA8D;AAE9D,sBAAsB;AACtB,IAAI,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;AAE1C,+BAA+B;AAC/B,MAAM,mBAAmB,GAAG,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;AAEvD,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,UAAU,CAAC,GAAG,EAAE;QACd,IAAI,CAAC,aAAa,EAAE,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;QACjC,EAAE,CAAC,sCAAsC,EAAE,KAAK,IAAI,EAAE;YACpD,aAAa;YACb,MAAM,cAAc,GAAG;gBACrB;oBACE,IAAI,EAAE,MAAM;oBACZ,WAAW,EAAE,SAAS;oBACtB,UAAU,EAAE;wBACV,EAAE,IAAI,EAAE,WAAW,EAAE,WAAW,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE;wBACxD,EAAE,IAAI,EAAE,cAAc,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE;qBAC7D;oBACD,QAAQ,EAAE,KAAK,EAAE,0BAA0B;oBAC3C,YAAY,EAAE,KAAK;iBACpB;gBACD;oBACE,IAAI,EAAE,MAAM;oBACZ,WAAW,EAAE,SAAS;oBACtB,UAAU,EAAE;wBACV,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE;qBACxD;oBACD,QAAQ,EAAE,KAAK;iBAChB;aACF,CAAC;YACF,mBAAmB,CAAC,cAAc,CAAC,iBAAiB,CAAC,cAAc,CAAC,CAAC;YAErE,MAAM,MAAM,GAAG,MAAM,IAAA,qCAAiB,GAAE,CAAC;YAEzC,+BAA+B;YAC/B,MAAM,CAAC,MAAM,CAAC,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC;YACzC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACjD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YAEvC,sBAAsB;YACtB,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;gBAChC,IAAI,EAAE,MAAM;gBACZ,WAAW,EAAE,SAAS;gBACtB,SAAS,EAAE;oBACT,EAAE,IAAI,EAAE,WAAW,EAAE,WAAW,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE;oBACxD,EAAE,IAAI,EAAE,cAAc,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE;iBAC7D;aACF,CAAC,CAAC;YACH,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;gBAChC,IAAI,EAAE,MAAM;gBACZ,WAAW,EAAE,SAAS;gBACtB,SAAS,EAAE;oBACT,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE;iBACxD;aACF,CAAC,CAAC;YAEH,aAAa;YACb,MAAM,CAAC,mBAAmB,CAAC,cAAc,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;QACtE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,oBAAoB,EAAE,KAAK,IAAI,EAAE;YAClC,mBAAmB,CAAC,cAAc,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC;YAEzD,MAAM,MAAM,GAAG,MAAM,IAAA,qCAAiB,GAAE,CAAC;YAEzC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC;YACxC,MAAM,CAAC,mBAAmB,CAAC,cAAc,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;QACtE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,0BAA0B,EAAE,KAAK,IAAI,EAAE;YACxC,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC;YAClC,mBAAmB,CAAC,cAAc,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC;YAE5D,MAAM,MAAM,CAAC,IAAA,qCAAiB,GAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC9D,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;QAChC,MAAM,QAAQ,GAAG,MAAM,CAAC;QACxB,MAAM,QAAQ,GAAG,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;QAEzC,EAAE,CAAC,6CAA6C,EAAE,KAAK,IAAI,EAAE;YAC3D,MAAM,iBAAiB,GAAG;gBACxB,QAAQ,EAAE;oBACR;wBACE,IAAI,EAAE,QAAoB;wBAC1B,OAAO,EAAE,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,UAAU,EAAE;qBACrD;oBACD;wBACE,IAAI,EAAE,MAAgB;wBACtB,OAAO,EAAE,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,cAAc,EAAE;qBACzD;iBACF;aACF,CAAC;YACF,mBAAmB,CAAC,YAAY,CAAC,iBAAiB,CAAC,iBAAiB,CAAC,CAAC;YAEtE,MAAM,MAAM,GAAG,MAAM,IAAA,oCAAgB,EAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;YAE1D,SAAS;YACT,MAAM,CAAC,MAAM,CAAC,CAAC,cAAc,CAAC,aAAa,EAAE,UAAU,CAAC,CAAC;YACzD,MAAM,CAAC,MAAM,CAAC,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;YAC1C,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC;YAE5D,aAAa;YACb,MAAM,CAAC,mBAAmB,CAAC,YAAY,CAAC,CAAC,oBAAoB,CAC3D,QAAQ,EACR,QAAQ,CACT,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,iCAAiC,EAAE,KAAK,IAAI,EAAE;YAC/C,MAAM,iBAAiB,GAAG,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;YAC3C,mBAAmB,CAAC,YAAY,CAAC,iBAAiB,CAAC,iBAAiB,CAAC,CAAC;YAEtE,sBAAsB;YACtB,MAAM,IAAA,oCAAgB,EAAC,QAAQ,EAAE,SAAgB,CAAC,CAAC;YAEnD,MAAM,CAAC,mBAAmB,CAAC,YAAY,CAAC,CAAC,oBAAoB,CAC3D,QAAQ,EACR,EAAE,CAAC,YAAY;aAChB,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,2BAA2B,EAAE,KAAK,IAAI,EAAE;YACzC,MAAM,YAAY,GAAG,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;YACxC,mBAAmB,CAAC,YAAY,CAAC,iBAAiB,CAAC,YAAY,CAAC,CAAC;YAEjE,gBAAgB;YAChB,MAAM,MAAM,CAAC,IAAA,oCAAgB,EAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAChE,eAAe,CAChB,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,0BAA0B,EAAE,KAAK,IAAI,EAAE;YACxC,eAAe;YACf,mBAAmB,CAAC,YAAY,CAAC,iBAAiB,CAAC,mBAAmB,CAAC,CAAC;YAExE,MAAM,MAAM,CAAC,IAAA,oCAAgB,EAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAChE,2BAA2B,CAC5B,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
||||
215
projects/arabica/src/sprint2/dist/controllers/__test__/toolsControll.test.js
vendored
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
// src/controllers/__tests__/toolsController.test.ts
|
||||
const toolsController_1 = require("../toolsController");
|
||||
const intentService = __importStar(require("../../services/intentService"));
|
||||
const resourceService = __importStar(require("../../services/resourceService"));
|
||||
// 模拟所有依赖的服务
|
||||
jest.mock('../../services/intentService');
|
||||
jest.mock('../../services/resourceService');
|
||||
// 类型断言,方便 TypeScript 识别模拟函数
|
||||
const mockedIntentService = jest.mocked(intentService);
|
||||
const mockedResourceService = jest.mocked(resourceService);
|
||||
describe('toolsController', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
// 可选:模拟 console.error 避免测试输出干扰
|
||||
jest.spyOn(console, 'error').mockImplementation(() => { });
|
||||
});
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
describe('handleToolCall', () => {
|
||||
describe('未知工具名称', () => {
|
||||
it('应返回包含未知工具错误信息的响应', async () => {
|
||||
const result = await (0, toolsController_1.handleToolCall)('unknown_tool', {});
|
||||
expect(result).toEqual({
|
||||
content: [{ type: 'text', text: '未知工具: unknown_tool' }],
|
||||
isError: true
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('generate_search_queries 工具', () => {
|
||||
const toolName = 'generate_search_queries';
|
||||
const validParams = { query: '新能源汽车电池回收' };
|
||||
it('参数校验失败时,应返回参数错误响应', async () => {
|
||||
// 传入缺少 query 的参数
|
||||
const result = await (0, toolsController_1.handleToolCall)(toolName, {});
|
||||
expect(result.isError).toBe(true);
|
||||
expect(result.content[0].text).toContain('参数错误');
|
||||
// 确保服务层未被调用
|
||||
expect(mockedIntentService.generateSearchQueries).not.toHaveBeenCalled();
|
||||
});
|
||||
it('服务层成功执行时,应返回检索词数组的 JSON 字符串', async () => {
|
||||
const mockQueries = ['动力电池回收', '锂离子再生', '环保法规'];
|
||||
mockedIntentService.generateSearchQueries.mockReturnValue(mockQueries);
|
||||
const result = await (0, toolsController_1.handleToolCall)(toolName, validParams);
|
||||
expect(result.isError).toBeUndefined(); // 成功时不设置 isError
|
||||
expect(result.content).toEqual([
|
||||
{ type: 'text', text: JSON.stringify(mockQueries, null, 2) }
|
||||
]);
|
||||
expect(mockedIntentService.generateSearchQueries).toHaveBeenCalledWith(validParams.query);
|
||||
});
|
||||
it('服务层抛出异常时,应返回执行失败响应', async () => {
|
||||
const error = new Error('意图拆解失败');
|
||||
mockedIntentService.generateSearchQueries.mockImplementation(() => {
|
||||
throw error;
|
||||
});
|
||||
const result = await (0, toolsController_1.handleToolCall)(toolName, validParams);
|
||||
expect(result.isError).toBe(true);
|
||||
expect(result.content[0].text).toBe('执行失败: 意图拆解失败');
|
||||
});
|
||||
it('当服务层抛出非 Error 类型时,应返回包含字符串化信息的失败响应', async () => {
|
||||
// 模拟抛出一个字符串
|
||||
mockedIntentService.generateSearchQueries.mockImplementation(() => {
|
||||
throw 'some string error';
|
||||
});
|
||||
const result = await (0, toolsController_1.handleToolCall)(toolName, validParams);
|
||||
expect(result.isError).toBe(true);
|
||||
expect(result.content[0].text).toBe('执行失败: some string error');
|
||||
});
|
||||
});
|
||||
describe('list_local_notes 工具', () => {
|
||||
const toolName = 'list_local_notes';
|
||||
it('服务层成功返回笔记列表时,应返回文本列表', async () => {
|
||||
const mockNotes = ['note1.md', 'note2.md', 'note3.md'];
|
||||
mockedResourceService.listObsidianNotes.mockResolvedValue(mockNotes);
|
||||
const result = await (0, toolsController_1.handleToolCall)(toolName, {});
|
||||
expect(result.isError).toBeUndefined();
|
||||
expect(result.content[0].text).toContain('找到了以下笔记');
|
||||
expect(result.content[0].text).toContain('note1.md\nnote2.md\nnote3.md');
|
||||
});
|
||||
it('服务层返回空列表时,应返回“未找到笔记”', async () => {
|
||||
mockedResourceService.listObsidianNotes.mockResolvedValue([]);
|
||||
const result = await (0, toolsController_1.handleToolCall)(toolName, {});
|
||||
expect(result.content[0].text).toBe('未找到笔记。');
|
||||
});
|
||||
it('服务层抛出异常时,应返回执行失败响应', async () => {
|
||||
const error = new Error('目录不可读');
|
||||
mockedResourceService.listObsidianNotes.mockRejectedValue(error);
|
||||
const result = await (0, toolsController_1.handleToolCall)(toolName, {});
|
||||
expect(result.isError).toBe(true);
|
||||
expect(result.content[0].text).toBe('执行失败: 目录不可读');
|
||||
});
|
||||
it('服务层抛出非 Error 类型时,应返回字符串化信息的失败响应', async () => {
|
||||
mockedResourceService.listObsidianNotes.mockRejectedValue('权限错误');
|
||||
const result = await (0, toolsController_1.handleToolCall)(toolName, {});
|
||||
expect(result.isError).toBe(true);
|
||||
expect(result.content[0].text).toBe('执行失败: 权限错误');
|
||||
});
|
||||
});
|
||||
describe('read_local_note 工具', () => {
|
||||
const toolName = 'read_local_note';
|
||||
const validParams = { filename: 'test.md' };
|
||||
it('参数校验失败(缺少 filename)时,应返回参数错误响应', async () => {
|
||||
const result = await (0, toolsController_1.handleToolCall)(toolName, {});
|
||||
expect(result.isError).toBe(true);
|
||||
expect(result.content[0].text).toContain('参数错误');
|
||||
expect(result.content[0].text).toContain('文件名不能为空');
|
||||
expect(mockedResourceService.readObsidianNote).not.toHaveBeenCalled();
|
||||
});
|
||||
it('参数校验失败(filename 不含 .md)时,应返回参数错误响应', async () => {
|
||||
const result = await (0, toolsController_1.handleToolCall)(toolName, { filename: 'test.txt' });
|
||||
expect(result.isError).toBe(true);
|
||||
expect(result.content[0].text).toContain('参数错误');
|
||||
expect(result.content[0].text).toContain('文件名必须包含 .md 后缀');
|
||||
});
|
||||
it('服务层成功读取笔记时,应返回笔记内容', async () => {
|
||||
const mockContent = '# 测试笔记\n这是一段内容。';
|
||||
mockedResourceService.readObsidianNote.mockResolvedValue(mockContent);
|
||||
const result = await (0, toolsController_1.handleToolCall)(toolName, validParams);
|
||||
expect(result.isError).toBeUndefined();
|
||||
expect(result.content).toEqual([{ type: 'text', text: mockContent }]);
|
||||
expect(mockedResourceService.readObsidianNote).toHaveBeenCalledWith(validParams.filename);
|
||||
});
|
||||
it('服务层抛出异常时,应返回读取失败响应', async () => {
|
||||
const error = new Error('文件不存在');
|
||||
mockedResourceService.readObsidianNote.mockRejectedValue(error);
|
||||
const result = await (0, toolsController_1.handleToolCall)(toolName, validParams);
|
||||
expect(result.isError).toBe(true);
|
||||
expect(result.content[0].text).toBe('读取失败: 文件不存在');
|
||||
});
|
||||
it('服务层抛出非 Error 类型时,应返回字符串化信息的失败响应', async () => {
|
||||
mockedResourceService.readObsidianNote.mockRejectedValue('权限不足');
|
||||
const result = await (0, toolsController_1.handleToolCall)(toolName, validParams);
|
||||
expect(result.isError).toBe(true);
|
||||
expect(result.content[0].text).toBe('读取失败: 权限不足');
|
||||
});
|
||||
});
|
||||
describe('save_note 工具', () => {
|
||||
const toolName = 'save_note';
|
||||
const validParams = { filename: 'new.md', content: '# 新笔记' };
|
||||
it('参数校验失败(缺少 filename)时,应返回参数错误响应', async () => {
|
||||
const result = await (0, toolsController_1.handleToolCall)(toolName, { content: '# 内容' });
|
||||
expect(result.isError).toBe(true);
|
||||
expect(result.content[0].text).toContain('参数错误');
|
||||
expect(result.content[0].text).toContain('filename'); // 错误信息应提及 filename
|
||||
expect(mockedResourceService.saveNote).not.toHaveBeenCalled();
|
||||
});
|
||||
it('参数校验失败(filename 不含 .md)时,应返回参数错误响应', async () => {
|
||||
const result = await (0, toolsController_1.handleToolCall)(toolName, {
|
||||
filename: 'new.txt',
|
||||
content: '# 内容'
|
||||
});
|
||||
expect(result.isError).toBe(true);
|
||||
expect(result.content[0].text).toContain('参数错误');
|
||||
expect(result.content[0].text).toContain('.md');
|
||||
});
|
||||
it('服务层成功保存笔记时,应返回成功信息', async () => {
|
||||
const mockMessage = '笔记已保存至: /path/new.md';
|
||||
mockedResourceService.saveNote.mockResolvedValue(mockMessage);
|
||||
const result = await (0, toolsController_1.handleToolCall)(toolName, validParams);
|
||||
expect(result.isError).toBeUndefined();
|
||||
expect(result.content).toEqual([{ type: 'text', text: mockMessage }]);
|
||||
expect(mockedResourceService.saveNote).toHaveBeenCalledWith(validParams.filename, validParams.content);
|
||||
});
|
||||
it('服务层抛出异常时,应返回保存失败响应', async () => {
|
||||
const error = new Error('磁盘空间不足');
|
||||
mockedResourceService.saveNote.mockRejectedValue(error);
|
||||
const result = await (0, toolsController_1.handleToolCall)(toolName, validParams);
|
||||
expect(result.isError).toBe(true);
|
||||
expect(result.content[0].text).toBe('保存失败: 磁盘空间不足');
|
||||
});
|
||||
it('服务层抛出非 Error 类型时,应返回字符串化信息的失败响应', async () => {
|
||||
mockedResourceService.saveNote.mockRejectedValue('写入错误');
|
||||
const result = await (0, toolsController_1.handleToolCall)(toolName, validParams);
|
||||
expect(result.isError).toBe(true);
|
||||
expect(result.content[0].text).toBe('保存失败: 写入错误');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
//# sourceMappingURL=toolsControll.test.js.map
|
||||
1
projects/arabica/src/sprint2/dist/controllers/__test__/toolsControll.test.js.map
vendored
Normal file
159
projects/arabica/src/sprint2/dist/controllers/__test__/toolsController.test.js
vendored
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
"use strict";
|
||||
/**
|
||||
* Project Caffeine
|
||||
* Copyright (c) 2025-2026 Gitconomy Research
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Contributors:
|
||||
* - 郭晧 <guohao@gitconomy.org> (Initial Author)
|
||||
*/
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const toolsController_1 = require("../toolsController");
|
||||
const intentService = __importStar(require("../../services/intentService"));
|
||||
const resourceService = __importStar(require("../../services/resourceService"));
|
||||
// 1. Mock 服务层模块,防止真实 I/O 和复杂逻辑干扰
|
||||
jest.mock('../../services/intentService');
|
||||
jest.mock('../../services/resourceService');
|
||||
describe('toolsController 单元测试', () => {
|
||||
// 类型断言方便调用 mock 方法
|
||||
const mockedIntentService = intentService;
|
||||
const mockedResourceService = resourceService;
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
// ==========================================
|
||||
// 1. 未知工具处理
|
||||
// ==========================================
|
||||
it('应当正确处理未定义的工具名称', async () => {
|
||||
const result = await (0, toolsController_1.handleToolCall)('invalid_tool_name', {});
|
||||
expect(result.isError).toBe(true);
|
||||
expect(result.content[0].text).toContain('未知工具: invalid_tool_name');
|
||||
});
|
||||
// ==========================================
|
||||
// 2. generate_search_queries 工具测试
|
||||
// ==========================================
|
||||
describe('generate_search_queries', () => {
|
||||
it('当 query 参数为空或缺失时,应当返回参数错误响应', async () => {
|
||||
// 测试空字符串
|
||||
const resultEmpty = await (0, toolsController_1.handleToolCall)('generate_search_queries', { query: '' });
|
||||
expect(resultEmpty.isError).toBe(true);
|
||||
expect(resultEmpty.content[0].text).toContain('参数错误');
|
||||
// 容错处理:由于不同环境 Zod 报错结构差异,只要包含“参数错误”即视为拦截成功
|
||||
// 但理想情况下应包含具体提示,这里我们断言它确实被拦截了
|
||||
});
|
||||
it('当输入合法时,应当调用服务并返回格式化后的结果', async () => {
|
||||
mockedIntentService.generateSearchQueries.mockReturnValue(['词1', '词2']);
|
||||
const result = await (0, toolsController_1.handleToolCall)('generate_search_queries', { query: '人工智能' });
|
||||
expect(mockedIntentService.generateSearchQueries).toHaveBeenCalledWith('人工智能');
|
||||
expect(result.content[0].text).toContain('词1');
|
||||
expect(result.isError).toBeUndefined();
|
||||
});
|
||||
});
|
||||
// ==========================================
|
||||
// 3. list_local_notes 工具测试
|
||||
// ==========================================
|
||||
describe('list_local_notes', () => {
|
||||
it('当成功获取笔记列表时,应当返回带预览格式的文本', async () => {
|
||||
mockedResourceService.listObsidianNotes.mockResolvedValue(['a.md', 'b.md']);
|
||||
const result = await (0, toolsController_1.handleToolCall)('list_local_notes', {});
|
||||
expect(result.content[0].text).toContain('找到了以下笔记');
|
||||
expect(result.content[0].text).toContain('a.md\nb.md');
|
||||
});
|
||||
it('当笔记列表为空时,应当返回友好提示', async () => {
|
||||
mockedResourceService.listObsidianNotes.mockResolvedValue([]);
|
||||
const result = await (0, toolsController_1.handleToolCall)('list_local_notes', {});
|
||||
expect(result.content[0].text).toBe('未找到笔记。');
|
||||
});
|
||||
});
|
||||
// ==========================================
|
||||
// 4. read_local_note 工具测试
|
||||
// ==========================================
|
||||
describe('read_local_note', () => {
|
||||
it('校验测试:当缺少参数时应当返回参数错误', async () => {
|
||||
const result = await (0, toolsController_1.handleToolCall)('read_local_note', {});
|
||||
expect(result.isError).toBe(true);
|
||||
expect(result.content[0].text).toContain('参数错误');
|
||||
});
|
||||
it('校验测试:当文件名后缀不正确时应当拦截', async () => {
|
||||
const result = await (0, toolsController_1.handleToolCall)('read_local_note', { filename: 'test.txt' });
|
||||
expect(result.isError).toBe(true);
|
||||
// 匹配“参数错误”或具体的“后缀”提示
|
||||
expect(result.content[0].text).toMatch(/参数错误|结尾|后缀/);
|
||||
});
|
||||
it('成功流:应当返回服务层读取的笔记内容', async () => {
|
||||
mockedResourceService.readObsidianNote.mockResolvedValue('# 笔记内容');
|
||||
const result = await (0, toolsController_1.handleToolCall)('read_local_note', { filename: 'test.md' });
|
||||
expect(mockedResourceService.readObsidianNote).toHaveBeenCalledWith('test.md');
|
||||
expect(result.content[0].text).toBe('# 笔记内容');
|
||||
});
|
||||
it('异常流:当服务层抛出错误时,应当捕获并返回', async () => {
|
||||
mockedResourceService.readObsidianNote.mockRejectedValue(new Error('文件不存在'));
|
||||
const result = await (0, toolsController_1.handleToolCall)('read_local_note', { filename: 'test.md' });
|
||||
expect(result.isError).toBe(true);
|
||||
expect(result.content[0].text).toContain('读取失败: 文件不存在');
|
||||
});
|
||||
});
|
||||
// ==========================================
|
||||
// 5. save_note 工具测试
|
||||
// ==========================================
|
||||
describe('save_note', () => {
|
||||
it('校验测试:当缺少必需参数时应当报错', async () => {
|
||||
const result = await (0, toolsController_1.handleToolCall)('save_note', { filename: 'new.md' });
|
||||
expect(result.isError).toBe(true);
|
||||
expect(result.content[0].text).toContain('参数错误');
|
||||
});
|
||||
it('成功流:应当调用服务层保存并返回成功提示', async () => {
|
||||
mockedResourceService.saveNote.mockResolvedValue('保存成功至 path');
|
||||
const result = await (0, toolsController_1.handleToolCall)('save_note', {
|
||||
filename: 'new.md',
|
||||
content: 'hello'
|
||||
});
|
||||
expect(mockedResourceService.saveNote).toHaveBeenCalledWith('new.md', 'hello');
|
||||
expect(result.content[0].text).toBe('保存成功至 path');
|
||||
});
|
||||
it('异常流:当捕获到非标准错误(字符串)时,也应能正常返回', async () => {
|
||||
mockedResourceService.saveNote.mockRejectedValue('磁盘已满');
|
||||
const result = await (0, toolsController_1.handleToolCall)('save_note', {
|
||||
filename: 'new.md',
|
||||
content: 'hello'
|
||||
});
|
||||
expect(result.isError).toBe(true);
|
||||
expect(result.content[0].text).toContain('保存失败: 磁盘已满');
|
||||
});
|
||||
});
|
||||
});
|
||||
//# sourceMappingURL=toolsController.test.js.map
|
||||
1
projects/arabica/src/sprint2/dist/controllers/__test__/toolsController.test.js.map
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.handlePromptsList = handlePromptsList;
|
||||
exports.handlePromptsGet = handlePromptsGet;
|
||||
const promptService_1 = require("../services/promptService");
|
||||
async function handlePromptsList() {
|
||||
const frameworks = await (0, promptService_1.listFrameworks)();
|
||||
return {
|
||||
prompts: frameworks.map(f => ({
|
||||
name: f.name,
|
||||
description: f.description,
|
||||
arguments: f.parameters.map(p => ({
|
||||
name: p.name,
|
||||
description: p.description,
|
||||
required: p.required
|
||||
}))
|
||||
}))
|
||||
};
|
||||
}
|
||||
async function handlePromptsGet(name, args) {
|
||||
try {
|
||||
const result = await (0, promptService_1.getFramework)(name, args || {});
|
||||
return {
|
||||
description: `框架: ${name}`,
|
||||
messages: result.messages
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
throw new Error(`获取框架失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=promptsController.js.map
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"promptsController.js","sourceRoot":"","sources":["../../src/controllers/promptsController.ts"],"names":[],"mappings":";;AAEA,8CAaC;AAED,4CAUC;AA3BD,6DAAyE;AAElE,KAAK,UAAU,iBAAiB;IACrC,MAAM,UAAU,GAAG,MAAM,IAAA,8BAAc,GAAE,CAAC;IAC1C,OAAO;QACL,OAAO,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YAC5B,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,WAAW,EAAE,CAAC,CAAC,WAAW;YAC1B,SAAS,EAAE,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;gBAChC,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,WAAW,EAAE,CAAC,CAAC,WAAW;gBAC1B,QAAQ,EAAE,CAAC,CAAC,QAAQ;aACrB,CAAC,CAAC;SACJ,CAAC,CAAC;KACJ,CAAC;AACJ,CAAC;AAEM,KAAK,UAAU,gBAAgB,CAAC,IAAY,EAAE,IAA4B;IAC/E,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,IAAA,4BAAY,EAAC,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC;QACpD,OAAO;YACL,WAAW,EAAE,OAAO,IAAI,EAAE;YAC1B,QAAQ,EAAE,MAAM,CAAC,QAAQ;SAC1B,CAAC;IACJ,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CAAC,WAAW,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;IAC9C,CAAC;AACH,CAAC"}
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.handleToolCall = handleToolCall;
|
||||
const intentService_1 = require("../services/intentService");
|
||||
const resourceService_1 = require("../services/resourceService");
|
||||
const zod_1 = require("zod");
|
||||
const schemas_1 = require("../models/schemas");
|
||||
/**
|
||||
* 统一工具调用处理入口
|
||||
* @param toolName 工具名称
|
||||
* @param params 工具参数对象
|
||||
* @returns MCP 工具响应格式
|
||||
*/
|
||||
async function handleToolCall(toolName, params) {
|
||||
switch (toolName) {
|
||||
case 'generate_search_queries':
|
||||
return handleGenerateSearchQueries(params);
|
||||
case 'list_local_notes':
|
||||
return handleListLocalNotes();
|
||||
case 'read_local_note':
|
||||
return handleReadLocalNote(params);
|
||||
case 'save_note':
|
||||
return handleSaveNote(params);
|
||||
default:
|
||||
return {
|
||||
content: [{ type: 'text', text: `未知工具: ${toolName}` }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 处理检索词生成工具
|
||||
*/
|
||||
async function handleGenerateSearchQueries(params) {
|
||||
// 使用集中管理的 schema 进行参数校验
|
||||
const parseResult = schemas_1.generateSearchQueriesSchema.safeParse(params);
|
||||
if (!parseResult.success) {
|
||||
return {
|
||||
content: [{ type: 'text', text: `参数错误: ${parseResult.error.message}` }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
const { query } = parseResult.data;
|
||||
try {
|
||||
const queries = (0, intentService_1.generateSearchQueries)(query);
|
||||
return {
|
||||
content: [{ type: 'text', text: JSON.stringify(queries, null, 2) }]
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
console.error('[ToolsController] generate_search_queries 失败:', error);
|
||||
return {
|
||||
content: [{ type: 'text', text: `执行失败: ${error.message}` }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 处理列出本地笔记工具
|
||||
*/
|
||||
async function handleListLocalNotes() {
|
||||
try {
|
||||
const notes = await (0, resourceService_1.listObsidianNotes)();
|
||||
return {
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: notes.length > 0 ? `找到了以下笔记:\n${notes.join('\n')}` : '未找到笔记。'
|
||||
}]
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
console.error('[ToolsController] list_local_notes 失败:', error);
|
||||
return {
|
||||
content: [{ type: 'text', text: `执行失败: ${error.message}` }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 处理读取本地笔记工具
|
||||
*/
|
||||
async function handleReadLocalNote(params) {
|
||||
const schema = zod_1.z.object({
|
||||
filename: zod_1.z.string().min(1, '文件名不能为空').includes('.md', { message: '文件名必须包含 .md 后缀' })
|
||||
});
|
||||
const parseResult = schema.safeParse(params);
|
||||
if (!parseResult.success) {
|
||||
return {
|
||||
content: [{ type: 'text', text: `参数错误: ${parseResult.error.message}` }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
const { filename } = parseResult.data;
|
||||
try {
|
||||
const content = await (0, resourceService_1.readObsidianNote)(filename);
|
||||
return {
|
||||
content: [{ type: 'text', text: content }]
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
console.error('[ToolsController] read_local_note 失败:', error);
|
||||
return {
|
||||
content: [{ type: 'text', text: `读取失败: ${error.message}` }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 处理保存笔记工具
|
||||
*/
|
||||
async function handleSaveNote(params) {
|
||||
const parseResult = schemas_1.saveNoteSchema.safeParse(params);
|
||||
if (!parseResult.success) {
|
||||
return {
|
||||
content: [{ type: 'text', text: `参数错误: ${parseResult.error.message}` }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
const { filename, content } = parseResult.data;
|
||||
try {
|
||||
const message = await (0, resourceService_1.saveNote)(filename, content);
|
||||
return {
|
||||
content: [{ type: 'text', text: message }]
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
console.error('[ToolsController] save_note 失败:', error);
|
||||
return {
|
||||
content: [{ type: 'text', text: `保存失败: ${error.message}` }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=toolsController.js.map
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"toolsController.js","sourceRoot":"","sources":["../../src/controllers/toolsController.ts"],"names":[],"mappings":"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"}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"name": "5w3h",
|
||||
"description": "5W3H 分析法:从 What、Why、Who、When、Where、How、How much、How feel 八个维度全面结构化拆解问题,并基于全景拆解得出行动洞察。",
|
||||
"persona": "structured_thinker",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "topic",
|
||||
"description": "需要分析的核心主题、事件或问题",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "context",
|
||||
"description": "该主题发生的特定背景、前置条件或行业环境(可选)",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"name": "objective",
|
||||
"description": "期望通过此次分析达成的核心目标(可选)",
|
||||
"required": false
|
||||
}
|
||||
],
|
||||
"template": "请使用 5W3H 框架分析以下主题:\n核心主题 (Topic):{{topic}}\n背景信息 (Context):{{context}}\n分析目标 (Objective):{{objective}}\n\n请依次从 What(是什么)、Why(为什么)、Who(谁)、When(何时)、Where(何地)、How(如何做)、How much(多少)、How feel(感受如何) 八个维度展开结构化深度拆解,并在最后给出可执行的行动洞察。\n\n输出格式要求:请严格按以下 JSON 格式输出你的分析结果,不要包含任何 Markdown 代码块标记(如反引号包裹的 json),直接输出纯 JSON 文本:\n{\n \"what\": \"描述问题的本质、核心现象或定义的准确陈述\",\n \"why\": \"分析现象产生的原因、背景动因或深层目的\",\n \"who\": \"识别涉及的核心主体、执行者、利益相关者或受众角色\",\n \"when\": \"明确事件发生的时间节点、生命周期、频率或关键时间窗\",\n \"where\": \"界定问题发生的空间范围、物理地点、系统模块或应用场景\",\n \"how\": \"探讨解决问题的途径、落地机制、实施过程或运转模式\",\n \"howMuch\": \"量化指标,如影响规模、涉及成本、人员数量、收益等核心数据\",\n \"howFeel\": \"描述各利益相关方的直观感受、情绪反馈、满意度或体验评价\",\n \"actionableInsights\": [\n \"基于上述8个维度的全景拆解,提炼出的第1条具体、可落地的行动指导建议\",\n \"第2条可落地的行动指导建议\"\n ],\n \"metadata\": {\n \"confidenceScore\": 0.85,\n \"assumptions\": [\"推导此拆解结构的关键前提假设\"]\n }\n}",
|
||||
"systemPrompt": "你是一位擅长运用 5W3H 框架进行结构化思考的分析师,能够全面且精确地覆盖事件的八个维度,确保分析的系统性,并能由繁入简提炼行动洞察。你必须严格遵守 JSON 格式输出规范,确保数据可直接被下游程序结构化解析。",
|
||||
"examples": [
|
||||
{
|
||||
"input": {
|
||||
"topic": "公司推行远程办公政策",
|
||||
"context": "疫情后复工,但员工普遍期望保留一定的弹性工作时间",
|
||||
"objective": "评估该政策的影响面,并为平稳落地提供建议"
|
||||
},
|
||||
"output": "{\n \"what\": \"允许员工定期或永久性在公司以外(主要为家中)完成工作任务的正式制度与新型工作模式。\",\n \"why\": \"顺应混合办公模式普及的趋势,提升员工满意度与工作生活平衡,减少通勤消耗,并降低公司办公场地的物理运营成本。\",\n \"who\": \"直接主体为全体适岗员工;管理主体为各级管理者与HR;支持主体为IT部门;间接利益相关者包括外部客户与供应商。\",\n \"when\": \"计划于2024年Q3开始小范围试点,Q4全面推广,包含前期准备、中期过渡、后期稳定三个完整阶段。\",\n \"where\": \"适用于总部及各分支机构的非现场操作岗位,实际工作地点延伸至员工家中或符合保密要求的远程协作空间。\",\n \"how\": \"通过发布政策文档、发放远程软硬件工具包、开展管理者赋能培训以及将考核标准从‘工时导向’强力转向‘成果导向(OKR)’来落地。\",\n \"howMuch\": \"预计覆盖全公司60%约800名员工,初期软硬件投入约200万,长期预计每年节省150万场地费,员工日均节省通勤1.5小时。\",\n \"howFeel\": \"员工普遍期待且满意度高,但部分中层管理者存在强烈的‘失控感’与焦虑,IT部门面临短期内支持压力暴增的疲惫感。\",\n \"actionableInsights\": [\n \"针对管理者的‘失控感’,HR需在Q3试点前组织专项的‘异步协同与在线目标管理’培训。\",\n \"IT部门需提前建立远程支持工单系统及自助排障知识库,以缓解全员推广初期的咨询并发压力。\"\n ],\n \"metadata\": {\n \"confidenceScore\": 0.88,\n \"assumptions\": [\n \"公司核心业务流程已基本实现数字化,支持云端协作\",\n \"员工居家环境具备基础的网络办公条件\"\n ]\n }\n}"
|
||||
},
|
||||
{
|
||||
"input": {
|
||||
"topic": "城市共享单车乱停乱放问题",
|
||||
"context": "市中心早晚高峰期间,大量单车堵塞地铁口和人行盲道",
|
||||
"objective": "全面拆解现象成因,为城市管理部门提供治理抓手"
|
||||
},
|
||||
"output": "{\n \"what\": \"共享单车使用结束后,用户未按规定停放至指定电子围栏或物理区域,导致违规占用盲道、消防通道及绿化带的现象。\",\n \"why\": \"用户追求极致便利的惰性心理;企业为抢占市场超量投放且电子围栏技术精度不足;政府缺乏实时动态的联合监管与企业惩处机制。\",\n \"who\": \"违规主体为部分单车用户;管理责任方为单车运营企业及城市街道城管部门;受损主体为普通路人、视障群体及社区居民。\",\n \"when\": \"乱停放现象全天候存在,但早晚潮汐通勤高峰期后(上午9点、下午7点)及周末节假日的商圈周边尤为严重且集中。\",\n \"where\": \"高度集中于地铁出入口、大型商圈外围、老旧写字楼周边以及高校门口等短途接驳需求极其旺盛且停车资源匮乏的区域。\",\n \"how\": \"需通过升级高精度北斗定位强制入栏结算、城管与企业建立动态配额联动扣分机制、以及用户信用分与骑行费率挂钩等综合手段治理。\",\n \"howMuch\": \"某一线城市日均需额外清理2万辆违停单车,耗费500名专职人员,年隐性财政支出超2000万元,且占该市出行投诉总量的30%。\",\n \"howFeel\": \"普通市民对出行受阻感到强烈反感与愤怒;企业面对高昂运维搬运成本感到无奈;基层城管面临极大管理压力与反复回潮的疲劳感。\",\n \"actionableInsights\": [\n \"交警与城管部门应联合约谈头部企业,推行‘按违停率扣减下季度投放配额’的强力商业杠杆机制。\",\n \"在地铁口等潮汐热点区域,引入带有物理锁孔的立体停车架,从物理空间上规范停放形态并增加单点容量。\"\n ],\n \"metadata\": {\n \"confidenceScore\": 0.92,\n \"assumptions\": [\n \"北斗高精度定位芯片在主流共享单车上的普及率已达到技术要求\",\n \"政府具备获取各企业实时车辆定位数据的权限\"\n ]\n }\n}"
|
||||
},
|
||||
{
|
||||
"input": {
|
||||
"topic": "某餐饮品牌推出植物肉新品的市场反响",
|
||||
"context": "品牌希望迎合年轻人的低碳饮食趋势,在核心门店试点推出了植物肉汉堡",
|
||||
"objective": "评估首月市场反馈,决定是否向二三线城市全面推广"
|
||||
},
|
||||
"output": "{\n \"what\": \"餐饮品牌推出以大豆、豌豆等植物蛋白为原料、模拟真肉口感和外观的植物基汉堡及相关配套轻食餐品。\",\n \"why\": \"为迎合全球健康饮食、低碳环保的新消费趋势,打造品牌差异化标签,并重点吸引年轻Z世代和弹性素食人群。\",\n \"who\": \"目标受众为年轻白领、健身达人及环保主义者;执行方包括内部研发、市场营销团队及外部植物基供应链头部供应商。\",\n \"when\": \"2024年3月配合‘春季健康季’节点首发上市,目前处于上市满首月的市场反馈收集、数据盘点与复盘期。\",\n \"where\": \"首批在一线城市(北上广深)的50家核心商圈门店及线上主流外卖平台同步推出,暂未下沉至二三线城市门店。\",\n \"how\": \"通过KOL探店种草、推出‘尝鲜价’折扣套餐、在门店布置碳足迹减排科普展板等线上线下全渠道营销组合拳推向市场。\",\n \"howMuch\": \"首月总销量达5万份,占总营收8%,客单价较常规产品溢价15%;营销推广费用约200万,获取全网曝光超1000万次。\",\n \"howFeel\": \"60%尝鲜用户对口感表示满意并愿复购,30%认为价格偏高且有‘科技与狠活’的过度加工顾虑,环保组织则给予了高度赞誉。\",\n \"actionableInsights\": [\n \"针对‘过度加工’的顾虑,需在后续营销中公开并强调植物肉的清洁配料表,突出‘0胆固醇、高蛋白’的健康属性。\",\n \"暂缓向二三线城市全面推广,考虑到30%用户对价格敏感,建议先优化供应链,推出‘半份植物肉+半份蔬菜’的高性价比平替沙拉碗进行下沉测试。\"\n ],\n \"metadata\": {\n \"confidenceScore\": 0.85,\n \"assumptions\": [\n \"一线城市消费者对植物基食品的溢价接受度和概念认知度显著高于下沉市场\"\n ]\n }\n}"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"name": "5whys",
|
||||
"description": "5 Whys 分析法:通过连续追问五次“为什么”,剥开表层现象,探究并锁定导致问题发生的根本原因 (Root Cause),并推导解决方案。",
|
||||
"persona": "root_cause_analyst",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "problem",
|
||||
"description": "需要分析的核心问题、故障或不良现象",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "context",
|
||||
"description": "问题发生的背景信息、前置条件或受影响的范围(可选)",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"name": "goal",
|
||||
"description": "期望通过解决此问题达成的最终目标(可选)",
|
||||
"required": false
|
||||
}
|
||||
],
|
||||
"template": "请使用 5 Whys 分析法探究以下问题的根本原因:\n核心问题 (Problem):{{problem}}\n背景信息 (Context):{{context}}\n改善目标 (Goal):{{goal}}\n\n请严格按照 5 Whys 的逻辑,从表象问题出发,连续进行 5 次有逻辑因果关系的追问,直到找到根本原因,并给出针对根本原因的解决方案。\n\n输出格式要求:请严格按以下 JSON 格式输出你的分析结果,不要包含任何 Markdown 代码块标记(如反引号包裹的 json),直接输出纯 JSON 文本:\n{\n \"problemStatement\": \"清晰界定当前面临的问题\",\n \"whys\": [\n {\n \"level\": 1,\n \"question\": \"为什么会出现上述问题?\",\n \"answer\": \"第一层直接原因\"\n },\n {\n \"level\": 2,\n \"question\": \"基于上一个回答,进一步追问为什么?\",\n \"answer\": \"第二层深层原因\"\n },\n {\n \"level\": 3,\n \"question\": \"继续追问为什么?\",\n \"answer\": \"第三层原因\"\n },\n {\n \"level\": 4,\n \"question\": \"继续追问为什么?\",\n \"answer\": \"第四层原因\"\n },\n {\n \"level\": 5,\n \"question\": \"追问根本机制或系统性缺陷?\",\n \"answer\": \"第五层根本原因\"\n }\n ],\n \"rootCause\": \"综合上述推导,用一句话总结最终的根本原因(通常是系统、流程或管理机制的问题)\",\n \"actionableSolutions\": [\n {\n \"action\": \"针对根本原因的具体整改措施\",\n \"owner\": \"负责执行的角色或部门\"\n }\n ],\n \"metadata\": {\n \"confidenceScore\": 0.85,\n \"assumptions\": [\"推导此因果链条的关键假设\"]\n }\n}",
|
||||
"systemPrompt": "你是一位擅长根因分析的问题解决专家,能够通过系统性追问深入挖掘问题的根本原因。你必须严格遵守 JSON 格式输出规范,确保推导链条清晰且可直接被下游程序结构化解析。",
|
||||
"examples": [
|
||||
{
|
||||
"input": {
|
||||
"problem": "某互联网公司近期新注册用户留存率大幅下降",
|
||||
"context": "公司主打年轻人的社交APP,近期刚投入了大量买量预算,但次日留存跌破了警戒线",
|
||||
"goal": "止住留存率下滑趋势,提升新用户的早期活跃度"
|
||||
},
|
||||
"output": "{\n \"problemStatement\": \"公司投入大量买量预算后,新注册用户的次日留存率大幅下跌,未达到预期活跃度。\",\n \"whys\": [\n {\n \"level\": 1,\n \"question\": \"为什么近期新用户留存率大幅下降?\",\n \"answer\": \"因为大量新用户在注册后的一小时内就卸载了APP或停止了活跃。\"\n },\n {\n \"level\": 2,\n \"question\": \"为什么新用户在一小时内就停止活跃?\",\n \"answer\": \"因为他们觉得新用户引导流程过于复杂繁琐,在第三步就放弃了。\"\n },\n {\n \"level\": 3,\n \"question\": \"为什么新用户引导流程复杂繁琐?\",\n \"answer\": \"因为引导步骤多达五步,且强制要求填写过多个人真实信息(如行业、真实头像等)才能进入主界面。\"\n },\n {\n \"level\": 4,\n \"question\": \"为什么引导步骤设计为五步且强制索要信息?\",\n \"answer\": \"因为产品和运营团队希望在注册漏斗最前端收集最全面的用户画像,以便后续做精准算法推荐。\"\n },\n {\n \"level\": 5,\n \"question\": \"为什么产品团队为了收集画像而牺牲了基础的注册转化体验?\",\n \"answer\": \"因为内部KPI考核导向存在偏差,设计决策基于内部业务诉求,且上线前缺乏可用性测试和A/B测试验证。\"\n }\n ],\n \"rootCause\": \"产品决策机制存在缺陷,以内部数据收集目标凌驾于用户体验之上,且关键路径改版缺乏科学的A/B测试验证闭环。\",\n \"actionableSolutions\": [\n {\n \"action\": \"立即回滚或精简注册流程,将部分非核心信息的收集后置到用户深度体验产品后\",\n \"owner\": \"产品团队\"\n },\n {\n \"action\": \"建立核心链路改版必须通过A/B测试且留存率不降才能全量发布的流程红线\",\n \"owner\": \"质量与数据分析团队\"\n }\n ],\n \"metadata\": {\n \"confidenceScore\": 0.90,\n \"assumptions\": [\n \"新买量的渠道质量与历史渠道一致,非渠道刷量导致\",\n \"留存率的下降与最近一次版本的注册流程改版在时间点上高度重合\"\n ]\n }\n}"
|
||||
},
|
||||
{
|
||||
"input": {
|
||||
"problem": "某制造工厂生产线设备故障率近期异常上升",
|
||||
"context": "生产旺季即将到来,订单压力极大,当前设备停机导致产能出现20%的缺口",
|
||||
"goal": "迅速降低设备故障率,恢复并保障生产旺季的产能供应"
|
||||
},
|
||||
"output": "{\n \"problemStatement\": \"生产旺季前夕,工厂核心生产线设备故障率异常上升,导致停机频发并造成严重产能缺口。\",\n \"whys\": [\n {\n \"level\": 1,\n \"question\": \"为什么设备故障率近期异常上升?\",\n \"answer\": \"因为核心设备的传动轴承等关键部件磨损速度异常加快,导致频繁卡死。\"\n },\n {\n \"level\": 2,\n \"question\": \"为什么关键部件磨损速度异常加快?\",\n \"answer\": \"因为传动部件的润滑严重不足,导致物理摩擦急剧增大。\"\n },\n {\n \"level\": 3,\n \"question\": \"为什么会发生润滑严重不足的情况?\",\n \"answer\": \"因为设备的自动润滑系统出现了堵塞,未能按预定计划加注润滑油。\"\n },\n {\n \"level\": 4,\n \"question\": \"为什么自动润滑系统堵塞没有被及时发现和处理?\",\n \"answer\": \"因为一线维护人员在日常巡检中忽略了对自动润滑系统的检查,误以为它既然是‘自动’的就不需要人工干预。\"\n },\n {\n \"level\": 5,\n \"question\": \"为什么维护人员会产生这种误解并忽略检查?\",\n \"answer\": \"因为设备维护的标准作业程序 (SOP) 文件存在漏洞,未将‘自动润滑系统巡检’明确写入日常检查表,导致责任和标准缺失。\"\n }\n ],\n \"rootCause\": \"设备维护流程与SOP存在管理盲区,未覆盖自动化附件的日常监督,导致预防性维护失效。\",\n \"actionableSolutions\": [\n {\n \"action\": \"立即全面清理自动润滑系统并更换受损轴承,恢复生产线运转\",\n \"owner\": \"设备维修组\"\n },\n {\n \"action\": \"修订全厂设备点检SOP,将所有自动化辅助系统纳入每日必检项,并对维修人员进行全员培训\",\n \"owner\": \"工程与设备管理部\"\n }\n ],\n \"metadata\": {\n \"confidenceScore\": 0.95,\n \"assumptions\": [\n \"润滑油的标号和品质符合设备要求,非油品质量问题\",\n \"设备并未处于超负荷设计极限运行状态\"\n ]\n }\n}"
|
||||
},
|
||||
{
|
||||
"input": {
|
||||
"problem": "高校图书馆自习室“占座”现象严重,引发大量投诉",
|
||||
"context": "期末考试临近,学生对学习空间需求暴增。大量座位上放着书本但长时间无人使用,引发学生间的矛盾",
|
||||
"goal": "提高图书馆座位的实际利用率,平息学生群体的不满情绪"
|
||||
},
|
||||
"output": "{\n \"problemStatement\": \"期末复习高峰期,图书馆自习室存在大量‘物品占座但无人使用’的现象,导致实际资源浪费并引发学生强烈不满。\",\n \"whys\": [\n {\n \"level\": 1,\n \"question\": \"为什么自习室‘占座’现象如此严重?\",\n \"answer\": \"因为很多学生为了确保自己随时有座位,习惯用书本占位后去上课或吃饭,且长时间不返回。\"\n },\n {\n \"level\": 2,\n \"question\": \"为什么学生能够肆无忌惮地长时间占座而不被制止?\",\n \"answer\": \"因为现场没有有效的制止机制,管理员巡查频率极低,且其他学生不敢私自清理他人物品。\"\n },\n {\n \"level\": 3,\n \"question\": \"为什么管理员巡查频率低且不主动清理?\",\n \"answer\": \"因为管理员人手不足,更重要的是,缺乏专门针对‘占座超时’的具体判定标准和管理授权。\"\n },\n {\n \"level\": 4,\n \"question\": \"为什么图书馆一直没有制定占座的判定标准和管理制度?\",\n \"answer\": \"因为图书馆管理层此前一直认为占座是‘学生道德和自觉性’问题,不愿采取强硬的管理手段引发可能的冲突。\"\n },\n {\n \"level\": 5,\n \"question\": \"为什么管理层会将管理责任推给‘自觉’,而不积极介入?\",\n \"answer\": \"因为图书馆缺乏有效的数据监控手段和学生意见反馈渠道,管理层与学生真实痛点脱节,未意识到资源错配的严重性。\"\n }\n ],\n \"rootCause\": \"图书馆管理层服务意识与数字化管理手段双重滞后,未建立科学的空间资源分配规则与违规惩处机制。\",\n \"actionableSolutions\": [\n {\n \"action\": \"紧急出台《自习室防占座管理规定》,明确离座超过45分钟即视为放弃,并安排专人定期清理滞留物品\",\n \"owner\": \"图书馆馆长办公室\"\n },\n {\n \"action\": \"加快引入基于微信小程序的‘座位预约与扫码签到系统’,用技术手段实现座位资源的动态分配与黑名单机制\",\n \"owner\": \"图书馆信息技术部\"\n }\n ],\n \"metadata\": {\n \"confidenceScore\": 0.88,\n \"assumptions\": [\n \"图书馆整体座位数在绝对数量上确实无法满足期末全体学生的同时自习需求,属于存量博弈\",\n \"学生群体对引入公平的预约机制具有较高的接受度\"\n ]\n }\n}"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"name": "pestle",
|
||||
"description": "PESTLE 宏观环境分析:从政治(P)、经济(E)、社会(S)、技术(T)、法律(L)、环境(E)六个维度,全面结构化评估目标行业或领域的外部宏观环境。",
|
||||
"persona": "macro_environment_analyst",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "domain",
|
||||
"description": "需要分析的具体行业、市场或业务领域",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "region",
|
||||
"description": "目标地域范围(如:全球、中国、北美、特定省市等,可选)",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"name": "timeframe",
|
||||
"description": "分析的时间跨度(如:当前现状、未来3-5年等,可选)",
|
||||
"required": false
|
||||
}
|
||||
],
|
||||
"template": "请使用 PESTLE 框架分析以下行业/领域的宏观环境:\n目标行业/领域 (Domain):{{domain}}\n地域范围 (Region):{{region}}\n时间跨度 (Timeframe):{{timeframe}}\n\n请依次从政治、经济、社会、技术、法律、环境六个维度展开深度评估,并给出战略建议。\n\n输出格式要求:请严格按以下 JSON 格式输出你的分析结果,不要包含任何 Markdown 代码块标记(如反引号包裹的 json),直接输出纯 JSON 文本:\n{\n \"political\": {\n \"analysis\": \"政治因素分析,如政府政策、稳定性、贸易导向等\",\n \"impactLevel\": \"High|Medium|Low\",\n \"trend\": \"Positive|Negative|Neutral\"\n },\n \"economic\": {\n \"analysis\": \"经济因素分析,如经济增长、汇率、消费能力等\",\n \"impactLevel\": \"High|Medium|Low\",\n \"trend\": \"Positive|Negative|Neutral\"\n },\n \"social\": {\n \"analysis\": \"社会因素分析,如人口结构、文化趋势、生活方式等\",\n \"impactLevel\": \"High|Medium|Low\",\n \"trend\": \"Positive|Negative|Neutral\"\n },\n \"technological\": {\n \"analysis\": \"技术因素分析,如技术创新、研发周期、自动化替代等\",\n \"impactLevel\": \"High|Medium|Low\",\n \"trend\": \"Positive|Negative|Neutral\"\n },\n \"legal\": {\n \"analysis\": \"法律因素分析,如劳动法、数据安全、知识产权等\",\n \"impactLevel\": \"High|Medium|Low\",\n \"trend\": \"Positive|Negative|Neutral\"\n },\n \"environmental\": {\n \"analysis\": \"环境因素分析,如气候变化、ESG标准、碳排放政策等\",\n \"impactLevel\": \"High|Medium|Low\",\n \"trend\": \"Positive|Negative|Neutral\"\n },\n \"strategicAdvice\": [\n {\n \"strategy\": \"基于上述宏观环境得出的具体战略建议\",\n \"focusArea\": \"研发|市场|合规|供应链等\"\n }\n ],\n \"metadata\": {\n \"confidenceScore\": 0.85,\n \"assumptions\": [\"推导该分析所基于的关键宏观假设\"]\n }\n}",
|
||||
"systemPrompt": "你是一位宏观环境分析专家,擅长运用 PESTLE 框架评估行业外部环境,能够结合具体数据和发展趋势进行深入洞察。你必须严格遵守 JSON 格式输出规范,确保数据可直接被下游程序结构化解析。",
|
||||
"examples": [
|
||||
{
|
||||
"input": {
|
||||
"domain": "人工智能芯片行业",
|
||||
"region": "中国",
|
||||
"timeframe": "未来3-5年"
|
||||
},
|
||||
"output": "{\n \"political\": {\n \"analysis\": \"中国政府将AI芯片列为国家战略新兴产业,通过‘大基金’等提供支持。同时,外部大国对华芯片出口管制持续收紧,倒逼国内加速自主可控。政府‘东数西算’工程也为国产芯片创造了庞大的算力底座应用场景。\",\n \"impactLevel\": \"High\",\n \"trend\": \"Positive\"\n },\n \"economic\": {\n \"analysis\": \"AI大模型带来的算力需求旺盛,市场规模年增速预计超30%。然而,宏观经济弱复苏可能导致部分下游行业资本开支收缩,且先进制程的代工成本持续高企,对芯片设计企业的现金流带来挑战。\",\n \"impactLevel\": \"High\",\n \"trend\": \"Neutral\"\n },\n \"social\": {\n \"analysis\": \"全社会数字化转型加速,AI应用向制造、医疗等实体经济渗透。公众对数据安全和算法伦理关注度上升。此外,高校相关专业扩招,使得底层架构研发人才供给逐渐增加,但高端领军人才依然稀缺。\",\n \"impactLevel\": \"Medium\",\n \"trend\": \"Positive\"\n },\n \"technological\": {\n \"analysis\": \"GPU仍占主导,但ASIC、类脑芯片等新架构不断涌现。先进封装(Chiplet)成为绕开先进制程封锁、提升性能的关键途径。国内在推理芯片端已接近国际水平,但在训练芯片端仍存代差。\",\n \"impactLevel\": \"High\",\n \"trend\": \"Positive\"\n },\n \"legal\": {\n \"analysis\": \"《数据安全法》等法规对芯片底层的数据处理合规提出要求。知识产权保护力度加大,专利战可能频发。海外实体清单限制了部分EDA工具和半导体IP的获取,带来法律和合规断供风险。\",\n \"impactLevel\": \"High\",\n \"trend\": \"Negative\"\n },\n \"environmental\": {\n \"analysis\": \"芯片制造是高耗能产业,在‘双碳’目标下,政府对晶圆厂能耗监管趋严。同时,AI数据中心对算力芯片的‘能效比(PPA)’要求极高,低功耗设计成为核心竞争力,符合ESG投资趋势。\",\n \"impactLevel\": \"Medium\",\n \"trend\": \"Positive\"\n },\n \"strategicAdvice\": [\n {\n \"strategy\": \"加大基于Chiplet架构和RISC-V开源指令集的研发投入,对冲先进制程和商业IP断供的风险。\",\n \"focusArea\": \"研发\"\n },\n {\n \"strategy\": \"紧贴‘东数西算’等政府新基建项目,聚焦信创市场进行国产化替代销售。\",\n \"focusArea\": \"市场\"\n }\n ],\n \"metadata\": {\n \"confidenceScore\": 0.88,\n \"assumptions\": [\n \"全球地缘政治博弈在未来3-5年内不会显著缓和\",\n \"国内算力需求将持续保持指数级增长\"\n ]\n }\n}"
|
||||
},
|
||||
{
|
||||
"input": {
|
||||
"domain": "新能源汽车电池回收行业",
|
||||
"region": "欧洲",
|
||||
"timeframe": "当前至2030年"
|
||||
},
|
||||
"output": "{\n \"political\": {\n \"analysis\": \"欧盟致力于实现‘欧洲绿色协议’目标,高度依赖海外关键矿物(锂、钴、镍)。通过立法推动本土电池供应链闭环,以减少对亚洲材料的依赖,政治支持力度空前。\",\n \"impactLevel\": \"High\",\n \"trend\": \"Positive\"\n },\n \"economic\": {\n \"analysis\": \"由于原始矿产资源价格波动巨大,回收材料的经济价值凸显。预计到2030年欧洲将有大量初代电动车电池退役,催生百亿欧元级别的回收市场,吸引大量风险投资和传统车企资本涌入。\",\n \"impactLevel\": \"High\",\n \"trend\": \"Positive\"\n },\n \"social\": {\n \"analysis\": \"欧洲民众环保意识极强,对循环经济接受度高。消费者在购车时越来越关注产品的全生命周期碳足迹,社会舆论对不可回收的高污染电池持强烈抵制态度。\",\n \"impactLevel\": \"Medium\",\n \"trend\": \"Positive\"\n },\n \"technological\": {\n \"analysis\": \"湿法冶金和火法冶金技术正面临优化,以提高金属回收率并降低能耗。电池直接修复与梯次利用(如降级用于储能)的检测和筛选技术是当前的研发热点,自动化拆解技术急需突破。\",\n \"impactLevel\": \"High\",\n \"trend\": \"Neutral\"\n },\n \"legal\": {\n \"analysis\": \"新《欧盟电池法》强制规定了新电池中必须包含最低比例的回收金属(如16%的钴、6%的锂等),并要求实施‘电池护照’制度。法规极其严苛,对企业的合规溯源能力提出巨大挑战。\",\n \"impactLevel\": \"High\",\n \"trend\": \"Positive\"\n },\n \"environmental\": {\n \"analysis\": \"电池回收本身旨在解决环境问题,但回收工艺(尤其是传统火法)可能产生二次污染和高碳排放。行业必须采用清洁能源驱动的低碳回收工艺,以符合欧盟严苛的ESG环保审核。\",\n \"impactLevel\": \"Medium\",\n \"trend\": \"Neutral\"\n },\n \"strategicAdvice\": [\n {\n \"strategy\": \"加快建立基于区块链的‘电池护照’数据平台,实现全链条数据追踪,满足欧盟合规要求。\",\n \"focusArea\": \"合规与数字化\"\n },\n {\n \"strategy\": \"与欧洲本土大型车企建立早期回收联盟,锁定未来5年的退役电池废料来源。\",\n \"focusArea\": \"供应链\"\n }\n ],\n \"metadata\": {\n \"confidenceScore\": 0.92,\n \"assumptions\": [\n \"新《欧盟电池法》将按预期时间表严格执行不倒退\",\n \"新能源车保有量达到预期规模,提供充足的退役电池\"\n ]\n }\n}"
|
||||
},
|
||||
{
|
||||
"input": {
|
||||
"domain": "在线职业教育",
|
||||
"region": "全球(聚焦新兴市场)",
|
||||
"timeframe": "当前现状"
|
||||
},
|
||||
"output": "{\n \"political\": {\n \"analysis\": \"多国政府为缓解结构性失业,出台政策鼓励成人技能重塑。部分新兴市场政府提供财政补贴支持数字化技能培训,但同时对跨境在线教育内容和数据出境的审查也在趋严。\",\n \"impactLevel\": \"Medium\",\n \"trend\": \"Neutral\"\n },\n \"economic\": {\n \"analysis\": \"全球经济增长放缓导致就业压力增大,职场人‘逆周期’自我提升投资意愿增强。然而,新兴市场用户支付能力有限,平台需要探索B2B2C(通过企业采购)或微支付等灵活的商业模式。\",\n \"impactLevel\": \"High\",\n \"trend\": \"Positive\"\n },\n \"social\": {\n \"analysis\": \"终身学习理念普及,‘斜杠青年’和自由职业者群体扩大。年轻一代习惯碎片化、移动化学习。由于AI对基础文职工作的冲击,社会对软技能和复合型技术技能的培训需求急剧上升。\",\n \"impactLevel\": \"High\",\n \"trend\": \"Positive\"\n },\n \"technological\": {\n \"analysis\": \"AIGC技术正在重塑在线教育,实现课程内容的自动化生成、个性化自适应学习路径以及7x24小时的AI虚拟导师辅导,大幅降低了教研和辅导的人力成本。\",\n \"impactLevel\": \"High\",\n \"trend\": \"Positive\"\n },\n \"legal\": {\n \"analysis\": \"面临不同国家的隐私保护法(如GDPR)合规挑战。AI生成内容的版权归属问题尚不明确。此外,部分国家对颁发职业资格证书的线上机构设有严格的准入资质限制。\",\n \"impactLevel\": \"Medium\",\n \"trend\": \"Negative\"\n },\n \"environmental\": {\n \"analysis\": \"在线教育作为无纸化、零通勤的低碳行业,契合ESG理念。云服务器带来的碳排放虽然存在,但总体环境负面影响极小。\",\n \"impactLevel\": \"Low\",\n \"trend\": \"Neutral\"\n },\n \"strategicAdvice\": [\n {\n \"strategy\": \"全面集成AIGC能力,推出‘AI私教’功能,以低成本在下沉市场和新兴市场提供个性化体验。\",\n \"focusArea\": \"研发与产品\"\n },\n {\n \"strategy\": \"加强与目标市场本地企业HR部门的合作,从单纯卖课向‘培训+推荐就业’闭环服务转型。\",\n \"focusArea\": \"市场与销售\"\n }\n ],\n \"metadata\": {\n \"confidenceScore\": 0.85,\n \"assumptions\": [\n \"AI技术能够有效替代部分基础人工辅导工作\",\n \"新兴市场互联网基础设施足以支持流畅的视频与AI互动\"\n ]\n }\n}"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"name": "scqa",
|
||||
"description": "SCQA 架构:Situation、Complication、Question、Answer,适用于问题分析与方案构建",
|
||||
"persona": "scqa_facilitator",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "situation",
|
||||
"description": "背景描述",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"template": "请使用 SCQA 架构分析以下情境:\n情境 (Situation):{{situation}}\n\n请依次构建:\n1. 复杂化 (Complication):指出情境中存在的矛盾或挑战。\n2. 问题 (Question):基于复杂化提炼出核心问题。\n3. 答案 (Answer):提出解决问题的初步方案或分析路径。\n\n输出格式要求:使用 Markdown 标题分节,每个部分至少 200 字。",
|
||||
"systemPrompt": "你是一位资深战略分析师,擅长运用 SCQA 框架构建逻辑严谨的问题分析报告。",
|
||||
"examples": [
|
||||
{
|
||||
"input": {
|
||||
"situation": "一家传统零售企业过去三年线上销售额年均增长仅 3%,而行业平均水平为 15%。公司拥有实体店网络和稳定供应链,但数字化转型进展缓慢。"
|
||||
},
|
||||
"output": "## 情境 (Situation)\n该传统零售企业拥有超过 20 年的历史,在全国拥有 300 家实体门店,供应链体系成熟,品牌认知度高。然而,随着电商的崛起,消费者购物习惯向线上迁移,公司虽然建立了官网和入驻第三方平台,但线上业务增长乏力,过去三年复合增长率仅为 3%,远低于行业 15% 的平均水平。\n\n## 复杂化 (Complication)\n尽管公司意识到数字化转型的必要性,并投入资金建设线上渠道,但收效甚微。主要矛盾在于:\n1. **线上线下割裂**:实体店和线上业务分属不同团队,缺乏协同,导致库存无法共享,促销活动不一致。\n2. **技术投入不足**:IT 部门仍以维护 ERP 系统为主,缺乏数据分析和个性化推荐能力,用户体验落后于纯电商对手。\n3. **组织文化阻力**:管理层多来自传统零售背景,对数字营销和用户运营理解不深,决策偏向保守,创新项目难以快速落地。\n\n## 问题 (Question)\n在资源有限且行业竞争加剧的背景下,如何通过有效的数字化转型策略,在两年内将线上销售增速提升至行业平均水平(15% 以上),同时保持实体店的盈利能力?\n\n## 答案 (Answer)\n建议采取“线上线下融合(OMO)”战略,具体路径如下:\n1. **组织重构**:成立独立的数字化事业部,由 CEO 直接领导,整合线上线下团队,建立统一考核指标(如全域 GMV、用户生命周期价值)。\n2. **技术升级**:引入成熟的电商中台,实现库存、会员、营销三通;部署 CDP 平台,利用客户数据构建精准画像,推动个性化推荐和自动化营销。\n3. **场景创新**:在门店部署智能终端,支持线上下单门店提货、门店缺货时引导至线上完成购买;试点“直播+门店”模式,将店员培养为导购主播,延伸服务半径。\n4. **敏捷试点**:选择一线城市 20 家门店作为试点,快速验证模式,成功后复制推广。同时设立创新基金,鼓励内部孵化新零售项目。\n\n通过以上措施,预计一年内可提升线上增速至 10%,两年内达到 15%,并带动整体营收增长。"
|
||||
},
|
||||
{
|
||||
"input": {
|
||||
"situation": "某三甲医院近期患者满意度调查得分持续下降,尤其在挂号、候诊环节抱怨最多。医院已推行线上预约,但现场仍存在排队过长、秩序混乱等问题。"
|
||||
},
|
||||
"output": "## 情境 (Situation)\n该医院是区域医疗中心,日均门诊量约 8000 人次。为缓解现场压力,两年前上线了微信预约挂号系统,目前预约率已达 60%。然而近期满意度调查显示,总体得分从 92 分降至 85 分,其中“挂号便捷性”和“候诊时间”两项降幅最大。\n\n## 复杂化 (Complication)\n线上预约本应分流患者,但实际并未有效改善体验:\n1. **预约时段不准**:预约时段为 1 小时窗口,但医生看诊速度不一,导致患者实际等待时间远超预期。\n2. **现场管理混乱**:仍有大量老年患者不会使用手机,依赖现场窗口挂号,与预约患者混流,加剧排队。\n3. **信息系统割裂**:预约系统与诊室叫号系统未打通,护士需手动核对,效率低下。\n4. **医患沟通不足**:医生问诊时间压缩,患者感觉“被敷衍”,投诉增多。\n\n## 问题 (Question)\n在不增加医生工作负荷的前提下,如何通过流程优化和技术手段,将患者平均候诊时间缩短 30%,并提升整体满意度?\n\n## 答案 (Answer)\n提出“精准预约+分时段报到”综合方案:\n1. **精准分时段预约**:将预约窗口缩短至 15 分钟,根据历史看诊速度动态调整每个时段号源,减少患者等候。\n2. **推行分时段报到制**:患者仅需在预约时段前 15 分钟到院扫码报到,过早到达者无法提前进入候诊队列,避免现场拥堵。\n3. **设置“无手机通道”**:保留 2 个现场窗口专为老年人服务,并配备志愿者协助,同时推广自助机。\n4. **打通信息系统**:预约平台与院内 HIS、叫号系统实时对接,实现自动排队叫号。\n5. **增加诊前沟通**:在候诊区屏幕播放科普视频,护士提供基础咨询,缓解患者焦虑。\n\n实施后预计平均候诊时间由 45 分钟降至 30 分钟,满意度提升至 90 分以上。"
|
||||
},
|
||||
{
|
||||
"input": {
|
||||
"situation": "某知识付费平台“课程完课率”不足 10%,用户购买课程后往往只学几节课就放弃,导致复购率低、口碑下滑。平台内容质量不差,但用户缺乏学习动力和督促。"
|
||||
},
|
||||
"output": "## 情境 (Situation)\n该平台上线三年,累计用户 500 万,主打职场技能、个人成长类课程。课程由知名讲师制作,平均评分 4.7 分。然而后台数据显示,完课率(完成 80% 以上课程)仅为 8%,大部分用户购买后一周内活跃,之后迅速流失。\n\n## 复杂化 (Complication)\n内容优质但完课率低,矛盾在于:\n1. **学习模式单一**:课程多为视频录播,缺乏互动和反馈,用户容易感到枯燥。\n2. **缺乏外部督促**:用户靠自律学习,一旦工作繁忙或热情减退,课程就被搁置。\n3. **目标感弱**:用户购买时冲动,但无明确学习计划和成果预期。\n4. **社群运营缺失**:用户之间无交流,无法形成学习氛围和互相激励。\n\n## 问题 (Question)\n如何在不增加大量人力成本的前提下,通过产品机制和轻度运营,将课程完课率提升至 30% 以上,并带动复购?\n\n## 答案 (Answer)\n设计“游戏化+社群轻运营”方案:\n1. **学习路径设计**:将课程拆分为每日 15 分钟的任务包,用户可按节奏完成,系统自动提醒。\n2. **积分与勋章体系**:每完成一节课获得积分,连续学习解锁勋章,可兑换优惠券或实物。\n3. **“学伴”匹配**:根据用户兴趣标签,系统自动匹配 3-5 人组成学习小组,共享进度,互相督促。\n4. **定期直播答疑**:每月邀请讲师进行直播,解答课程疑问,并鼓励学员分享学习心得。\n5. **毕业设计**:课程结束后布置小项目,用户提交后可获得电子证书,增强成就感。\n6. **数据驱动干预**:当用户超过 3 天未学习,自动推送定制化提醒或推荐下一个学习任务。\n\n通过上述机制,预计完课率可提升至 25-30%,复购率增加 20%,同时形成口碑传播。"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"name": "swot",
|
||||
"description": "SWOT 分析:从内部优势(S)、内部劣势(W)、外部机会(O)、外部威胁(T)四个维度全面评估分析对象,并推导交叉战略方案(如SO/ST/WO/WT)。",
|
||||
"persona": "strategy_advisor",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "entity",
|
||||
"description": "分析对象(如企业、产品、项目、个人等)",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "context",
|
||||
"description": "补充的行业背景、当前阶段或面临的核心挑战(可选)",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"name": "competitors",
|
||||
"description": "主要竞争对手或对标对象(可选)",
|
||||
"required": false
|
||||
}
|
||||
],
|
||||
"template": "请使用 SWOT 框架分析以下对象:\n分析对象 (Entity):{{entity}}\n背景信息 (Context):{{context}}\n主要竞争对手 (Competitors):{{competitors}}\n\n请依次从内部优势、内部劣势、外部机会、外部威胁四个维度展开评估,并结合 SWOT 交叉矩阵给出战略建议。\n\n输出格式要求:请严格按以下 JSON 格式输出你的分析结果,不要包含任何 Markdown 代码块标记(如反引号包裹的 json),直接输出纯 JSON 文本:\n{\n \"strengths\": [\n { \"factor\": \"优势点名称\", \"reasoning\": \"简要论证\", \"impactLevel\": \"High|Medium|Low\" }\n ],\n \"weaknesses\": [\n { \"factor\": \"劣势点名称\", \"reasoning\": \"简要论证\", \"impactLevel\": \"High|Medium|Low\" }\n ],\n \"opportunities\": [\n { \"factor\": \"机会点名称\", \"reasoning\": \"简要论证\", \"urgency\": \"High|Medium|Low\" }\n ],\n \"threats\": [\n { \"factor\": \"威胁点名称\", \"reasoning\": \"简要论证\", \"severity\": \"High|Medium|Low\" }\n ],\n \"actionableStrategies\": [\n {\n \"strategyType\": \"SO增长战略 | ST防御战略 | WO扭转战略 | WT生存战略\",\n \"description\": \"结合具体优劣势和外部环境得出的具体行动建议\"\n }\n ],\n \"metadata\": {\n \"confidenceScore\": 0.85,\n \"assumptions\": [\"推导该分析所基于的关键假设\"]\n }\n}",
|
||||
"systemPrompt": "你是一位资深的战略分析顾问,擅长使用 SWOT 框架进行竞争态势分析。你必须清晰区分内外部因素,并通过交叉分析推导可执行的战略。你必须严格遵守 JSON 格式输出规范,确保数据可直接被下游程序结构化解析。",
|
||||
"examples": [
|
||||
{
|
||||
"input": {
|
||||
"entity": "一家专注于新能源汽车电池研发的初创公司",
|
||||
"context": "A轮融资仅够支撑18个月,急需实现技术向商业的转化并打入头部整车厂供应链",
|
||||
"competitors": "宁德时代、LG新能源等传统电池巨头"
|
||||
},
|
||||
"output": "{\n \"strengths\": [\n { \"factor\": \"核心技术领先\", \"reasoning\": \"创始团队拥有5项固态电池核心专利,实验室能量密度比市面主流产品高30%。\", \"impactLevel\": \"High\" },\n { \"factor\": \"轻资产与高灵活性\", \"reasoning\": \"采用研发+技术授权模式,决策链路短,无需背负沉重的重资产建厂包袱。\", \"impactLevel\": \"Medium\" }\n ],\n \"weaknesses\": [\n { \"factor\": \"资金链脆弱\", \"reasoning\": \"A轮资金仅能维持18个月的研发,抗风险能力弱,难以支撑长周期的商业化试错。\", \"impactLevel\": \"High\" },\n { \"factor\": \"缺乏量产与品控经验\", \"reasoning\": \"团队主要为科研背景,中试到规模化量产的技术转化能力未经验证。\", \"impactLevel\": \"High\" }\n ],\n \"opportunities\": [\n { \"factor\": \"高端车型对高能量密度电池渴求\", \"reasoning\": \"整车厂为缓解里程焦虑,对下一代固态电池技术存在巨大的潜在订单需求。\", \"urgency\": \"High\" },\n { \"factor\": \"车企供应链‘去中心化’趋势\", \"reasoning\": \"整车厂不愿被单一巨头(如宁德时代)绑定,积极扶持‘二供’和‘三供’。\", \"urgency\": \"Medium\" }\n ],\n \"threats\": [\n { \"factor\": \"巨头挤压与技术路线封锁\", \"reasoning\": \"传统巨头已投入巨资研发固态电池,初创公司面临巨大的资本与专利壁垒。\", \"severity\": \"High\" },\n { \"factor\": \"原材料价格剧烈波动\", \"reasoning\": \"上游关键矿石材料价格波动易导致研发和中试成本失控。\", \"severity\": \"Medium\" }\n ],\n \"actionableStrategies\": [\n {\n \"strategyType\": \"ST防御战略\",\n \"description\": \"凭借核心专利(S),选择与头部整车厂建立独家联合研发实验室,通过技术入股或预付款形式绑定客户,抵御传统电池巨头的资本挤压(T),缓解资金压力。\"\n },\n {\n \"strategyType\": \"WO扭转战略\",\n \"description\": \"利用车企扶持‘二供’的意愿(O),引入具备制造业经验的外部高管或与成熟代工厂合资,弥补团队缺乏量产经验的短板(W)。\"\n }\n ],\n \"metadata\": {\n \"confidenceScore\": 0.88,\n \"assumptions\": [\n \"固态电池技术在未来两年内能够达到量产前夕的中试标准\",\n \"资本市场对固态电池赛道依然保持较高估值期待\"\n ]\n }\n}"
|
||||
},
|
||||
{
|
||||
"input": {
|
||||
"entity": "个人职业转型:从技术开发岗转向产品经理岗",
|
||||
"context": "30岁,5年后端开发经验,公司内部有产品线扩张的转岗机会",
|
||||
"competitors": "外部经验丰富的产品经理候选人、内部更年轻的转岗者"
|
||||
},
|
||||
"output": "{\n \"strengths\": [\n { \"factor\": \"技术底层理解深\", \"reasoning\": \"5年开发经验使得对系统架构、边界条件及开发成本有精准预判,不易被技术团队忽悠。\", \"impactLevel\": \"High\" },\n { \"factor\": \"业务熟悉度高\", \"reasoning\": \"作为内部员工,熟悉公司现有产品线历史债务及组织协作文化,上手成本极低。\", \"impactLevel\": \"High\" }\n ],\n \"weaknesses\": [\n { \"factor\": \"缺乏用户洞察经验\", \"reasoning\": \"长期面对机器和代码,缺乏系统的竞品分析、用户访谈及交互设计思维训练。\", \"impactLevel\": \"High\" },\n { \"factor\": \"沟通习惯偏向确定性\", \"reasoning\": \"习惯以确定性的技术视角看待问题,需适应产品早期阶段高度模糊和频繁变更的沟通场景。\", \"impactLevel\": \"Medium\" }\n ],\n \"opportunities\": [\n { \"factor\": \"公司内部业务扩张\", \"reasoning\": \"公司正进行中台化或技术驱动型产品线扩张,亟需懂业务逻辑的技术型产品经理。\", \"urgency\": \"High\" },\n { \"factor\": \"AI与大模型浪潮\", \"reasoning\": \"AI产品的设计越来越需要懂算法和接口边界的复合型人才,这是纯业务型PM的软肋。\", \"urgency\": \"Medium\" }\n ],\n \"threats\": [\n { \"factor\": \"年龄与试错成本\", \"reasoning\": \"30岁转岗意味着放弃原有技术积累的溢价,且一旦转型失败难以退回原岗。\", \"severity\": \"High\" },\n { \"factor\": \"外部熟练工竞争\", \"reasoning\": \"行业内涌现大量自带成熟产品方法论的外部候选人,竞争激烈。\", \"severity\": \"High\" }\n ],\n \"actionableStrategies\": [\n {\n \"strategyType\": \"SO增长战略\",\n \"description\": \"利用自身深厚的技术背景和对内部系统的熟悉(S),主动申请主导偏底层逻辑或技术中台类的产品线(O),形成差异化竞争。\"\n },\n {\n \"strategyType\": \"WO扭转战略\",\n \"description\": \"在内部寻找资深业务型PM作为导师,并在日常工作中主动承担原型绘制和用户调研任务(O),快速补齐需求分析短板(W)。\"\n }\n ],\n \"metadata\": {\n \"confidenceScore\": 0.85,\n \"assumptions\": [\n \"公司内部拥有较为宽容的转岗试用机制\",\n \"目标产品线确实属于技术逻辑较重的领域\"\n ]\n }\n}"
|
||||
},
|
||||
{
|
||||
"input": {
|
||||
"entity": "某地方性连锁超市",
|
||||
"context": "在本市拥有40家门店,主打生鲜,当前正面临客流严重下滑的危机",
|
||||
"competitors": "美团优选、多多买菜等全国性社区团购平台"
|
||||
},
|
||||
"output": "{\n \"strengths\": [\n { \"factor\": \"本地供应链深耕\", \"reasoning\": \"与本地农户长期合作,生鲜直采品质可控,生鲜品类的信任度高于纯线上平台。\", \"impactLevel\": \"High\" },\n { \"factor\": \"高密度的前置网点\", \"reasoning\": \"40家门店深入社区腹地,具备开展‘最后一公里’即时零售的天然地理优势。\", \"impactLevel\": \"Medium\" }\n ],\n \"weaknesses\": [\n { \"factor\": \"数字化运营能力弱\", \"reasoning\": \"缺乏精准的用户画像与私域流量池,营销手段依赖传统的纸质海报,触达率极低。\", \"impactLevel\": \"High\" },\n { \"factor\": \"规模采购成本劣势\", \"reasoning\": \"标品(日化、零食)的采购体量远不及全国性互联网巨头,价格竞争处于下风。\", \"impactLevel\": \"High\" }\n ],\n \"opportunities\": [\n { \"factor\": \"即时零售(即时配送)红利\", \"reasoning\": \"消费者对‘半小时达’的即时性需求增加,而社区团购通常是‘次日达’。\", \"urgency\": \"High\" },\n { \"factor\": \"适老化消费场景构建\", \"reasoning\": \"老龄化趋势下,老年人依然偏好实体店挑拣生鲜的体验和人际交流。\", \"urgency\": \"Medium\" }\n ],\n \"threats\": [\n { \"factor\": \"巨头低价倾销抢夺客流\", \"reasoning\": \"社区团购利用资本优势持续高额补贴,导致价格敏感型顾客大量流失。\", \"severity\": \"High\" },\n { \"factor\": \"运营成本刚性上升\", \"reasoning\": \"实体门店租金、人工成本逐年攀升,持续挤压本已微薄的零售利润。\", \"severity\": \"High\" }\n ],\n \"actionableStrategies\": [\n {\n \"strategyType\": \"ST防御战略\",\n \"description\": \"避开与互联网巨头在标品上的价格战(T),利用生鲜直采优势(S),主打高品质、即买即得的差异化生鲜体验,巩固基本盘。\"\n },\n {\n \"strategyType\": \"WO扭转战略\",\n \"description\": \"借势即时零售平台(如入驻美团/饿了么)(O),将实体店转化为前置仓,弥补自身数字化与配送能力的短板(W),拓展年轻客群。\"\n }\n ],\n \"metadata\": {\n \"confidenceScore\": 0.90,\n \"assumptions\": [\n \"超市有一定资金支撑初期接入第三方即时配送平台的改造成本\",\n \"本地核心消费群体仍愿为高品质生鲜支付一定的溢价\"\n ]\n }\n}"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
[
|
||||
{
|
||||
"id": "strategy_advisor",
|
||||
"name": "战略分析顾问",
|
||||
"description": "擅长竞争态势分析,能够清晰区分内外部因素并提出客观见解。",
|
||||
"systemPrompt": "你是一位资深的战略分析顾问,擅长使用 SWOT 框架进行竞争态势分析,能够清晰区分内外部因素并提出客观见解。"
|
||||
},
|
||||
{
|
||||
"id": "root_cause_analyst",
|
||||
"name": "根因分析专家",
|
||||
"description": "擅长通过系统性追问深入挖掘问题的根本原因。",
|
||||
"systemPrompt": "你是一位擅长根因分析的问题解决专家,能够通过系统性追问深入挖掘问题的根本原因。"
|
||||
},
|
||||
{
|
||||
"id": "macro_environment_analyst",
|
||||
"name": "宏观环境分析专家",
|
||||
"description": "擅长运用 PESTLE 框架评估行业外部环境。",
|
||||
"systemPrompt": "你是一位宏观环境分析专家,擅长运用 PESTLE 框架评估行业外部环境,能够结合具体数据和发展趋势进行深入洞察。"
|
||||
},
|
||||
{
|
||||
"id": "structured_thinker",
|
||||
"name": "结构化思维分析师",
|
||||
"description": "擅长运用 5W3H 框架进行系统性拆解。",
|
||||
"systemPrompt": "你是一位擅长运用 5W3H 框架进行结构化思考的分析师,能够全面覆盖问题的各个维度,确保分析的系统性和深度。"
|
||||
},
|
||||
{
|
||||
"id": "scqa_facilitator",
|
||||
"name": "SCQA 引导师",
|
||||
"description": "擅长运用 SCQA 框架构建逻辑严谨的问题分析报告。",
|
||||
"systemPrompt": "你是一位资深战略分析师,擅长运用 SCQA 框架构建逻辑严谨的问题分析报告。"
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
"use strict";
|
||||
/**
|
||||
* Project Caffeine
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.saveNoteSchema = exports.generateSearchQueriesSchema = void 0;
|
||||
const zod_1 = require("zod");
|
||||
exports.generateSearchQueriesSchema = zod_1.z.object({
|
||||
query: zod_1.z.string().min(1),
|
||||
});
|
||||
exports.saveNoteSchema = zod_1.z.object({
|
||||
filename: zod_1.z.string().min(1, '文件名不能为空').refine(name => name.endsWith('.md'), { message: '文件名必须以 .md 结尾' }),
|
||||
content: zod_1.z.string().min(1, '内容不能为空')
|
||||
});
|
||||
//# sourceMappingURL=schemas.js.map
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"schemas.js","sourceRoot":"","sources":["../../src/models/schemas.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,6BAAwB;AAEX,QAAA,2BAA2B,GAAG,OAAC,CAAC,MAAM,CAAC;IAClD,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;CACzB,CAAC,CAAC;AAEU,QAAA,cAAc,GAAG,OAAC,CAAC,MAAM,CAAC;IACrC,QAAQ,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,MAAM,CAC3C,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAC5B,EAAE,OAAO,EAAE,eAAe,EAAE,CAC7B;IACD,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC;CACrC,CAAC,CAAC"}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
/**
|
||||
* Project Caffeine
|
||||
* 单元测试: intentService.ts
|
||||
*/
|
||||
const intentService_1 = require("../intentService");
|
||||
describe('intentService -> generateSearchQueries', () => {
|
||||
it('异常流:当输入为空字符串或全空格时,应当返回默认的后备检索词', () => {
|
||||
// 验证空字符串
|
||||
expect((0, intentService_1.generateSearchQueries)('')).toEqual(['通用研究主题']);
|
||||
// 验证全空格
|
||||
expect((0, intentService_1.generateSearchQueries)(' ')).toEqual(['通用研究主题']);
|
||||
// 验证 null/undefined (通过 any 强制绕过 ts 检查,确保防御性编程生效)
|
||||
expect((0, intentService_1.generateSearchQueries)(null)).toEqual(['通用研究主题']);
|
||||
});
|
||||
it('正常流:当输入包含多个有效关键词时,应当正确分词、去重并返回', () => {
|
||||
// 包含重复的 "人工智能"
|
||||
const query = '人工智能 机器学习 深度学习 人工智能';
|
||||
const result = (0, intentService_1.generateSearchQueries)(query);
|
||||
// 验证去重逻辑
|
||||
expect(result).toEqual(['人工智能', '机器学习', '深度学习']);
|
||||
expect(result).toHaveLength(3);
|
||||
});
|
||||
it('边界测试:当输入包含各种中英文标点符号时,应当正确替换为空格并分词', () => {
|
||||
const query = 'AI芯片,市场趋势?2026;未来发展、产业格局';
|
||||
const result = (0, intentService_1.generateSearchQueries)(query);
|
||||
// 验证所有的标点都被视为了分隔符
|
||||
expect(result).toEqual(['AI芯片', '市场趋势', '2026', '未来发展', '产业格局']);
|
||||
});
|
||||
it('边界测试:当有效检索词少于 3 个时,应当触发补全机制', () => {
|
||||
const query = '量子计算';
|
||||
const result = (0, intentService_1.generateSearchQueries)(query);
|
||||
// 验证数量是否被补全到了 3 个
|
||||
expect(result).toHaveLength(3);
|
||||
// 验证第一项是有效词本身
|
||||
expect(result[0]).toBe('量子计算');
|
||||
// 验证后两项是被 "${query} 相关研究" 占位补全的
|
||||
expect(result[1]).toBe('量子计算 相关研究');
|
||||
expect(result[2]).toBe('量子计算 相关研究');
|
||||
});
|
||||
it('边界测试:当有效检索词超过 5 个时,应当执行截取操作', () => {
|
||||
const query = '苹果 香蕉 橘子 葡萄 西瓜 芒果 樱桃';
|
||||
const result = (0, intentService_1.generateSearchQueries)(query);
|
||||
// 验证长度严格限制在 5
|
||||
expect(result).toHaveLength(5);
|
||||
// 验证截取的是前 5 个有效词
|
||||
expect(result).toEqual(['苹果', '香蕉', '橘子', '葡萄', '西瓜']);
|
||||
});
|
||||
it('边界测试:应当自动过滤掉长度小于 2 的无意义单字(如停用词)', () => {
|
||||
// "论"、"的"、"与" 长度均为 1,应该被抛弃
|
||||
const query = '论 AI 的 发展 与 IT 行业';
|
||||
const result = (0, intentService_1.generateSearchQueries)(query);
|
||||
// 验证单字被正确过滤
|
||||
expect(result).toEqual(['AI', '发展', 'IT', '行业']);
|
||||
});
|
||||
});
|
||||
//# sourceMappingURL=intentService.test.js.map
|
||||
1
projects/arabica/src/sprint2/dist/services/__test__/intentService.test.js.map
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"intentService.test.js","sourceRoot":"","sources":["../../../src/services/__test__/intentService.test.ts"],"names":[],"mappings":"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"}
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
"use strict";
|
||||
/**
|
||||
* Project Caffeine
|
||||
* 单元测试: promptService.ts
|
||||
*/
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
// 1. 全局 Mock fs/promises 模块
|
||||
jest.mock('fs/promises');
|
||||
// 2. 全局 Mock personas.json,提供测试专用的角色系统提示词
|
||||
jest.mock('../../models/personas/personas.json', () => [
|
||||
{ id: 'test_advisor', name: '测试顾问', systemPrompt: '这是一条测试专用的系统提示词' }
|
||||
], { virtual: true });
|
||||
describe('promptService', () => {
|
||||
// 使用 any 替代 typeof import,避免部分环境中未深度配置 ts-jest 导致的 Babel 解析报错
|
||||
let promptService;
|
||||
// 用于承载每次重置后的全新 fs mock 实例
|
||||
let fsMock;
|
||||
beforeEach(async () => {
|
||||
jest.clearAllMocks(); // 清理 mock 的调用统计
|
||||
jest.resetModules(); // 核心:重置模块注册表,清空 promptService 内部的 frameworksCache 状态
|
||||
// 【关键修复】在 resetModules 之后,必须重新 require mock 的 fs 模块!
|
||||
// 否则 promptService 内部引用的 fs 和这里外部的 fs 不是同一个实例,导致 mock 返回值失效并引发 TypeError。
|
||||
fsMock = require('fs/promises');
|
||||
promptService = await Promise.resolve().then(() => __importStar(require('../promptService')));
|
||||
});
|
||||
// 用于测试的伪造思维框架 JSON 数据
|
||||
const mockFrameworkJson = {
|
||||
name: "test_framework",
|
||||
description: "用于测试的伪造框架",
|
||||
parameters: [{ name: "topic", description: "测试主题", required: true }],
|
||||
template: "请分析这个主题:{{topic}}",
|
||||
persona: "test_advisor",
|
||||
examples: [
|
||||
{
|
||||
input: { topic: "人工智能" },
|
||||
output: "人工智能的分析结果"
|
||||
}
|
||||
]
|
||||
};
|
||||
describe('listFrameworks', () => {
|
||||
it('应当成功读取并返回框架的元数据(且必须剔除 template 等内部字段)', async () => {
|
||||
// Given: 使用重新获取的 fsMock 模拟文件系统的返回
|
||||
fsMock.readdir.mockResolvedValueOnce(['test_framework.json']);
|
||||
fsMock.readFile.mockResolvedValueOnce(JSON.stringify(mockFrameworkJson));
|
||||
// When: 调用服务
|
||||
const result = await promptService.listFrameworks();
|
||||
// Then: 断言文件系统调用次数及返回值格式
|
||||
expect(fsMock.readdir).toHaveBeenCalledTimes(1);
|
||||
expect(fsMock.readFile).toHaveBeenCalledTimes(1);
|
||||
expect(result).toHaveLength(1);
|
||||
// 验证返回的元数据对象是否符合预期
|
||||
expect(result[0]).toEqual({
|
||||
name: "test_framework",
|
||||
description: "用于测试的伪造框架",
|
||||
parameters: [{ name: "topic", description: "测试主题", required: true }]
|
||||
});
|
||||
// 断言安全红线:不应该将模板内容暴露在 list 接口中
|
||||
expect(result[0].template).toBeUndefined();
|
||||
});
|
||||
it('异常流:当框架目录读取失败时,应当安全捕获错误并返回空数组', async () => {
|
||||
// 【新增优化】拦截并静音 console.error,避免预期的错误日志污染终端测试面板
|
||||
const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
|
||||
// 模拟磁盘异常
|
||||
fsMock.readdir.mockRejectedValueOnce(new Error('目录不存在或无权限'));
|
||||
const result = await promptService.listFrameworks();
|
||||
// 验证业务逻辑:服务不会崩溃,而是返回容错的空数组
|
||||
expect(result).toEqual([]);
|
||||
// 验证系统确实捕获并打印了预期的错误
|
||||
expect(consoleSpy).toHaveBeenCalledWith('[PromptService] 加载框架失败:', expect.any(Error));
|
||||
// 测试完毕后恢复 console.error 的正常行为
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
describe('getFramework', () => {
|
||||
beforeEach(() => {
|
||||
// 为 getFramework 的测试统一挂载成功的 Mock 返回
|
||||
fsMock.readdir.mockResolvedValue(['test_framework.json']);
|
||||
fsMock.readFile.mockResolvedValue(JSON.stringify(mockFrameworkJson));
|
||||
});
|
||||
it('异常流:当请求不存在的框架名称时,应当抛出明确的错误', async () => {
|
||||
await expect(promptService.getFramework('unknown_framework', {}))
|
||||
.rejects.toThrow('框架 "unknown_framework" 不存在');
|
||||
});
|
||||
it('核心逻辑:应当正确解析角色,并组装 System、Few-Shot 及 Current User 消息序列', async () => {
|
||||
// When: 请求框架,并传入动态参数
|
||||
const result = await promptService.getFramework('test_framework', { topic: '云计算' });
|
||||
const messages = result.messages;
|
||||
// Then: 应当精确包含 4 条消息
|
||||
// (1x System, 1x Example User, 1x Example Assistant, 1x Current User)
|
||||
expect(messages).toHaveLength(4);
|
||||
// 1. 验证 System 提示词 (是否成功匹配并读取到了 Mock 的 persona 数据)
|
||||
expect(messages[0].role).toBe('system');
|
||||
expect(messages[0].content.text).toBe('这是一条测试专用的系统提示词');
|
||||
// 2. 验证 Few-Shot 示例中的 User 消息 (模板占位符是否被示例 input 正确替换)
|
||||
expect(messages[1].role).toBe('user');
|
||||
expect(messages[1].content.text).toBe('请分析这个主题:人工智能');
|
||||
// 3. 验证 Few-Shot 示例中的 Assistant 消息
|
||||
expect(messages[2].role).toBe('assistant');
|
||||
expect(messages[2].content.text).toBe('人工智能的分析结果');
|
||||
// 4. 验证当前用户请求 ({{topic}} 占位符是否被传入的 '云计算' 正确替换)
|
||||
expect(messages[3].role).toBe('user');
|
||||
expect(messages[3].content.text).toBe('请分析这个主题:云计算');
|
||||
});
|
||||
it('边界情况:如果多次调用 getFramework,验证内部框架缓存 (frameworksCache) 是否生效', async () => {
|
||||
// 第一次调用,会触发读盘
|
||||
await promptService.getFramework('test_framework', { topic: '测试1' });
|
||||
// 第二次调用,应该直接命中内部的 frameworksCache 缓存
|
||||
await promptService.getFramework('test_framework', { topic: '测试2' });
|
||||
// 断言:整个生命周期中,读盘操作应只有 1 次
|
||||
expect(fsMock.readdir).toHaveBeenCalledTimes(1);
|
||||
expect(fsMock.readFile).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
//# sourceMappingURL=promptService.test.js.map
|
||||
1
projects/arabica/src/sprint2/dist/services/__test__/promptService.test.js.map
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"promptService.test.js","sourceRoot":"","sources":["../../../src/services/__test__/promptService.test.ts"],"names":[],"mappings":"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"}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
"use strict";
|
||||
/**
|
||||
* Project Caffeine v0.1.1
|
||||
* Copyright (c) 2025-2026 Gitconomy Research
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Contributors:
|
||||
* - 郭晧 <guohao@gitconomy.org> (Initial Author)
|
||||
*/
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.listObsidianNotes = listObsidianNotes;
|
||||
exports.readObsidianNote = readObsidianNote;
|
||||
exports.saveNote = saveNote;
|
||||
const promises_1 = __importDefault(require("fs/promises"));
|
||||
const path_1 = __importDefault(require("path"));
|
||||
/**
|
||||
* 本地知识库的根目录路径。
|
||||
*
|
||||
* 该目录存放所有 Markdown 笔记文件,所有文件操作均限定在此目录内,
|
||||
* 以防止路径遍历攻击。
|
||||
*
|
||||
* @constant {string}
|
||||
*/
|
||||
const OBSIDIAN_VAULT_PATH = '/home/wguo/Downloads/MyVault'; // 【⚠️ 重要配置】请修改为你电脑上真实的 Markdown 笔记文件夹绝对路径!
|
||||
/**
|
||||
* 列出知识库中所有 Markdown 笔记的文件名。
|
||||
*
|
||||
* 该函数读取 OBSIDIAN_VAULT_PATH 目录下的所有文件,过滤出以 .md 结尾
|
||||
* (不区分大小写)的文件,并返回文件名列表。若目录不存在或无权限访问,
|
||||
* 则返回空数组并打印错误日志。
|
||||
*
|
||||
* @returns {Promise<string[]>} 包含所有笔记文件名的数组,若失败则返回空数组。
|
||||
*/
|
||||
async function listObsidianNotes() {
|
||||
try {
|
||||
const files = await promises_1.default.readdir(OBSIDIAN_VAULT_PATH);
|
||||
return files.filter(file => file.toLowerCase().endsWith('.md'));
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`[Project Caffeine] 无法读取知识库目录: ${error.message}`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 读取指定笔记文件的完整内容。
|
||||
*
|
||||
* 该函数首先对文件名进行安全校验,确保文件位于知识库目录内,
|
||||
* 防止路径遍历攻击。校验通过后,读取文件内容并返回。
|
||||
*
|
||||
* @param {string} filename - 要读取的笔记文件名(必须包含 .md 后缀)
|
||||
* @returns {Promise<string>} 笔记文件的文本内容
|
||||
* @throws {Error} 当文件名导致路径越界时抛出安全警告
|
||||
* @throws {Error} 当文件不存在或无权限读取时抛出错误
|
||||
*/
|
||||
async function readObsidianNote(filename) {
|
||||
const targetPath = path_1.default.resolve(OBSIDIAN_VAULT_PATH, filename);
|
||||
const safeVaultPath = path_1.default.resolve(OBSIDIAN_VAULT_PATH);
|
||||
// 核心防御:防止大模型通过传入 "../../" 读取系统敏感文件
|
||||
if (!targetPath.startsWith(safeVaultPath)) {
|
||||
throw new Error(`安全警告:越权访问拦截!禁止读取目录外的文件: ${filename}`);
|
||||
}
|
||||
try {
|
||||
const content = await promises_1.default.readFile(targetPath, 'utf-8');
|
||||
return content;
|
||||
}
|
||||
catch (error) {
|
||||
throw new Error(`无法读取笔记 [${filename}]: 文件可能不存在或无权限。`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 保存笔记到本地知识库。
|
||||
*
|
||||
* 该函数将内容写入指定文件,执行以下校验和操作:
|
||||
* 1. 验证文件名是否以 .md 结尾。
|
||||
* 2. 验证文件路径是否在知识库目录内,防止路径遍历攻击。
|
||||
* 3. 确保知识库目录存在(若不存在则自动创建)。
|
||||
* 4. 将内容写入文件。
|
||||
*
|
||||
* @param {string} filename - 笔记文件名(必须以 .md 结尾)
|
||||
* @param {string} content - 笔记内容(Markdown 格式)
|
||||
* @returns {Promise<string>} 保存成功的提示信息,包含文件绝对路径
|
||||
* @throws {Error} 当文件名不以 .md 结尾时抛出错误
|
||||
* @throws {Error} 当文件名导致路径越界时抛出错误
|
||||
* @throws {Error} 当目录创建失败或文件写入失败时抛出错误
|
||||
*/
|
||||
async function saveNote(filename, content) {
|
||||
// 1. 验证文件名是否以 .md 结尾
|
||||
if (!filename.endsWith('.md')) {
|
||||
throw new Error('文件名必须以 .md 结尾');
|
||||
}
|
||||
// 2. 防止路径遍历攻击:解析绝对路径,并检查是否在 NOTES_DIR 下
|
||||
const fullPath = path_1.default.resolve(OBSIDIAN_VAULT_PATH, filename);
|
||||
const relative = path_1.default.relative(OBSIDIAN_VAULT_PATH, fullPath);
|
||||
if (relative.startsWith('..') || path_1.default.isAbsolute(relative)) {
|
||||
throw new Error('无效的文件名,不允许访问上层目录');
|
||||
}
|
||||
// 3. 确保目标目录存在(可选,如果 NOTES_DIR 必须存在则可跳过)
|
||||
await promises_1.default.mkdir(OBSIDIAN_VAULT_PATH, { recursive: true });
|
||||
// 4. 写入文件
|
||||
await promises_1.default.writeFile(fullPath, content, 'utf-8');
|
||||
return `笔记已保存至: ${fullPath}`;
|
||||
}
|
||||
//# sourceMappingURL=resourceService.js.map
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"resourceService.js","sourceRoot":"","sources":["../../../src/services/__test__/resourceService.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;;;AA2BH,8CAQC;AAcD,4CAeC;AAmBD,4BAmBC;AApGD,2DAA6B;AAC7B,gDAAwB;AAExB;;;;;;;GAOG;AAGH,MAAM,mBAAmB,GAAG,8BAA8B,CAAC,CAAC,2CAA2C;AAEvG;;;;;;;;GAQG;AAEI,KAAK,UAAU,iBAAiB;IACnC,IAAI,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,kBAAE,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;QACpD,OAAO,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;IACpE,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QAClB,OAAO,CAAC,KAAK,CAAC,iCAAiC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QAChE,OAAO,EAAE,CAAC;IACd,CAAC;AACL,CAAC;AAED;;;;;;;;;;GAUG;AAEI,KAAK,UAAU,gBAAgB,CAAC,QAAgB;IACnD,MAAM,UAAU,GAAG,cAAI,CAAC,OAAO,CAAC,mBAAmB,EAAE,QAAQ,CAAC,CAAC;IAC/D,MAAM,aAAa,GAAG,cAAI,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAExD,mCAAmC;IACnC,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CAAC,2BAA2B,QAAQ,EAAE,CAAC,CAAC;IAC3D,CAAC;IAED,IAAI,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,kBAAE,CAAC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QACvD,OAAO,OAAO,CAAC;IACnB,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CAAC,WAAW,QAAQ,iBAAiB,CAAC,CAAC;IAC1D,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AAEI,KAAK,UAAU,QAAQ,CAAC,QAAgB,EAAE,OAAe;IAC9D,qBAAqB;IACrB,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,eAAe,CAAC,CAAC;IACnC,CAAC;IAED,wCAAwC;IACxC,MAAM,QAAQ,GAAG,cAAI,CAAC,OAAO,CAAC,mBAAmB,EAAE,QAAQ,CAAC,CAAC;IAC7D,MAAM,QAAQ,GAAG,cAAI,CAAC,QAAQ,CAAC,mBAAmB,EAAE,QAAQ,CAAC,CAAC;IAC9D,IAAI,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,cAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC3D,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC;IACtC,CAAC;IAED,wCAAwC;IACxC,MAAM,kBAAE,CAAC,KAAK,CAAC,mBAAmB,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEzD,UAAU;IACV,MAAM,kBAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IAC/C,OAAO,WAAW,QAAQ,EAAE,CAAC;AAC/B,CAAC"}
|
||||
113
projects/arabica/src/sprint2/dist/services/__test__/resourceService.test.js
vendored
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
/**
|
||||
* Project Caffeine
|
||||
* 单元测试: resourceService.ts
|
||||
*/
|
||||
const promises_1 = __importDefault(require("fs/promises"));
|
||||
const path_1 = __importDefault(require("path"));
|
||||
const resourceService_1 = require("../resourceService");
|
||||
// 全局 Mock fs/promises 模块,严禁真实磁盘 I/O
|
||||
jest.mock('fs/promises');
|
||||
describe('resourceService', () => {
|
||||
// 提取源码中硬编码的知识库路径,用于动态构建断言的预期路径
|
||||
const MOCK_VAULT_PATH = '/home/wguo/Downloads/MyVault';
|
||||
beforeEach(() => {
|
||||
// 确保每个用例运行前,清空 mock 的调用历史,保持独立性
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
// ==================================================================
|
||||
// 测试: listObsidianNotes
|
||||
// ==================================================================
|
||||
describe('listObsidianNotes', () => {
|
||||
it('正常流:应当正确读取目录,并只过滤出 .md 结尾的文件(忽略大小写)', async () => {
|
||||
// 模拟 fs.readdir 返回多种类型的文件
|
||||
const mockFiles = ['note1.md', 'note2.MD', 'image.png', 'folder', 'test.txt'];
|
||||
promises_1.default.readdir.mockResolvedValueOnce(mockFiles);
|
||||
const result = await (0, resourceService_1.listObsidianNotes)();
|
||||
expect(promises_1.default.readdir).toHaveBeenCalledWith(MOCK_VAULT_PATH);
|
||||
// 验证过滤逻辑:只保留 .md 和 .MD
|
||||
expect(result).toEqual(['note1.md', 'note2.MD']);
|
||||
expect(result).toHaveLength(2);
|
||||
});
|
||||
it('异常流:当目录读取失败(如不存在或无权限)时,应当安全捕获并返回空数组', async () => {
|
||||
// 拦截 console.error,避免预期的报错污染终端视图
|
||||
const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
|
||||
promises_1.default.readdir.mockRejectedValueOnce(new Error('Permission denied'));
|
||||
const result = await (0, resourceService_1.listObsidianNotes)();
|
||||
// 验证:服务没有崩溃,而是优雅降级返回空数组
|
||||
expect(result).toEqual([]);
|
||||
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('[Project Caffeine] 无法读取知识库目录:'));
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
// ==================================================================
|
||||
// 测试: readObsidianNote
|
||||
// ==================================================================
|
||||
describe('readObsidianNote', () => {
|
||||
it('安全红线测试:发生相对路径遍历攻击 (../) 时,应当拦截并抛出安全警告', async () => {
|
||||
const maliciousFilename = '../../etc/passwd';
|
||||
// 验证越权访问是否被拦截
|
||||
await expect((0, resourceService_1.readObsidianNote)(maliciousFilename)).rejects.toThrow(`安全警告:越权访问拦截!禁止读取目录外的文件: ${maliciousFilename}`);
|
||||
// 验证未发生任何实质性的文件读取
|
||||
expect(promises_1.default.readFile).not.toHaveBeenCalled();
|
||||
});
|
||||
it('正常流:当请求合法文件名时,应当正确读取并返回文件内容', async () => {
|
||||
const validFilename = 'test_note.md';
|
||||
const expectedContent = '# 这是一个测试笔记';
|
||||
const expectedPath = path_1.default.resolve(MOCK_VAULT_PATH, validFilename);
|
||||
promises_1.default.readFile.mockResolvedValueOnce(expectedContent);
|
||||
const result = await (0, resourceService_1.readObsidianNote)(validFilename);
|
||||
expect(promises_1.default.readFile).toHaveBeenCalledWith(expectedPath, 'utf-8');
|
||||
expect(result).toBe(expectedContent);
|
||||
});
|
||||
it('异常流:当合法文件不存在或读取失败时,应当抛出统一的业务错误', async () => {
|
||||
const validFilename = 'missing.md';
|
||||
promises_1.default.readFile.mockRejectedValueOnce(new Error('ENOENT'));
|
||||
await expect((0, resourceService_1.readObsidianNote)(validFilename)).rejects.toThrow(`无法读取笔记 [${validFilename}]: 文件可能不存在或无权限。`);
|
||||
});
|
||||
});
|
||||
// ==================================================================
|
||||
// 测试: saveNote
|
||||
// ==================================================================
|
||||
describe('saveNote', () => {
|
||||
it('边界测试:当文件名不以 .md 结尾时,应当拒绝保存', async () => {
|
||||
await expect((0, resourceService_1.saveNote)('test.txt', '内容')).rejects.toThrow('文件名必须以 .md 结尾');
|
||||
expect(promises_1.default.writeFile).not.toHaveBeenCalled();
|
||||
});
|
||||
it('安全红线测试:发生绝对路径越界写入时,应当拦截并抛出错误', async () => {
|
||||
await expect((0, resourceService_1.saveNote)('/root/hack.md', '内容')).rejects.toThrow('无效的文件名,不允许访问上层目录');
|
||||
expect(promises_1.default.writeFile).not.toHaveBeenCalled();
|
||||
});
|
||||
it('安全红线测试:发生相对路径遍历写入 (../) 时,应当拦截并抛出错误', async () => {
|
||||
await expect((0, resourceService_1.saveNote)('../outside.md', '内容')).rejects.toThrow('无效的文件名,不允许访问上层目录');
|
||||
expect(promises_1.default.writeFile).not.toHaveBeenCalled();
|
||||
});
|
||||
it('正常流:当输入合法时,应当先确保目录存在,然后成功写入文件并返回提示', async () => {
|
||||
const filename = 'new_insight.md';
|
||||
const content = '## 新的洞察发现';
|
||||
const expectedPath = path_1.default.resolve(MOCK_VAULT_PATH, filename);
|
||||
promises_1.default.mkdir.mockResolvedValueOnce(undefined);
|
||||
promises_1.default.writeFile.mockResolvedValueOnce(undefined);
|
||||
const result = await (0, resourceService_1.saveNote)(filename, content);
|
||||
// 1. 验证是否调用了创建目录 (递归模式)
|
||||
expect(promises_1.default.mkdir).toHaveBeenCalledWith(MOCK_VAULT_PATH, { recursive: true });
|
||||
// 2. 验证是否向正确路径写入了文件
|
||||
expect(promises_1.default.writeFile).toHaveBeenCalledWith(expectedPath, content, 'utf-8');
|
||||
// 3. 验证返回的成功确认消息
|
||||
expect(result).toBe(`笔记已保存至: ${expectedPath}`);
|
||||
});
|
||||
it('异常流:当底层磁盘写入发生崩溃时,应当将错误向上抛出', async () => {
|
||||
const filename = 'fail_test.md';
|
||||
const errorMsg = 'Disk Full';
|
||||
promises_1.default.mkdir.mockResolvedValueOnce(undefined);
|
||||
promises_1.default.writeFile.mockRejectedValueOnce(new Error(errorMsg));
|
||||
// 测试是否会将文件系统的原生错误冒泡(便于被 controller 层捕获)
|
||||
await expect((0, resourceService_1.saveNote)(filename, '内容')).rejects.toThrow(errorMsg);
|
||||
});
|
||||
});
|
||||
});
|
||||
//# sourceMappingURL=resourceService.test.js.map
|
||||
1
projects/arabica/src/sprint2/dist/services/__test__/resourceService.test.js.map
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"resourceService.test.js","sourceRoot":"","sources":["../../../src/services/__test__/resourceService.test.ts"],"names":[],"mappings":"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"}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.generateSearchQueries = generateSearchQueries;
|
||||
/**
|
||||
* 将用户的自然语言查询拆解为专业检索词列表
|
||||
* @param query 用户原始查询字符串
|
||||
* @returns 去重后的检索词数组(3~5 个)
|
||||
*/
|
||||
function generateSearchQueries(query) {
|
||||
if (!query || query.trim().length === 0) {
|
||||
return ['通用研究主题'];
|
||||
}
|
||||
// 1. 去除常见标点符号,替换为空格
|
||||
const cleaned = query.replace(/[,,。??、;;]/g, ' ');
|
||||
// 2. 按空白字符分割,过滤掉长度小于 2 的词(避免单字噪音)
|
||||
const words = cleaned.split(/\s+/).filter(word => word.length >= 2);
|
||||
// 3. 去重
|
||||
const uniqueWords = [...new Set(words)];
|
||||
// 4. 若不足 3 个,补充基于原查询的扩展词
|
||||
while (uniqueWords.length < 3) {
|
||||
uniqueWords.push(`${query} 相关研究`);
|
||||
}
|
||||
// 5. 截取前 5 个返回
|
||||
return uniqueWords.slice(0, 5);
|
||||
}
|
||||
//# sourceMappingURL=intentService.js.map
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"intentService.js","sourceRoot":"","sources":["../../src/services/intentService.ts"],"names":[],"mappings":";;AAKA,sDAqBC;AA1BD;;;;GAIG;AACH,SAAgB,qBAAqB,CAAC,KAAa;IACjD,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACpB,CAAC;IAED,oBAAoB;IACpB,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC;IAElD,kCAAkC;IAClC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;IAEpE,QAAQ;IACR,MAAM,WAAW,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;IAExC,yBAAyB;IACzB,OAAO,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9B,WAAW,CAAC,IAAI,CAAC,GAAG,KAAK,OAAO,CAAC,CAAC;IACpC,CAAC;IAED,eAAe;IACf,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AACjC,CAAC"}
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.listFrameworks = listFrameworks;
|
||||
exports.getFramework = getFramework;
|
||||
const promises_1 = __importDefault(require("fs/promises"));
|
||||
const path_1 = __importDefault(require("path"));
|
||||
const personas_json_1 = __importDefault(require("../models/personas/personas.json"));
|
||||
// ==========================================
|
||||
// 框架缓存与加载
|
||||
// ==========================================
|
||||
const FRAMEWORKS_DIR = path_1.default.join(__dirname, '../models/frameworks');
|
||||
let frameworksCache = null;
|
||||
/**
|
||||
* 从文件系统加载所有框架 JSON 文件
|
||||
*/
|
||||
async function loadFrameworks() {
|
||||
if (frameworksCache)
|
||||
return frameworksCache;
|
||||
try {
|
||||
const files = await promises_1.default.readdir(FRAMEWORKS_DIR);
|
||||
const jsonFiles = files.filter(f => f.endsWith('.json'));
|
||||
const frameworks = await Promise.all(jsonFiles.map(async (file) => {
|
||||
const content = await promises_1.default.readFile(path_1.default.join(FRAMEWORKS_DIR, file), 'utf-8');
|
||||
return JSON.parse(content);
|
||||
}));
|
||||
frameworksCache = frameworks;
|
||||
return frameworks;
|
||||
}
|
||||
catch (error) {
|
||||
console.error('[PromptService] 加载框架失败:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
// ==========================================
|
||||
// 公开 API
|
||||
// ==========================================
|
||||
/**
|
||||
* 列出所有可用框架(不含模板和系统提示词)
|
||||
*/
|
||||
async function listFrameworks() {
|
||||
const frameworks = await loadFrameworks();
|
||||
return frameworks.map(({ name, description, parameters }) => ({
|
||||
name,
|
||||
description,
|
||||
parameters
|
||||
}));
|
||||
}
|
||||
/**
|
||||
* 获取指定框架的完整提示词消息序列
|
||||
* @param name 框架名称
|
||||
* @param args 用户传入的参数(键值对)
|
||||
* @returns MCP Prompts 标准响应格式
|
||||
*/
|
||||
async function getFramework(name, args) {
|
||||
const frameworks = await loadFrameworks();
|
||||
const framework = frameworks.find(f => f.name === name);
|
||||
if (!framework) {
|
||||
throw new Error(`框架 "${name}" 不存在`);
|
||||
}
|
||||
// ==========================================
|
||||
// 确定系统提示词(优先使用角色矩阵)
|
||||
// ==========================================
|
||||
let systemPrompt = framework.systemPrompt || '';
|
||||
if (framework.persona) {
|
||||
const persona = personas_json_1.default.find(p => p.id === framework.persona);
|
||||
if (persona) {
|
||||
systemPrompt = persona.systemPrompt;
|
||||
}
|
||||
}
|
||||
// ==========================================
|
||||
// 构建消息数组
|
||||
// ==========================================
|
||||
const messages = [];
|
||||
// 1. 系统消息
|
||||
if (systemPrompt) {
|
||||
messages.push({
|
||||
role: 'system',
|
||||
content: { type: 'text', text: systemPrompt }
|
||||
});
|
||||
}
|
||||
// 2. Few-Shot 示例(如果存在)
|
||||
if (framework.examples && Array.isArray(framework.examples)) {
|
||||
for (const example of framework.examples) {
|
||||
// 构建示例用户输入:将 example.input 中的参数填充到模板中
|
||||
let exampleUserContent = framework.template;
|
||||
for (const [key, value] of Object.entries(example.input)) {
|
||||
exampleUserContent = exampleUserContent.replace(new RegExp(`{{${key}}}`, 'g'), value);
|
||||
}
|
||||
messages.push({
|
||||
role: 'user',
|
||||
content: { type: 'text', text: exampleUserContent }
|
||||
});
|
||||
// 添加示例助手输出
|
||||
messages.push({
|
||||
role: 'assistant',
|
||||
content: { type: 'text', text: example.output }
|
||||
});
|
||||
}
|
||||
}
|
||||
// 3. 当前用户请求
|
||||
let currentUserContent = framework.template;
|
||||
for (const [key, value] of Object.entries(args)) {
|
||||
currentUserContent = currentUserContent.replace(new RegExp(`{{${key}}}`, 'g'), value);
|
||||
}
|
||||
messages.push({
|
||||
role: 'user',
|
||||
content: { type: 'text', text: currentUserContent }
|
||||
});
|
||||
return { messages };
|
||||
}
|
||||
//# sourceMappingURL=promptService.js.map
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"promptService.js","sourceRoot":"","sources":["../../src/services/promptService.ts"],"names":[],"mappings":"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"}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
"use strict";
|
||||
/**
|
||||
* Project Caffeine
|
||||
* Copyright (c) 2025-2026 Gitconomy Research
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Contributors:
|
||||
* - 郭晧 <guohao@gitconomy.org> (Initial Author)
|
||||
*/
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.listObsidianNotes = listObsidianNotes;
|
||||
exports.readObsidianNote = readObsidianNote;
|
||||
exports.saveNote = saveNote;
|
||||
const promises_1 = __importDefault(require("fs/promises"));
|
||||
const path_1 = __importDefault(require("path"));
|
||||
// 【⚠️ 重要配置】请修改为你电脑上真实的 Markdown 笔记文件夹绝对路径!
|
||||
const OBSIDIAN_VAULT_PATH = '/home/wguo/Downloads/MyVault';
|
||||
async function listObsidianNotes() {
|
||||
try {
|
||||
const files = await promises_1.default.readdir(OBSIDIAN_VAULT_PATH);
|
||||
return files.filter(file => file.toLowerCase().endsWith('.md'));
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`[Project Caffeine] 无法读取知识库目录: ${error.message}`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
async function readObsidianNote(filename) {
|
||||
const targetPath = path_1.default.resolve(OBSIDIAN_VAULT_PATH, filename);
|
||||
const safeVaultPath = path_1.default.resolve(OBSIDIAN_VAULT_PATH);
|
||||
// 核心防御:防止大模型通过传入 "../../" 读取系统敏感文件
|
||||
if (!targetPath.startsWith(safeVaultPath)) {
|
||||
throw new Error(`安全警告:越权访问拦截!禁止读取目录外的文件: ${filename}`);
|
||||
}
|
||||
try {
|
||||
const content = await promises_1.default.readFile(targetPath, 'utf-8');
|
||||
return content;
|
||||
}
|
||||
catch (error) {
|
||||
throw new Error(`无法读取笔记 [${filename}]: 文件可能不存在或无权限。`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 保存笔记到本地知识库
|
||||
* @param filename 文件名(必须包含 .md 后缀)
|
||||
* @param content 笔记内容
|
||||
* @returns 保存结果信息
|
||||
*/
|
||||
async function saveNote(filename, content) {
|
||||
// 1. 验证文件名是否以 .md 结尾
|
||||
if (!filename.endsWith('.md')) {
|
||||
throw new Error('文件名必须以 .md 结尾');
|
||||
}
|
||||
// 2. 防止路径遍历攻击:解析绝对路径,并检查是否在 NOTES_DIR 下
|
||||
const fullPath = path_1.default.resolve(OBSIDIAN_VAULT_PATH, filename);
|
||||
const relative = path_1.default.relative(OBSIDIAN_VAULT_PATH, fullPath);
|
||||
if (relative.startsWith('..') || path_1.default.isAbsolute(relative)) {
|
||||
throw new Error('无效的文件名,不允许访问上层目录');
|
||||
}
|
||||
// 3. 确保目标目录存在(可选,如果 NOTES_DIR 必须存在则可跳过)
|
||||
await promises_1.default.mkdir(OBSIDIAN_VAULT_PATH, { recursive: true });
|
||||
// 4. 写入文件
|
||||
await promises_1.default.writeFile(fullPath, content, 'utf-8');
|
||||
return `笔记已保存至: ${fullPath}`;
|
||||
}
|
||||
//# sourceMappingURL=resourceService.js.map
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"resourceService.js","sourceRoot":"","sources":["../../src/services/resourceService.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;;;AASH,8CAQC;AAED,4CAeC;AAQD,4BAmBC;AA3DD,2DAA6B;AAC7B,gDAAwB;AAExB,2CAA2C;AAC3C,MAAM,mBAAmB,GAAG,8BAA8B,CAAC;AAGpD,KAAK,UAAU,iBAAiB;IACnC,IAAI,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,kBAAE,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;QACpD,OAAO,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;IACpE,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QAClB,OAAO,CAAC,KAAK,CAAC,iCAAiC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QAChE,OAAO,EAAE,CAAC;IACd,CAAC;AACL,CAAC;AAEM,KAAK,UAAU,gBAAgB,CAAC,QAAgB;IACnD,MAAM,UAAU,GAAG,cAAI,CAAC,OAAO,CAAC,mBAAmB,EAAE,QAAQ,CAAC,CAAC;IAC/D,MAAM,aAAa,GAAG,cAAI,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAExD,mCAAmC;IACnC,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CAAC,2BAA2B,QAAQ,EAAE,CAAC,CAAC;IAC3D,CAAC;IAED,IAAI,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,kBAAE,CAAC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QACvD,OAAO,OAAO,CAAC;IACnB,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CAAC,WAAW,QAAQ,iBAAiB,CAAC,CAAC;IAC1D,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACI,KAAK,UAAU,QAAQ,CAAC,QAAgB,EAAE,OAAe;IAC9D,qBAAqB;IACrB,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,eAAe,CAAC,CAAC;IACnC,CAAC;IAED,wCAAwC;IACxC,MAAM,QAAQ,GAAG,cAAI,CAAC,OAAO,CAAC,mBAAmB,EAAE,QAAQ,CAAC,CAAC;IAC7D,MAAM,QAAQ,GAAG,cAAI,CAAC,QAAQ,CAAC,mBAAmB,EAAE,QAAQ,CAAC,CAAC;IAC9D,IAAI,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,cAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC3D,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC;IACtC,CAAC;IAED,wCAAwC;IACxC,MAAM,kBAAE,CAAC,KAAK,CAAC,mBAAmB,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEzD,UAAU;IACV,MAAM,kBAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IAC/C,OAAO,WAAW,QAAQ,EAAE,CAAC;AAC/B,CAAC"}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
testPathIgnorePatterns: ['/node_modules/', '/dist/'] // 忽略 dist 目录!
|
||||
};
|
||||
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"name": "project-caffeine-sprint1",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"watch": "tsc --watch",
|
||||
"start": "node dist/app.js"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.27.1",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^5.0.6",
|
||||
"@types/node": "^25.3.3",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,431 @@
|
|||
/**
|
||||
* Project Caffeine v0.1.1
|
||||
* Copyright (c) 2025-2026 Gitconomy Research
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Contributors:
|
||||
* - 郭晧 <guohao@gitconomy.org> (Initial Author)
|
||||
*/
|
||||
|
||||
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
||||
import { z } from 'zod';
|
||||
import { handlePromptsGet } from './controllers/promptsController';
|
||||
import { handleToolCall } from './controllers/toolsController';
|
||||
import { listObsidianNotes, readObsidianNote } from './services/resourceService';
|
||||
|
||||
// ==========================================
|
||||
// 初始化 MCP Server
|
||||
// ==========================================
|
||||
const server = new McpServer({
|
||||
name: 'Project-Caffeine-Arabica-Edition',
|
||||
version: '0.0.2'
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// 辅助函数
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* 处理可选参数的默认值转换与补全。
|
||||
*
|
||||
* 该函数执行两个主要任务:
|
||||
* 1. 遍历传入的参数对象,将空值(undefined/null/"")转换为字符串 "无"。
|
||||
* 2. 根据提供的 expectedKeys 列表,补全缺失的可选参数键,值为 "无",确保模板替换不会失败。
|
||||
*
|
||||
* @param {Record<string, any>} args - 客户端传入的原始参数对象
|
||||
* @param {string[]} expectedKeys - 该框架预期的所有参数键名列表(用于补全缺失的键)
|
||||
* @returns {Record<string, string>} 补全且清洗后的参数对象,所有值均为非空字符串
|
||||
*/
|
||||
function sanitizeArgs(args: Record<string, any>, expectedKeys: string[] = []): Record<string, string> {
|
||||
const safeArgs: Record<string, string> = {};
|
||||
|
||||
// 处理已有的参数,确保值不为空
|
||||
for (const [key, value] of Object.entries(args)) {
|
||||
safeArgs[key] = value !== undefined && value !== null && value !== "" ? String(value) : "无";
|
||||
}
|
||||
|
||||
// 补全缺失的可选参数键,确保模板替换不会失败
|
||||
expectedKeys.forEach(key => {
|
||||
if (!(key in safeArgs)) {
|
||||
safeArgs[key] = "无";
|
||||
}
|
||||
});
|
||||
|
||||
return safeArgs;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 注册 Prompts 原语(多维思维框架模板)
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* 注册并处理 SCQA 框架的 prompts/get 请求。
|
||||
*
|
||||
* @param {object} args - 客户端传入的框架参数
|
||||
* @param {string} args.situation - 当前的客观背景或情境描述(必填)
|
||||
* @param {string} [args.context] - 补充的约束条件、行业背景或已有资源(可选)
|
||||
* @param {string} [args.objective] - 期望达成的最终业务目标(可选)
|
||||
* @returns {Promise<object>} 符合 MCP 协议规范的 prompt 响应对象,包含过滤和补全元数据后的 messages 序列
|
||||
*/
|
||||
server.prompt(
|
||||
'scqa',
|
||||
{
|
||||
situation: z.string().describe('当前的客观背景或情境描述'),
|
||||
context: z.string().optional().describe('补充的约束条件、行业背景或已有资源(可选)'),
|
||||
objective: z.string().optional().describe('期望达成的最终业务目标(可选)')
|
||||
},
|
||||
async (args) => {
|
||||
const result = await handlePromptsGet('scqa', sanitizeArgs(args, ['situation', 'context', 'objective']));
|
||||
return {
|
||||
...result,
|
||||
messages: Array.isArray(result.messages)
|
||||
? result.messages
|
||||
.filter((msg: any) => msg.role === "user" || msg.role === "assistant")
|
||||
.map((msg: any) => ({
|
||||
...msg,
|
||||
content: {
|
||||
...msg.content,
|
||||
annotations: msg.content.annotations ?? undefined,
|
||||
_meta: msg.content._meta ?? undefined
|
||||
}
|
||||
}))
|
||||
: []
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 注册并处理 5 Whys 框架的 prompts/get 请求。
|
||||
*
|
||||
* @param {object} args - 客户端传入的框架参数
|
||||
* @param {string} args.problem - 需要分析的核心问题、故障或不良现象(必填)
|
||||
* @param {string} [args.context] - 问题发生的背景信息、前置条件或受影响的范围(可选)
|
||||
* @param {string} [args.goal] - 期望通过解决此问题达成的最终目标(可选)
|
||||
* @returns {Promise<object>} 符合 MCP 协议规范的 prompt 响应对象
|
||||
*/
|
||||
server.prompt(
|
||||
'5whys',
|
||||
{
|
||||
problem: z.string().describe('需要分析的核心问题、故障或不良现象'),
|
||||
context: z.string().optional().describe('问题发生的背景信息、前置条件或受影响的范围(可选)'),
|
||||
goal: z.string().optional().describe('期望通过解决此问题达成的最终目标(可选)')
|
||||
},
|
||||
async (args) => {
|
||||
const result = await handlePromptsGet('5whys', sanitizeArgs(args, ['problem', 'context', 'goal']));
|
||||
return {
|
||||
...result,
|
||||
messages: Array.isArray(result.messages)
|
||||
? result.messages
|
||||
.filter((msg: any) => msg.role === "user" || msg.role === "assistant")
|
||||
.map((msg: any) => ({
|
||||
...msg,
|
||||
content: {
|
||||
...msg.content,
|
||||
annotations: msg.content.annotations ?? undefined,
|
||||
_meta: msg.content._meta ?? undefined
|
||||
}
|
||||
}))
|
||||
: []
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 注册并处理 5W3H 框架的 prompts/get 请求。
|
||||
*
|
||||
* @param {object} args - 客户端传入的框架参数
|
||||
* @param {string} args.topic - 需要分析的核心主题、事件或问题(必填)
|
||||
* @param {string} [args.context] - 该主题发生的特定背景、前置条件或行业环境(可选)
|
||||
* @param {string} [args.objective] - 期望通过此次分析达成的核心目标(可选)
|
||||
* @returns {Promise<object>} 符合 MCP 协议规范的 prompt 响应对象
|
||||
*/
|
||||
server.prompt(
|
||||
'5w3h',
|
||||
{
|
||||
topic: z.string().describe('需要分析的核心主题、事件或问题'),
|
||||
context: z.string().optional().describe('该主题发生的特定背景、前置条件或行业环境(可选)'),
|
||||
objective: z.string().optional().describe('期望通过此次分析达成的核心目标(可选)')
|
||||
},
|
||||
async (args) => {
|
||||
const result = await handlePromptsGet('5w3h', sanitizeArgs(args, ['topic', 'context', 'objective']));
|
||||
return {
|
||||
...result,
|
||||
messages: Array.isArray(result.messages)
|
||||
? result.messages
|
||||
.filter((msg: any) => msg.role === "user" || msg.role === "assistant")
|
||||
.map((msg: any) => ({
|
||||
...msg,
|
||||
content: {
|
||||
...msg.content,
|
||||
annotations: msg.content.annotations ?? undefined,
|
||||
_meta: msg.content._meta ?? undefined
|
||||
}
|
||||
}))
|
||||
: []
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 注册并处理 SWOT 框架的 prompts/get 请求。
|
||||
*
|
||||
* @param {object} args - 客户端传入的框架参数
|
||||
* @param {string} args.entity - 分析对象(如企业、产品、项目、个人等)(必填)
|
||||
* @param {string} [args.context] - 补充的行业背景、当前阶段或面临的核心挑战(可选)
|
||||
* @param {string} [args.competitors] - 主要竞争对手或对标对象(可选)
|
||||
* @returns {Promise<object>} 符合 MCP 协议规范的 prompt 响应对象
|
||||
*/
|
||||
server.prompt(
|
||||
'swot',
|
||||
{
|
||||
entity: z.string().describe('分析对象(如企业、产品、项目、个人等)'),
|
||||
context: z.string().optional().describe('补充的行业背景、当前阶段或面临的核心挑战(可选)'),
|
||||
competitors: z.string().optional().describe('主要竞争对手或对标对象(可选)')
|
||||
},
|
||||
async (args) => {
|
||||
const result = await handlePromptsGet('swot', sanitizeArgs(args, ['entity', 'context', 'competitors']));
|
||||
return {
|
||||
...result,
|
||||
messages: Array.isArray(result.messages)
|
||||
? result.messages
|
||||
.filter((msg: any) => msg.role === "user" || msg.role === "assistant")
|
||||
.map((msg: any) => ({
|
||||
...msg,
|
||||
content: {
|
||||
...msg.content,
|
||||
annotations: msg.content.annotations ?? undefined,
|
||||
_meta: msg.content._meta ?? undefined
|
||||
}
|
||||
}))
|
||||
: []
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 注册并处理 PESTLE 框架的 prompts/get 请求。
|
||||
*
|
||||
* @param {object} args - 客户端传入的框架参数
|
||||
* @param {string} args.domain - 需要分析的具体行业、市场或业务领域(必填)
|
||||
* @param {string} [args.region] - 目标地域范围(可选)
|
||||
* @param {string} [args.timeframe] - 分析的时间跨度(可选)
|
||||
* @returns {Promise<object>} 符合 MCP 协议规范的 prompt 响应对象
|
||||
*/
|
||||
server.prompt(
|
||||
'pestle',
|
||||
{
|
||||
domain: z.string().describe('需要分析的具体行业、市场或业务领域'),
|
||||
region: z.string().optional().describe('目标地域范围(可选)'),
|
||||
timeframe: z.string().optional().describe('分析的时间跨度(可选)')
|
||||
},
|
||||
async (args) => {
|
||||
const result = await handlePromptsGet('pestle', sanitizeArgs(args, ['domain', 'region', 'timeframe']));
|
||||
return {
|
||||
...result,
|
||||
messages: Array.isArray(result.messages)
|
||||
? result.messages
|
||||
.filter((msg: any) => msg.role === "user" || msg.role === "assistant")
|
||||
.map((msg: any) => ({
|
||||
...msg,
|
||||
content: {
|
||||
...msg.content,
|
||||
annotations: msg.content.annotations ?? undefined,
|
||||
_meta: msg.content._meta ?? undefined
|
||||
}
|
||||
}))
|
||||
: []
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ==========================================
|
||||
// 注册工具(Tools)
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* 注册意图拆解工具 generate_search_queries。
|
||||
* 接收用户的原始自然语言查询,调用意图拆解服务生成 3-5 个专业检索词。
|
||||
*
|
||||
* @param {object} args - 工具参数对象
|
||||
* @param {string} args.query - 用户的原始查询语句
|
||||
* @returns {Promise<object>} 符合 MCP 协议的工具响应对象,包含 type 为 "text" 的内容格式化结果
|
||||
*/
|
||||
server.tool(
|
||||
'generate_search_queries',
|
||||
{ query: z.string().describe('用户的原始查询语句') },
|
||||
async (args) => {
|
||||
const result = await handleToolCall('generate_search_queries', args);
|
||||
return {
|
||||
...result,
|
||||
content: result.content.map((item: any) => ({
|
||||
...item,
|
||||
type: "text"
|
||||
}))
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 注册获取本地笔记列表工具 list_local_notes。
|
||||
* 允许大模型获取当前知识库目录下所有存在的 Markdown 笔记文件名。
|
||||
*
|
||||
* @param {object} args - 无需额外参数(保留以符合 MCP 签名)
|
||||
* @returns {Promise<object>} 包含笔记文件名列表的文本响应对象
|
||||
*/
|
||||
server.tool(
|
||||
'list_local_notes',
|
||||
{},
|
||||
async (args) => {
|
||||
const result = await handleToolCall('list_local_notes', args);
|
||||
return {
|
||||
...result,
|
||||
content: result.content.map((item: any) => ({
|
||||
type: 'text',
|
||||
text: item.text
|
||||
}))
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 注册读取单一本地笔记工具 read_local_note。
|
||||
* 允许大模型根据指定文件名获取该 Markdown 文件的具体文本内容。
|
||||
*
|
||||
* @param {object} args - 工具参数对象
|
||||
* @param {string} args.filename - 需要读取的笔记文件名,必须包含 .md 后缀
|
||||
* @returns {Promise<object>} 包含目标文件全量文本内容的响应对象
|
||||
*/
|
||||
server.tool(
|
||||
'read_local_note',
|
||||
{ filename: z.string().describe('需要读取的笔记文件名,必须包含 .md 后缀') },
|
||||
async (args) => {
|
||||
const result = await handleToolCall('read_local_note', args);
|
||||
return {
|
||||
...result,
|
||||
content: result.content.map((item: any) => ({
|
||||
type: 'text',
|
||||
text: item.text
|
||||
}))
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 注册保存本地笔记工具 save_note。
|
||||
* 允许大模型将分析结果或摘要撰写并保存为本地知识库中的 Markdown 文件。
|
||||
*
|
||||
* @param {object} args - 工具参数对象
|
||||
* @param {string} args.filename - 笔记文件名,必须以 .md 结尾
|
||||
* @param {string} args.content - 需要持久化写入的笔记内容(Markdown 格式)
|
||||
* @returns {Promise<object>} 保存成功的状态回执或报错信息的响应对象
|
||||
*/
|
||||
server.tool(
|
||||
'save_note',
|
||||
{
|
||||
filename: z.string().describe('笔记文件名,必须以 .md 结尾'),
|
||||
content: z.string().describe('笔记内容(Markdown 格式)')
|
||||
},
|
||||
async (args) => {
|
||||
const result = await handleToolCall('save_note', args);
|
||||
return {
|
||||
...result,
|
||||
content: result.content.map((item: any) => ({
|
||||
type: 'text',
|
||||
text: item.text
|
||||
}))
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ==========================================
|
||||
// 注册资源(Resources)
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* 注册 local-notes 资源模板原语。
|
||||
* 提供给支持 Resources 的 MCP 客户端,使用户可以直接在界面上勾选并注入本地知识库笔记。
|
||||
*
|
||||
* - list 钩子:返回所有有效的 note://local/{filename} 资源列表。
|
||||
* - 读取钩子:根据解析出的 filename 读取本地文件内容,组装为符合 MCP 规范的响应。
|
||||
*
|
||||
* @type {import('@modelcontextprotocol/sdk/server/mcp.js').ResourceTemplate}
|
||||
*/
|
||||
server.resource(
|
||||
"local-notes",
|
||||
new ResourceTemplate("note://local/{filename}", {
|
||||
/**
|
||||
* 列出所有可用的笔记资源。
|
||||
*
|
||||
* @returns {Promise<{ resources: Array<{ name: string; uri: string; mimeType: string; description: string }> }>}
|
||||
* 资源列表,每个资源包含名称、URI、MIME 类型和描述
|
||||
*/
|
||||
list: async () => {
|
||||
try {
|
||||
const notes = await listObsidianNotes();
|
||||
return {
|
||||
resources: notes.map(filename => ({
|
||||
name: filename,
|
||||
uri: `note://local/${encodeURIComponent(filename)}`,
|
||||
mimeType: "text/markdown",
|
||||
description: `本地笔记: ${filename}`
|
||||
}))
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.error('[Resources] 列出资源失败:', error);
|
||||
return { resources: [] };
|
||||
}
|
||||
}
|
||||
}),
|
||||
/**
|
||||
* 处理资源读取请求:根据 URI 中的 filename 参数读取对应笔记内容。
|
||||
*
|
||||
* @param {URL} uri - 请求的完整 URI 对象
|
||||
* @param {{ filename: string | string[] }} params - 从 URI 中提取的参数,包含 filename
|
||||
* @returns {Promise<{ contents: Array<{ uri: string; mimeType: string; text: string }> }>}
|
||||
* 符合 MCP 规范的内容响应对象
|
||||
* @throws {Error} 当读取失败时抛出错误,错误信息将被 MCP 客户端捕获
|
||||
*/
|
||||
async (uri, { filename }) => {
|
||||
try {
|
||||
const filenameStr = Array.isArray(filename) ? filename[0] : filename;
|
||||
const decodedFilename = decodeURIComponent(filenameStr);
|
||||
const content = await readObsidianNote(decodedFilename);
|
||||
|
||||
return {
|
||||
contents: [{
|
||||
uri: uri.href,
|
||||
mimeType: "text/markdown",
|
||||
text: content
|
||||
}]
|
||||
};
|
||||
} catch (error: any) {
|
||||
throw new Error(`读取笔记失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// ==========================================
|
||||
// 启动服务器
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* 启动 MCP 服务器的主入口函数。
|
||||
* 建立与客户端(如 Cherry Studio)的标准输入/输出 (STDIO) 传输通信通道,
|
||||
* 将服务器配置挂载至进程,并就绪等待各类协议请求。
|
||||
*
|
||||
* @returns {Promise<void>} 异步的启动过程,无返回值
|
||||
* @throws {Error} 如果连接失败,错误会被捕获并记录,进程退出
|
||||
*/
|
||||
async function start() {
|
||||
console.error('[S2] 正在启动 MCP Server (Prompts + Tools + Resources)...');
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
console.error('[S2] MCP Server 已就绪,等待 Cherry Studio 连接');
|
||||
}
|
||||
|
||||
start().catch((err) => {
|
||||
console.error('[S2] 服务器启动失败:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
/**
|
||||
* Project Caffeine v0.1.1
|
||||
* Copyright (c) 2025-2026 Gitconomy Research
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Contributors:
|
||||
* - 郭晧 <guohao@gitconomy.org> (Initial Author)
|
||||
*/
|
||||
import { listFrameworks, getFramework } from '../services/promptService';
|
||||
|
||||
/**
|
||||
* 处理 prompts/list 请求,返回所有可用思维框架的元信息列表。
|
||||
*
|
||||
* 该函数从服务层获取所有框架的概要信息(名称、描述、参数定义),
|
||||
* 并将其转换为 MCP prompts/list 响应所要求的格式。
|
||||
*
|
||||
* @returns {Promise<{ prompts: Array<{ name: string, description: string, arguments: Array<{ name: string, description: string, required: boolean }> }> }>}
|
||||
* 符合 MCP 规范的 prompts 列表,每个 prompt 包含名称、描述及参数列表。
|
||||
*
|
||||
* @throws 不会直接抛出异常,服务层错误已在 listFrameworks 内部处理并返回空数组。
|
||||
*/
|
||||
|
||||
export async function handlePromptsList() {
|
||||
const frameworks = await listFrameworks();
|
||||
return {
|
||||
prompts: frameworks.map(f => ({
|
||||
name: f.name,
|
||||
description: f.description,
|
||||
arguments: f.parameters.map(p => ({
|
||||
name: p.name,
|
||||
description: p.description,
|
||||
required: p.required
|
||||
}))
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 处理 prompts/get 请求,获取指定思维框架的完整提示词消息序列。
|
||||
*
|
||||
* 该函数根据框架名称和用户传入的参数,调用服务层组装包含系统提示、
|
||||
* few-shot 示例和当前用户请求的 messages 数组,并返回符合 MCP 规范的响应。
|
||||
*
|
||||
* @param {string} name - 框架名称(如 "scqa", "swot" 等)
|
||||
* @param {Record<string, string>} args - 用户传入的参数键值对,用于填充模板中的变量
|
||||
*
|
||||
* @returns {Promise<{ description: string, messages: Array<{ role: string, content: { type: string, text: string } }> }>}
|
||||
* 包含描述信息和消息序列的响应对象,可直接用于 MCP prompts/get 响应。
|
||||
*
|
||||
* @throws {Error} 当框架不存在或服务层组装失败时,抛出错误(将被上层捕获并返回给客户端)。
|
||||
*/
|
||||
|
||||
export async function handlePromptsGet(name: string, args: Record<string, string>) {
|
||||
try {
|
||||
const result = await getFramework(name, args || {});
|
||||
return {
|
||||
description: `框架: ${name}`,
|
||||
messages: result.messages
|
||||
};
|
||||
} catch (error: any) {
|
||||
throw new Error(`获取框架失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
/**
|
||||
* Project Caffeine v0.1.1
|
||||
* Copyright (c) 2025-2026 Gitconomy Research
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Contributors:
|
||||
* - 郭晧 <guohao@gitconomy.org> (Initial Author)
|
||||
*/
|
||||
import { generateSearchQueries } from '../services/intentService';
|
||||
import { listObsidianNotes, readObsidianNote, saveNote } from '../services/resourceService';
|
||||
import { z } from 'zod';
|
||||
import { generateSearchQueriesSchema, saveNoteSchema } from '../models/schemas';
|
||||
|
||||
/**
|
||||
* 统一工具调用处理入口。
|
||||
*
|
||||
* 根据工具名称分发到对应的具体处理函数,并对未知工具返回错误响应。
|
||||
*
|
||||
* @param toolName - 工具名称,支持 'generate_search_queries'、'list_local_notes'、'read_local_note'、'save_note'
|
||||
* @param params - 工具参数对象,具体结构取决于工具
|
||||
* @returns MCP 工具响应格式,包含 content 数组,可能带有 isError 标记
|
||||
*/
|
||||
|
||||
export async function handleToolCall(toolName: string, params: any) {
|
||||
switch (toolName) {
|
||||
case 'generate_search_queries':
|
||||
return handleGenerateSearchQueries(params);
|
||||
case 'list_local_notes':
|
||||
return handleListLocalNotes();
|
||||
case 'read_local_note':
|
||||
return handleReadLocalNote(params);
|
||||
case 'save_note':
|
||||
return handleSaveNote(params);
|
||||
default:
|
||||
return {
|
||||
content: [{ type: 'text', text: `未知工具: ${toolName}` }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理检索词生成工具 (generate_search_queries)。
|
||||
*
|
||||
* 该函数接收用户原始查询语句,通过 intentService 生成 3~5 个专业检索词,
|
||||
* 并以 JSON 字符串形式返回。
|
||||
*
|
||||
* @param params - 工具参数对象,应包含 query 字段
|
||||
* @param params.query - 用户的原始查询语句
|
||||
* @returns MCP 工具响应,成功时 content 包含 JSON 格式的检索词数组;失败时 content 包含错误信息且 isError 为 true
|
||||
*/
|
||||
|
||||
async function handleGenerateSearchQueries(params: any) {
|
||||
// 使用集中管理的 schema 进行参数校验
|
||||
const parseResult = generateSearchQueriesSchema.safeParse(params);
|
||||
if (!parseResult.success) {
|
||||
return {
|
||||
content: [{ type: 'text', text: `参数错误: ${parseResult.error.message}` }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
|
||||
const { query } = parseResult.data;
|
||||
|
||||
try {
|
||||
const queries = generateSearchQueries(query);
|
||||
return {
|
||||
content: [{ type: 'text', text: JSON.stringify(queries, null, 2) }]
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.error('[ToolsController] generate_search_queries 失败:', error);
|
||||
return {
|
||||
content: [{ type: 'text', text: `执行失败: ${error.message}` }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理列出本地笔记工具 (list_local_notes)。
|
||||
*
|
||||
* 调用 resourceService 获取知识库中所有 Markdown 笔记的文件名,
|
||||
* 并以文本列表形式返回。
|
||||
*
|
||||
* @returns MCP 工具响应,成功时 content 包含笔记列表文本;失败时 content 包含错误信息且 isError 为 true
|
||||
*/
|
||||
|
||||
async function handleListLocalNotes() {
|
||||
try {
|
||||
const notes = await listObsidianNotes();
|
||||
return {
|
||||
content: [{
|
||||
type: 'text',
|
||||
text: notes.length > 0 ? `找到了以下笔记:\n${notes.join('\n')}` : '未找到笔记。'
|
||||
}]
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.error('[ToolsController] list_local_notes 失败:', error);
|
||||
return {
|
||||
content: [{ type: 'text', text: `执行失败: ${error.message}` }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理读取本地笔记工具 (read_local_note)。
|
||||
*
|
||||
* 接收文件名参数,调用 resourceService 读取对应笔记内容并返回。
|
||||
*
|
||||
* @param params - 工具参数对象,应包含 filename 字段
|
||||
* @param params.filename - 要读取的笔记文件名(必须包含 .md 后缀)
|
||||
* @returns MCP 工具响应,成功时 content 包含笔记内容;失败时 content 包含错误信息且 isError 为 true
|
||||
*/
|
||||
|
||||
async function handleReadLocalNote(params: any) {
|
||||
const schema = z.object({
|
||||
filename: z.string().min(1, '文件名不能为空').includes('.md', { message: '文件名必须包含 .md 后缀' })
|
||||
});
|
||||
|
||||
const parseResult = schema.safeParse(params);
|
||||
if (!parseResult.success) {
|
||||
return {
|
||||
content: [{ type: 'text', text: `参数错误: ${parseResult.error.message}` }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
|
||||
const { filename } = parseResult.data;
|
||||
|
||||
try {
|
||||
const content = await readObsidianNote(filename);
|
||||
return {
|
||||
content: [{ type: 'text', text: content }]
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.error('[ToolsController] read_local_note 失败:', error);
|
||||
return {
|
||||
content: [{ type: 'text', text: `读取失败: ${error.message}` }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理保存笔记工具 (save_note)。
|
||||
*
|
||||
* 接收文件名和内容参数,调用 resourceService 将笔记保存到本地知识库。
|
||||
*
|
||||
* @param params - 工具参数对象,应包含 filename 和 content 字段
|
||||
* @param params.filename - 笔记文件名(必须以 .md 结尾)
|
||||
* @param params.content - 笔记内容(Markdown 格式)
|
||||
* @returns MCP 工具响应,成功时 content 包含保存成功信息;失败时 content 包含错误信息且 isError 为 true
|
||||
*/
|
||||
|
||||
async function handleSaveNote(params: any) {
|
||||
const parseResult = saveNoteSchema.safeParse(params);
|
||||
if (!parseResult.success) {
|
||||
return {
|
||||
content: [{ type: 'text', text: `参数错误: ${parseResult.error.message}` }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
|
||||
const { filename, content } = parseResult.data;
|
||||
|
||||
try {
|
||||
const message = await saveNote(filename, content);
|
||||
return {
|
||||
content: [{ type: 'text', text: message }]
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.error('[ToolsController] save_note 失败:', error);
|
||||
return {
|
||||
content: [{ type: 'text', text: `保存失败: ${error.message}` }],
|
||||
isError: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"name": "5w3h",
|
||||
"description": "5W3H 分析法:从 What、Why、Who、When、Where、How、How much、How feel 八个维度全面拆解问题。",
|
||||
"persona": "structured_thinker",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "topic",
|
||||
"description": "需要分析的主题",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"template": "请使用 5W3H 框架分析以下主题:{{topic}}\n\n请依次从以下八个维度展开:\n1. What(是什么):描述问题的本质、现象或定义。\n2. Why(为什么):分析问题产生的原因、背景或动机。\n3. Who(谁):识别涉及的主体、利益相关者或角色。\n4. When(何时):明确问题发生的时间节点、阶段或周期。\n5. Where(何地):界定问题发生的空间范围、地点或场景。\n6. How(如何):探讨解决问题的途径、方法或过程。\n7. How much(多少):量化问题的规模、成本、数量或程度。\n8. How feel(感受如何):描述相关方的感受、体验或情绪影响。\n\n输出格式要求:使用 Markdown 标题分节(## 维度名称),每个维度至少 100 字。",
|
||||
"systemPrompt": "你是一位擅长运用 5W3H 框架进行结构化思考的分析师,能够全面覆盖问题的各个维度,确保分析的系统性和深度。",
|
||||
"examples": [
|
||||
{
|
||||
"input": {
|
||||
"topic": "公司推行远程办公政策"
|
||||
},
|
||||
"output": "## What(是什么)\n远程办公政策是指允许员工定期或永久性地在公司办公场所以外的地点(通常是家中)完成工作任务的正式制度。该政策包括硬件支持、沟通规范、绩效评估等方面的规定。\n\n## Why(为什么)\n推行此政策的背景是疫情后混合办公模式的普及,以及为提升员工满意度和保留人才。同时,公司希望通过减少通勤时间提高工作效率,并降低办公室运营成本。\n\n## Who(谁)\n涉及主体包括:员工(需适应新工作模式)、管理者(需调整管理方式)、IT部门(提供技术支持)、HR部门(制定政策与培训)、以及外部客户(沟通方式可能变化)。\n\n## When(何时)\n政策计划于 2024 年 Q3 开始试点,Q4 全面推广。初期将分为三个阶段:准备期(政策制定与工具部署)、过渡期(混合办公试行)、稳定期(效果评估与调整)。\n\n## Where(何地)\n主要适用于总部及各地分公司,但不包括必须现场操作的岗位(如生产线、客服中心)。员工需确保家中具备稳定的网络和工作环境。\n\n## How(如何)\n实施路径:1)发布正式政策文档;2)提供远程办公工具包(笔记本、VPN、协作软件);3)开展管理者培训;4)建立定期线上会议和反馈机制;5)通过 OKR 和项目成果考核绩效,而非工时。\n\n## How much(多少)\n预计覆盖 60% 的岗位,涉及 800 名员工。初期硬件投入约 200 万元,长期每年可节省办公室租金和水电费约 150 万元。员工平均通勤时间减少 1.5 小时/天。\n\n## How feel(感受如何)\n员工普遍期待,但部分管理者担忧团队凝聚力下降。IT部门反映支持压力增大。客户可能需要适应更灵活的联系时间。整体情绪偏向积极,但需关注过渡期的适应问题。"
|
||||
},
|
||||
{
|
||||
"input": {
|
||||
"topic": "城市共享单车乱停乱放问题"
|
||||
},
|
||||
"output": "## What(是什么)\n共享单车乱停乱放是指用户在使用结束后未将车辆停放在指定区域,导致占用盲道、消防通道、绿化带等公共空间,影响市容和交通秩序的现象。\n\n## Why(为什么)\n主要原因:部分用户图方便不愿多走路;运营企业投放过量,超出停车设施承载能力;电子围栏技术不完善,无法有效约束;政府监管和处罚力度不足。\n\n## Who(谁)\n涉及主体:用户(乱停乱放行为主体)、共享单车企业(投放和管理责任方)、城市管理部门(监管者)、普通市民(受影响者)、社区街道(属地管理)。\n\n## When(何时)\n问题全天候存在,早晚高峰后尤为严重,周末和节假日乱停放量比工作日增加约 30%。\n\n## Where(何地)\n集中于地铁站、商圈、写字楼、大学周边等人流密集区域,尤其缺乏足够停车位的路段。\n\n## How(如何)\n解决方案:1)优化电子围栏技术,提高定位精度,强制用户入栏结算;2)设置更多合规停车点,并引入立体停车架;3)建立企业信用考核机制,根据停放秩序动态分配配额;4)开展文明骑行宣传,对违规用户实施联合信用惩戒。\n\n## How much(多少)\n据调查,某一线城市日均清理乱停放单车约 2 万辆,投入管理人员 500 人,年财政支出超 2000 万元。乱停放导致 30% 的投诉涉及出行障碍。\n\n## How feel(感受如何)\n市民普遍反感,认为影响出行和安全;企业抱怨监管标准不一,运维成本高;政府部门感到管理难度大,缺乏长效机制。"
|
||||
},
|
||||
{
|
||||
"input": {
|
||||
"topic": "某餐饮品牌推出植物肉新品的市场反响"
|
||||
},
|
||||
"output": "## What(是什么)\n该餐饮品牌推出的植物肉新品是以大豆蛋白、豌豆蛋白等为原料,模拟真肉口感与营养的汉堡、饺子等产品,旨在吸引健康饮食和环保意识消费者。\n\n## Why(为什么)\n品牌希望通过创新产品差异化竞争,顺应全球植物基饮食趋势,同时减少碳足迹,提升企业社会责任形象。\n\n## Who(谁)\n目标消费者:年轻白领、健身人群、素食主义者、环保主义者;内部涉及研发、采购、市场、门店运营团队;外部合作方为植物肉供应商 Beyond Meat。\n\n## When(何时)\n新品于 2024 年 3 月上市,首月为推广期,配合广告和试吃活动;目前处于市场反馈收集阶段。\n\n## Where(何地)\n首先在一线城市 50 家核心门店推出,线上外卖平台同步上线,计划根据反响逐步推广至二线城市。\n\n## How(如何)\n上市策略:1)邀请美食 KOL 探店打卡;2)推出“植物肉尝鲜套餐”折扣;3)在门店设置科普展板,解释植物肉环保价值;4)收集用户口味评价,快速迭代产品配方。\n\n## How much(多少)\n首月销量约 5 万份,占当月总销售额的 8%,客单价较常规产品高 15%。营销投入约 200 万元,线上曝光量达 1000 万次。\n\n## How feel(感受如何)\n尝试者中 60% 表示口感接近真肉,愿意复购;30% 认为价格偏高;10% 表示不喜欢。环保组织给予正面评价,但部分消费者对“过度加工”表示担忧。内部团队对初期销量基本满意,但认为需加强消费者教育。"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"name": "5whys",
|
||||
"description": "5 Whys 分析法:通过连续追问五次“为什么”来探究问题的根本原因。",
|
||||
"persona": "root_cause_analyst",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "problem",
|
||||
"description": "需要分析的问题或现象",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"template": "请使用 5 Whys 分析法探究以下问题的根本原因:{{problem}}\n\n请按照以下步骤:\n1. 第一问:为什么会出现 {{problem}}?\n2. 基于上一回答,追问为什么?\n3. 继续追问,直到找到根本原因(通常连续五问)。\n\n输出格式要求:以 Markdown 列表呈现五个追问及其回答,每个追问前标明“为什么?”并附上简要分析。",
|
||||
"systemPrompt": "你是一位擅长根因分析的问题解决专家,能够通过系统性追问深入挖掘问题的根本原因。",
|
||||
"examples": [
|
||||
{
|
||||
"input": {
|
||||
"problem": "某互联网公司近期用户留存率下降"
|
||||
},
|
||||
"output": "- 为什么?近期用户留存率下降。\n - 因为新用户注册后一周内活跃度明显降低。\n- 为什么?新用户注册后一周内活跃度降低。\n - 因为新用户引导流程复杂,很多用户在第三步就放弃了。\n- 为什么?新用户引导流程复杂。\n - 因为引导步骤多达五步,且需要填写过多个人信息。\n- 为什么?引导步骤设计为五步。\n - 因为产品团队希望收集更全面的用户画像,但未考虑用户体验。\n- 为什么?产品团队未考虑用户体验。\n - 因为缺乏用户测试,设计决策基于内部假设,未从新用户角度验证。\n\n**根本原因**:产品团队在设计新用户引导流程时,以数据收集为目标而非用户体验,且未进行可用性测试,导致流程复杂,新用户流失。"
|
||||
},
|
||||
{
|
||||
"input": {
|
||||
"problem": "某制造工厂设备故障率上升"
|
||||
},
|
||||
"output": "- 为什么?设备故障率上升。\n - 因为关键部件磨损速度加快。\n- 为什么?关键部件磨损加快。\n - 因为润滑不足,导致摩擦增大。\n- 为什么?润滑不足。\n - 因为自动润滑系统未按计划加注润滑油。\n- 为什么?润滑系统未按计划工作。\n - 因为维护人员忽略了润滑系统的检查,误以为它完全自动运行。\n- 为什么?维护人员忽略检查。\n - 因为润滑系统的维护责任未明确写入 SOP,且缺乏定期巡检记录。\n\n**根本原因**:维护流程存在漏洞,润滑系统未被纳入定期检查清单,导致责任缺失。"
|
||||
},
|
||||
{
|
||||
"input": {
|
||||
"problem": "某高校图书馆自习室占座现象严重"
|
||||
},
|
||||
"output": "- 为什么?自习室占座现象严重。\n - 因为很多学生用物品长时间占用座位但人不在。\n- 为什么?学生能长时间占座而不被制止。\n - 因为没有有效的座位管理机制,管理员巡查频率低。\n- 为什么?管理员巡查频率低。\n - 因为管理员人手不足,且缺乏专门针对占座的管理制度。\n- 为什么?图书馆未制定占座管理制度。\n - 因为管理层认为占座是学生自觉问题,未意识到严重性。\n- 为什么?管理层未意识到严重性。\n - 因为缺乏学生反馈渠道,图书馆未定期收集自习室使用问题。\n\n**根本原因**:图书馆管理层忽视学生需求,未建立问题反馈机制和占座管理制度,导致管理缺位。"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"name": "pestle",
|
||||
"description": "PESTLE 宏观环境分析:从政治 (Political)、经济 (Economic)、社会 (Social)、技术 (Technological)、法律 (Legal)、环境 (Environmental) 六个维度分析宏观环境。",
|
||||
"persona": "macro_environment_analyst",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "domain",
|
||||
"description": "行业或领域",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"template": "请使用 PESTLE 框架分析以下行业/领域的宏观环境:{{domain}}\n\n请依次从以下六个维度展开:\n1. 政治因素 (Political):政府政策、政治稳定性、法规导向等。\n2. 经济因素 (Economic):经济增长、通货膨胀、利率、就业等。\n3. 社会因素 (Social):人口结构、文化趋势、生活方式变化等。\n4. 技术因素 (Technological):技术创新、研发投入、技术迭代等。\n5. 法律因素 (Legal):法律法规、行业标准、知识产权保护等。\n6. 环境因素 (Environmental):环保政策、气候变化、可持续发展等。\n\n输出格式要求:使用 Markdown 标题分节,每个维度至少 150 字,总字数不低于 900 字。",
|
||||
"systemPrompt": "你是一位宏观环境分析专家,擅长运用 PESTLE 框架评估行业外部环境,能够结合具体数据和发展趋势进行深入洞察。",
|
||||
"examples": [
|
||||
{
|
||||
"input": {
|
||||
"domain": "中国人工智能芯片行业"
|
||||
},
|
||||
"output": "## 1. 政治因素 (Political)\n中国政府将人工智能芯片列为国家战略新兴产业,通过“十四五”规划、集成电路产业投资基金(大基金)等提供资金支持。同时,美国对华芯片出口管制持续收紧,倒逼国内加速自主可控。政府通过“东数西算”工程布局算力基础设施,为国产芯片创造应用场景。\n\n## 2. 经济因素 (Economic)\n2023 年中国半导体市场规模超 1.5 万亿元,其中 AI 芯片占比约 15%,年增速超过 30%。受宏观经济影响,下游互联网、金融等行业资本开支可能收缩,但 AI 大模型带来的算力需求依然旺盛。国产替代趋势下,国内芯片设计企业获得更多流片和量产订单,但先进制程代工成本高昂。\n\n## 3. 社会因素 (Social)\n数字化和智能化浪潮深入各行各业,AI 应用从互联网向制造、医疗、交通等领域渗透,提升了社会对算力的依赖。公众对数据隐私和算法伦理的关注度上升,促使芯片设计需考虑安全特性。同时,高校扩招集成电路相关专业,人才供给逐渐增加,但高端领军人才仍稀缺。\n\n## 4. 技术因素 (Technological)\n技术层面呈现多元化:GPU 仍占主导,但 ASIC、FPGA 以及类脑芯片等新架构不断涌现。先进封装(Chiplet、3D 堆叠)成为提升性能的重要途径。国内企业在推理芯片领域已接近国际水平,但在训练芯片和高性能计算方面仍有差距。RISC-V 开源架构为自主 CPU/IP 提供了新机遇,生态建设逐步完善。\n\n## 5. 法律因素 (Legal)\n《出口管制法》《网络安全法》《数据安全法》等法规对芯片进出口、数据处理提出合规要求。知识产权保护力度加大,专利纠纷增多,企业需加强专利布局。美国实体清单对部分中国企业实施技术封锁,涉及 EDA 工具、先进设备等,企业被迫寻找替代方案。\n\n## 6. 环境因素 (Environmental)\n芯片制造是高能耗、高水耗产业,双碳目标下,政府对半导体工厂的能耗和排放监管趋严。台积电等领先企业已承诺使用 100% 可再生能源,国内厂商也开始建设绿色工厂。同时,AI 芯片的能效比成为重要竞争指标,低功耗设计有助于减少数据中心碳排放,符合 ESG 投资趋势。"
|
||||
},
|
||||
{
|
||||
"input": {
|
||||
"domain": "中国在线教育行业"
|
||||
},
|
||||
"output": "## 1. 政治因素 (Political)\n“双减”政策后,K12 学科类培训受到严格限制,行业转向素质教育、职业教育、成人教育等赛道。政府持续推动教育数字化,鼓励“互联网+教育”发展,出台《教育信息化 2.0 行动计划》,为在线教育提供政策支持。同时,数据安全和个人信息保护法规趋严,企业需加强用户数据合规管理。\n\n## 2. 经济因素 (Economic)\n2023 年中国在线教育市场规模约 5000 亿元,增速放缓至 10% 左右。受宏观经济影响,家庭教育支出趋于谨慎,但职场人自我提升需求仍在增长。资本退潮后,行业从烧钱获客转向精细化运营,现金流成为生存关键。\n\n## 3. 社会因素 (Social)\n终身学习理念普及,成年人接受在线培训的意愿增强。家长对素质教育的重视提升,如编程、美术、科学等课程需求旺盛。同时,教育资源不均衡问题依然存在,在线教育成为弥补差距的重要手段。老年人数字化学习需求初现,但适老化产品供给不足。\n\n## 4. 技术因素 (Technological)\nAI 技术应用深化:自适应学习、智能批改、虚拟教师等提升教学效率。直播技术成熟,支持万人同时在线互动。VR/AR 开始应用于职业实训场景。大数据分析助力个性化学习路径规划。但技术同质化严重,差异化体验成为竞争关键。\n\n## 5. 法律因素 (Legal)\n《未成年人保护法》对线上教育内容、时长、广告等做出限制。教育培训服务合同监管加强,退费纠纷处理更严格。知识产权保护方面,盗版网课仍屡禁不止,企业维权成本高。\n\n## 6. 环境因素 (Environmental)\n在线教育本身低碳,但数据中心能耗问题受到关注,部分头部企业开始采用绿色云计算服务。ESG 投资理念兴起,投资者关注教育企业的社会效益和普惠性。"
|
||||
},
|
||||
{
|
||||
"input": {
|
||||
"domain": "中国新能源汽车行业"
|
||||
},
|
||||
"output": "## 1. 政治因素 (Political)\n国家延续新能源汽车购置补贴、免征购置税等政策,推动市场渗透率提升。双积分政策迫使传统车企加速电动化转型。地方政府对充电基础设施建设给予补贴,优化使用环境。同时,欧美碳关税等贸易政策影响出口。\n\n## 2. 经济因素 (Economic)\n2023 年新能源汽车销量达 950 万辆,同比增长 37%,渗透率突破 30%。锂、钴等原材料价格高位波动,电池成本压力向整车传导。宏观经济复苏放缓可能影响消费意愿,但油价上涨凸显电车经济性。\n\n## 3. 社会因素 (Social)\n消费者接受度显著提高,环保意识增强,智能网联功能成为购车重要考量。充电焦虑、续航衰减等问题仍是部分用户顾虑。年轻群体偏好新品牌和个性化设计,为造车新势力提供机会。\n\n## 4. 技术因素 (Technological)\n电池技术持续突破,半固态电池开始装车,磷酸铁锂和三元路线并行。800V 高压平台普及,充电速度大幅提升。自动驾驶技术进入 L2+ 普及期,城市 NOA 功能竞争白热化。智能座舱从多屏向情感化交互演进。\n\n## 5. 法律因素 (Legal)\n数据安全法规要求车企加强用户隐私保护,自动驾驶事故责任认定尚在探索。动力电池回收政策逐步完善,生产者责任延伸制度落地。出口欧盟需满足新电池法碳足迹要求。\n\n## 6. 环境因素 (Environmental)\n全生命周期碳排放成为关注焦点,车企公布碳中和时间表。绿电使用比例影响产品碳足迹。电池梯次利用和再生材料应用受到鼓励。废旧电池环保处理压力增大,倒逼回收体系建设。"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"name": "scqa",
|
||||
"description": "SCQA 架构:Situation、Complication、Question、Answer,适用于问题分析与方案构建",
|
||||
"persona": "scqa_facilitator",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "situation",
|
||||
"description": "背景描述",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"template": "请使用 SCQA 架构分析以下情境:\n情境 (Situation):{{situation}}\n\n请依次构建:\n1. 复杂化 (Complication):指出情境中存在的矛盾或挑战。\n2. 问题 (Question):基于复杂化提炼出核心问题。\n3. 答案 (Answer):提出解决问题的初步方案或分析路径。\n\n输出格式要求:使用 Markdown 标题分节,每个部分至少 200 字。",
|
||||
"systemPrompt": "你是一位资深战略分析师,擅长运用 SCQA 框架构建逻辑严谨的问题分析报告。",
|
||||
"examples": [
|
||||
{
|
||||
"input": {
|
||||
"situation": "一家传统零售企业过去三年线上销售额年均增长仅 3%,而行业平均水平为 15%。公司拥有实体店网络和稳定供应链,但数字化转型进展缓慢。"
|
||||
},
|
||||
"output": "## 情境 (Situation)\n该传统零售企业拥有超过 20 年的历史,在全国拥有 300 家实体门店,供应链体系成熟,品牌认知度高。然而,随着电商的崛起,消费者购物习惯向线上迁移,公司虽然建立了官网和入驻第三方平台,但线上业务增长乏力,过去三年复合增长率仅为 3%,远低于行业 15% 的平均水平。\n\n## 复杂化 (Complication)\n尽管公司意识到数字化转型的必要性,并投入资金建设线上渠道,但收效甚微。主要矛盾在于:\n1. **线上线下割裂**:实体店和线上业务分属不同团队,缺乏协同,导致库存无法共享,促销活动不一致。\n2. **技术投入不足**:IT 部门仍以维护 ERP 系统为主,缺乏数据分析和个性化推荐能力,用户体验落后于纯电商对手。\n3. **组织文化阻力**:管理层多来自传统零售背景,对数字营销和用户运营理解不深,决策偏向保守,创新项目难以快速落地。\n\n## 问题 (Question)\n在资源有限且行业竞争加剧的背景下,如何通过有效的数字化转型策略,在两年内将线上销售增速提升至行业平均水平(15% 以上),同时保持实体店的盈利能力?\n\n## 答案 (Answer)\n建议采取“线上线下融合(OMO)”战略,具体路径如下:\n1. **组织重构**:成立独立的数字化事业部,由 CEO 直接领导,整合线上线下团队,建立统一考核指标(如全域 GMV、用户生命周期价值)。\n2. **技术升级**:引入成熟的电商中台,实现库存、会员、营销三通;部署 CDP 平台,利用客户数据构建精准画像,推动个性化推荐和自动化营销。\n3. **场景创新**:在门店部署智能终端,支持线上下单门店提货、门店缺货时引导至线上完成购买;试点“直播+门店”模式,将店员培养为导购主播,延伸服务半径。\n4. **敏捷试点**:选择一线城市 20 家门店作为试点,快速验证模式,成功后复制推广。同时设立创新基金,鼓励内部孵化新零售项目。\n\n通过以上措施,预计一年内可提升线上增速至 10%,两年内达到 15%,并带动整体营收增长。"
|
||||
},
|
||||
{
|
||||
"input": {
|
||||
"situation": "某三甲医院近期患者满意度调查得分持续下降,尤其在挂号、候诊环节抱怨最多。医院已推行线上预约,但现场仍存在排队过长、秩序混乱等问题。"
|
||||
},
|
||||
"output": "## 情境 (Situation)\n该医院是区域医疗中心,日均门诊量约 8000 人次。为缓解现场压力,两年前上线了微信预约挂号系统,目前预约率已达 60%。然而近期满意度调查显示,总体得分从 92 分降至 85 分,其中“挂号便捷性”和“候诊时间”两项降幅最大。\n\n## 复杂化 (Complication)\n线上预约本应分流患者,但实际并未有效改善体验:\n1. **预约时段不准**:预约时段为 1 小时窗口,但医生看诊速度不一,导致患者实际等待时间远超预期。\n2. **现场管理混乱**:仍有大量老年患者不会使用手机,依赖现场窗口挂号,与预约患者混流,加剧排队。\n3. **信息系统割裂**:预约系统与诊室叫号系统未打通,护士需手动核对,效率低下。\n4. **医患沟通不足**:医生问诊时间压缩,患者感觉“被敷衍”,投诉增多。\n\n## 问题 (Question)\n在不增加医生工作负荷的前提下,如何通过流程优化和技术手段,将患者平均候诊时间缩短 30%,并提升整体满意度?\n\n## 答案 (Answer)\n提出“精准预约+分时段报到”综合方案:\n1. **精准分时段预约**:将预约窗口缩短至 15 分钟,根据历史看诊速度动态调整每个时段号源,减少患者等候。\n2. **推行分时段报到制**:患者仅需在预约时段前 15 分钟到院扫码报到,过早到达者无法提前进入候诊队列,避免现场拥堵。\n3. **设置“无手机通道”**:保留 2 个现场窗口专为老年人服务,并配备志愿者协助,同时推广自助机。\n4. **打通信息系统**:预约平台与院内 HIS、叫号系统实时对接,实现自动排队叫号。\n5. **增加诊前沟通**:在候诊区屏幕播放科普视频,护士提供基础咨询,缓解患者焦虑。\n\n实施后预计平均候诊时间由 45 分钟降至 30 分钟,满意度提升至 90 分以上。"
|
||||
},
|
||||
{
|
||||
"input": {
|
||||
"situation": "某知识付费平台“课程完课率”不足 10%,用户购买课程后往往只学几节课就放弃,导致复购率低、口碑下滑。平台内容质量不差,但用户缺乏学习动力和督促。"
|
||||
},
|
||||
"output": "## 情境 (Situation)\n该平台上线三年,累计用户 500 万,主打职场技能、个人成长类课程。课程由知名讲师制作,平均评分 4.7 分。然而后台数据显示,完课率(完成 80% 以上课程)仅为 8%,大部分用户购买后一周内活跃,之后迅速流失。\n\n## 复杂化 (Complication)\n内容优质但完课率低,矛盾在于:\n1. **学习模式单一**:课程多为视频录播,缺乏互动和反馈,用户容易感到枯燥。\n2. **缺乏外部督促**:用户靠自律学习,一旦工作繁忙或热情减退,课程就被搁置。\n3. **目标感弱**:用户购买时冲动,但无明确学习计划和成果预期。\n4. **社群运营缺失**:用户之间无交流,无法形成学习氛围和互相激励。\n\n## 问题 (Question)\n如何在不增加大量人力成本的前提下,通过产品机制和轻度运营,将课程完课率提升至 30% 以上,并带动复购?\n\n## 答案 (Answer)\n设计“游戏化+社群轻运营”方案:\n1. **学习路径设计**:将课程拆分为每日 15 分钟的任务包,用户可按节奏完成,系统自动提醒。\n2. **积分与勋章体系**:每完成一节课获得积分,连续学习解锁勋章,可兑换优惠券或实物。\n3. **“学伴”匹配**:根据用户兴趣标签,系统自动匹配 3-5 人组成学习小组,共享进度,互相督促。\n4. **定期直播答疑**:每月邀请讲师进行直播,解答课程疑问,并鼓励学员分享学习心得。\n5. **毕业设计**:课程结束后布置小项目,用户提交后可获得电子证书,增强成就感。\n6. **数据驱动干预**:当用户超过 3 天未学习,自动推送定制化提醒或推荐下一个学习任务。\n\n通过上述机制,预计完课率可提升至 25-30%,复购率增加 20%,同时形成口碑传播。"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"name": "swot",
|
||||
"description": "SWOT 分析:从内部优势 (Strengths)、内部劣势 (Weaknesses)、外部机会 (Opportunities)、外部威胁 (Threats) 四个维度评估实体。",
|
||||
"persona": "strategy_advisor",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "entity",
|
||||
"description": "分析对象(如企业、项目、个人等)",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"template": "请使用 SWOT 框架分析以下对象:{{entity}}\n\n请按照以下结构展开:\n## 1. 优势 (Strengths)\n- 列举内部积极因素,并附上简要论证。\n## 2. 劣势 (Weaknesses)\n- 列举内部消极因素,并附上简要论证。\n## 3. 机会 (Opportunities)\n- 列举外部积极因素,并附上简要论证。\n## 4. 威胁 (Threats)\n- 列举外部消极因素,并附上简要论证。\n\n输出格式要求:使用 Markdown 标题分节,每个维度至少列出 3 个要点,总字数不低于 800 字。",
|
||||
"systemPrompt": "你是一位资深的战略分析顾问,擅长使用 SWOT 框架进行竞争态势分析,能够清晰区分内外部因素并提出客观见解。",
|
||||
"examples": [
|
||||
{
|
||||
"input": {
|
||||
"entity": "一家专注于新能源汽车电池研发的初创公司"
|
||||
},
|
||||
"output": "## 1. 优势 (Strengths)\n- **技术创新能力强**:创始团队来自顶尖高校和研究所,拥有 5 项固态电池核心专利,能量密度比市场主流产品高 30%。\n- **轻资产运营模式**:采用研发+技术授权的商业模式,无需自建大规模工厂,资金利用效率高。\n- **政策支持**:作为高新技术企业,享受税收减免和研发补贴,地方政府提供土地优惠用于建设中试线。\n\n## 2. 劣势 (Weaknesses)\n- **资金链紧张**:A 轮融资仅够支撑 18 个月,后续研发和商业化需要持续投入,抗风险能力弱。\n- **品牌知名度低**:在整车厂客户中缺乏信任背书,难以进入头部车企的供应链。\n- **量产经验不足**:团队以科研背景为主,缺乏规模化生产和质量控制的实战经验,中试到量产可能存在技术转化风险。\n\n## 3. 机会 (Opportunities)\n- **市场需求旺盛**:全球新能源汽车销量快速增长,对高能量密度、安全性的电池需求迫切,尤其是高端车型。\n- **产业链重构**:传统电池巨头产能饱和,车企开始寻求二供、三供,给新进入者提供了切入点。\n- **技术迭代窗口**:固态电池被公认为下一代技术,目前尚未有企业完全垄断,标准未定,存在弯道超车可能。\n\n## 4. 威胁 (Threats)\n- **巨头挤压**:宁德时代、LG 等已宣布固态电池研发计划,资金和产业化能力远超初创公司。\n- **技术路线风险**:固态电池存在硫化物、氧化物等多种技术路线,若押错方向可能导致前功尽弃。\n- **原材料价格波动**:锂、镍等关键材料价格受国际局势影响大,成本控制难度高。\n- **专利壁垒**:海外企业已布局大量基础专利,可能面临侵权诉讼或高额授权费。"
|
||||
},
|
||||
{
|
||||
"input": {
|
||||
"entity": "个人职业转型:从技术岗转向产品经理"
|
||||
},
|
||||
"output": "## 1. 优势 (Strengths)\n- **技术背景扎实**:5年开发经验,能与工程师高效沟通,理解技术可行性。\n- **逻辑思维强**:编程训练培养了结构化思考,擅长梳理复杂流程和需求。\n- **内部转岗优势**:熟悉公司产品和团队,无需重新适应企业文化。\n\n## 2. 劣势 (Weaknesses)\n- **缺乏产品经验**:无独立负责产品的经历,对用户研究、数据分析工具不熟练。\n- **沟通风格偏技术化**:需适应与业务、设计等角色的协作方式。\n- **年龄压力**:30岁转岗,面临与更年轻候选人的竞争。\n\n## 3. 机会 (Opportunities)\n- **公司内部需求**:当前产品线扩张,产品经理岗位有空缺,鼓励内部流动。\n- **行业趋势**:数字化转型推动产品经理需求,尤其懂技术的复合型人才受青睐。\n- **学习资源丰富**:线上课程、社区、导师制可快速补足知识短板。\n\n## 4. 威胁 (Threats)\n- **竞争激烈**:外部有大量经验丰富的产品经理涌入,内部也可能有其他转岗者。\n- **试错成本高**:转岗失败可能影响职业信心,且难退回原技术岗。\n- **经济周期**:若行业遇冷,企业可能缩减产品团队,新晋者易被裁员。"
|
||||
},
|
||||
{
|
||||
"input": {
|
||||
"entity": "某地方性连锁超市应对社区团购冲击"
|
||||
},
|
||||
"output": "## 1. 优势 (Strengths)\n- **实体网点密集**:在本市拥有 40 家门店,覆盖主要社区,居民步行可达,生鲜产品可即买即用。\n- **供应链稳定**:与本地农户长期合作,生鲜直采,品质可控,损耗率低于社区团购平台。\n- **品牌信任度高**:经营 20 年,在本地居民中有良好口碑,尤其是生鲜和熟食。\n\n## 2. 劣势 (Weaknesses)\n- **线上能力薄弱**:自建 APP 用户少,配送仅限门店 3 公里,且需满 99 元才免运费。\n- **价格竞争力不足**:采购规模不及全国性平台,部分标品价格高于社区团购。\n- **营销手段传统**:依赖店内海报和短信通知,缺乏数字化精准营销。\n\n## 3. 机会 (Opportunities)\n- **即时零售兴起**:消费者对“小时达”需求增加,超市可作为前置仓,发挥近场优势。\n- **社区关系深化**:可联合居委会开展团购、老年课堂等,增强用户粘性。\n- **政策支持**:地方政府鼓励实体零售数字化转型,提供补贴和培训。\n\n## 4. 威胁 (Threats)\n- **社区团购低价倾销**:巨头持续烧钱补贴,分流价格敏感型顾客。\n- **租金人工上涨**:实体店运营成本逐年攀升,压缩利润空间。\n- **消费者习惯迁移**:年轻一代更习惯手机下单送货上门,到店客流自然减少。\n- **竞品模仿**:其他连锁超市也在尝试 O2O,同质化竞争加剧。"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
[
|
||||
{
|
||||
"id": "strategy_advisor",
|
||||
"name": "战略分析顾问",
|
||||
"description": "擅长竞争态势分析,能够清晰区分内外部因素并提出客观见解。",
|
||||
"systemPrompt": "你是一位资深的战略分析顾问,擅长使用 SWOT 框架进行竞争态势分析,能够清晰区分内外部因素并提出客观见解。"
|
||||
},
|
||||
{
|
||||
"id": "root_cause_analyst",
|
||||
"name": "根因分析专家",
|
||||
"description": "擅长通过系统性追问深入挖掘问题的根本原因。",
|
||||
"systemPrompt": "你是一位擅长根因分析的问题解决专家,能够通过系统性追问深入挖掘问题的根本原因。"
|
||||
},
|
||||
{
|
||||
"id": "macro_environment_analyst",
|
||||
"name": "宏观环境分析专家",
|
||||
"description": "擅长运用 PESTLE 框架评估行业外部环境。",
|
||||
"systemPrompt": "你是一位宏观环境分析专家,擅长运用 PESTLE 框架评估行业外部环境,能够结合具体数据和发展趋势进行深入洞察。"
|
||||
},
|
||||
{
|
||||
"id": "structured_thinker",
|
||||
"name": "结构化思维分析师",
|
||||
"description": "擅长运用 5W3H 框架进行系统性拆解。",
|
||||
"systemPrompt": "你是一位擅长运用 5W3H 框架进行结构化思考的分析师,能够全面覆盖问题的各个维度,确保分析的系统性和深度。"
|
||||
},
|
||||
{
|
||||
"id": "scqa_facilitator",
|
||||
"name": "SCQA 引导师",
|
||||
"description": "擅长运用 SCQA 框架构建逻辑严谨的问题分析报告。",
|
||||
"systemPrompt": "你是一位资深战略分析师,擅长运用 SCQA 框架构建逻辑严谨的问题分析报告。"
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* Project Caffeine
|
||||
* Copyright (c) 2025-2026 Gitconomy Research
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Contributors:
|
||||
* - 郭晧 <guohao@gitconomy.org> (Initial Author)
|
||||
*/
|
||||
import { z } from 'zod';
|
||||
|
||||
export const generateSearchQueriesSchema = z.object({
|
||||
query: z.string().min(1),
|
||||
});
|
||||
|
||||
export const saveNoteSchema = z.object({
|
||||
filename: z.string().min(1, '文件名不能为空').refine(
|
||||
name => name.endsWith('.md'),
|
||||
{ message: '文件名必须以 .md 结尾' }
|
||||
),
|
||||
content: z.string().min(1, '内容不能为空')
|
||||
});
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* Project Caffeine 0.1.1
|
||||
* Copyright (c) 2025-2026 Gitconomy Research
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Contributors:
|
||||
* - 郭晧 <guohao@gitconomy.org> (Initial Author)
|
||||
*/
|
||||
|
||||
/**
|
||||
* 将用户的自然语言查询拆解为专业检索词列表
|
||||
* @param query 用户原始查询字符串
|
||||
* @returns 去重后的检索词数组(3~5 个)
|
||||
*/
|
||||
|
||||
export function generateSearchQueries(query: string): string[] {
|
||||
if (!query || query.trim().length === 0) {
|
||||
return ['通用研究主题'];
|
||||
}
|
||||
|
||||
// 1. 去除常见标点符号,替换为空格
|
||||
const cleaned = query.replace(/[,,。??、;;]/g, ' ');
|
||||
|
||||
// 2. 按空白字符分割,过滤掉长度小于 2 的词(避免单字噪音)
|
||||
const words = cleaned.split(/\s+/).filter(word => word.length >= 2);
|
||||
|
||||
// 3. 去重
|
||||
const uniqueWords = [...new Set(words)];
|
||||
|
||||
// 4. 若不足 3 个,补充基于原查询的扩展词
|
||||
while (uniqueWords.length < 3) {
|
||||
uniqueWords.push(`${query} 相关研究`);
|
||||
}
|
||||
|
||||
// 5. 截取前 5 个返回
|
||||
return uniqueWords.slice(0, 5);
|
||||
}
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
/**
|
||||
* Project Caffeine v0.1.1
|
||||
* Copyright (c) 2025-2026 Gitconomy Research
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Contributors:
|
||||
* - 郭晧 <guohao@gitconomy.org> (Initial Author)
|
||||
*/
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import personas from '../models/personas/personas.json';
|
||||
|
||||
// ==========================================
|
||||
// 类型定义
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* 思维框架的定义结构,与框架 JSON 文件中的字段一一对应。
|
||||
*/
|
||||
|
||||
export interface Framework {
|
||||
name: string;
|
||||
description: string;
|
||||
parameters: Array<{ name: string; description: string; required: boolean }>;
|
||||
template: string;
|
||||
systemPrompt?: string;
|
||||
persona?: string; // 引用的角色 ID
|
||||
examples?: Array<{ // Few-Shot 示例
|
||||
input: Record<string, string>; // 示例输入参数
|
||||
output: string; // 期望输出(Markdown 格式)
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
* MCP Prompts 原语所要求的消息序列格式。
|
||||
*/
|
||||
|
||||
export type PromptResult = {
|
||||
messages: Array<{
|
||||
role: 'system' | 'user' | 'assistant';
|
||||
content: { type: 'text'; text: string };
|
||||
}>;
|
||||
};
|
||||
|
||||
// ==========================================
|
||||
// 框架缓存与加载
|
||||
// ==========================================
|
||||
|
||||
/** 框架定义文件存放的目录路径 */
|
||||
|
||||
const FRAMEWORKS_DIR = path.join(__dirname, '../models/frameworks');
|
||||
|
||||
/** 框架缓存,避免重复读取文件系统 */
|
||||
|
||||
let frameworksCache: Framework[] | null = null;
|
||||
|
||||
/**
|
||||
* 从文件系统加载所有框架 JSON 文件。
|
||||
*
|
||||
* 该函数会读取 FRAMEWORKS_DIR 下所有 .json 文件,解析为 Framework 对象,
|
||||
* 并存入缓存。首次调用后,后续调用直接返回缓存数据。
|
||||
*
|
||||
* @returns {Promise<Framework[]>} 框架对象数组,若加载失败则返回空数组
|
||||
*/
|
||||
|
||||
async function loadFrameworks(): Promise<Framework[]> {
|
||||
if (frameworksCache) return frameworksCache;
|
||||
|
||||
try {
|
||||
const files = await fs.readdir(FRAMEWORKS_DIR);
|
||||
const jsonFiles = files.filter(f => f.endsWith('.json'));
|
||||
const frameworks = await Promise.all(
|
||||
jsonFiles.map(async file => {
|
||||
const content = await fs.readFile(path.join(FRAMEWORKS_DIR, file), 'utf-8');
|
||||
return JSON.parse(content) as Framework;
|
||||
})
|
||||
);
|
||||
frameworksCache = frameworks;
|
||||
return frameworks;
|
||||
} catch (error) {
|
||||
console.error('[PromptService] 加载框架失败:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 公开 API
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* 列出所有可用框架的元信息(不含模板、系统提示词和示例)。
|
||||
*
|
||||
* @returns {Promise<Array<Omit<Framework, 'template' | 'systemPrompt' | 'examples'>>>}
|
||||
* 框架元信息列表,每个框架包含名称、描述和参数列表
|
||||
*/
|
||||
|
||||
export async function listFrameworks(): Promise<Array<Omit<Framework, 'template' | 'systemPrompt' | 'examples'>>> {
|
||||
const frameworks = await loadFrameworks();
|
||||
return frameworks.map(({ name, description, parameters }) => ({
|
||||
name,
|
||||
description,
|
||||
parameters
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定框架的完整提示词消息序列。
|
||||
*
|
||||
* 该函数根据框架名称查找对应的框架定义,结合用户传入的参数,
|
||||
* 构建包含系统提示、Few-Shot 示例和当前用户请求的消息数组。
|
||||
* 系统提示优先使用框架关联的角色(persona),若未定义则使用框架自带的 systemPrompt。
|
||||
*
|
||||
* @param {string} name - 框架名称,需与框架 JSON 文件中的 name 字段一致
|
||||
* @param {Record<string, string>} args - 用户传入的参数键值对,用于填充模板中的 {{param}} 占位符
|
||||
* @returns {Promise<PromptResult>} 符合 MCP 规范的消息序列对象
|
||||
* @throws {Error} 当指定名称的框架不存在时抛出错误
|
||||
*/
|
||||
|
||||
export async function getFramework(name: string, args: Record<string, string>): Promise<PromptResult> {
|
||||
const frameworks = await loadFrameworks();
|
||||
const framework = frameworks.find(f => f.name === name);
|
||||
if (!framework) {
|
||||
throw new Error(`框架 "${name}" 不存在`);
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 确定系统提示词(优先使用角色矩阵)
|
||||
// ==========================================
|
||||
let systemPrompt = framework.systemPrompt || '';
|
||||
if (framework.persona) {
|
||||
const persona = personas.find(p => p.id === framework.persona);
|
||||
if (persona) {
|
||||
systemPrompt = persona.systemPrompt;
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 构建消息数组
|
||||
// ==========================================
|
||||
const messages: PromptResult['messages'] = [];
|
||||
|
||||
// 1. 系统消息
|
||||
if (systemPrompt) {
|
||||
messages.push({
|
||||
role: 'system',
|
||||
content: { type: 'text', text: systemPrompt }
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Few-Shot 示例(如果存在)
|
||||
if (framework.examples && Array.isArray(framework.examples)) {
|
||||
for (const example of framework.examples) {
|
||||
// 构建示例用户输入:将 example.input 中的参数填充到模板中
|
||||
let exampleUserContent = framework.template;
|
||||
for (const [key, value] of Object.entries(example.input)) {
|
||||
exampleUserContent = exampleUserContent.replace(new RegExp(`{{${key}}}`, 'g'), value);
|
||||
}
|
||||
messages.push({
|
||||
role: 'user',
|
||||
content: { type: 'text', text: exampleUserContent }
|
||||
});
|
||||
// 添加示例助手输出
|
||||
messages.push({
|
||||
role: 'assistant',
|
||||
content: { type: 'text', text: example.output }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 当前用户请求
|
||||
let currentUserContent = framework.template;
|
||||
for (const [key, value] of Object.entries(args)) {
|
||||
currentUserContent = currentUserContent.replace(new RegExp(`{{${key}}}`, 'g'), value);
|
||||
}
|
||||
messages.push({
|
||||
role: 'user',
|
||||
content: { type: 'text', text: currentUserContent }
|
||||
});
|
||||
|
||||
return { messages };
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
/**
|
||||
* Project Caffeine v0.1.1
|
||||
* Copyright (c) 2025-2026 Gitconomy Research
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Contributors:
|
||||
* - 郭晧 <guohao@gitconomy.org> (Initial Author)
|
||||
*/
|
||||
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
|
||||
/**
|
||||
* 本地知识库的根目录路径。
|
||||
*
|
||||
* 该目录存放所有 Markdown 笔记文件,所有文件操作均限定在此目录内,
|
||||
* 以防止路径遍历攻击。
|
||||
*
|
||||
* @constant {string}
|
||||
*/
|
||||
|
||||
|
||||
const OBSIDIAN_VAULT_PATH = '/home/wguo/Downloads/MyVault'; // 【⚠️ 重要配置】请修改为你电脑上真实的 Markdown 笔记文件夹绝对路径!
|
||||
|
||||
/**
|
||||
* 列出知识库中所有 Markdown 笔记的文件名。
|
||||
*
|
||||
* 该函数读取 OBSIDIAN_VAULT_PATH 目录下的所有文件,过滤出以 .md 结尾
|
||||
* (不区分大小写)的文件,并返回文件名列表。若目录不存在或无权限访问,
|
||||
* 则返回空数组并打印错误日志。
|
||||
*
|
||||
* @returns {Promise<string[]>} 包含所有笔记文件名的数组,若失败则返回空数组。
|
||||
*/
|
||||
|
||||
export async function listObsidianNotes(): Promise<string[]> {
|
||||
try {
|
||||
const files = await fs.readdir(OBSIDIAN_VAULT_PATH);
|
||||
return files.filter(file => file.toLowerCase().endsWith('.md'));
|
||||
} catch (error: any) {
|
||||
console.error(`[Project Caffeine] 无法读取知识库目录: ${error.message}`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取指定笔记文件的完整内容。
|
||||
*
|
||||
* 该函数首先对文件名进行安全校验,确保文件位于知识库目录内,
|
||||
* 防止路径遍历攻击。校验通过后,读取文件内容并返回。
|
||||
*
|
||||
* @param {string} filename - 要读取的笔记文件名(必须包含 .md 后缀)
|
||||
* @returns {Promise<string>} 笔记文件的文本内容
|
||||
* @throws {Error} 当文件名导致路径越界时抛出安全警告
|
||||
* @throws {Error} 当文件不存在或无权限读取时抛出错误
|
||||
*/
|
||||
|
||||
export async function readObsidianNote(filename: string): Promise<string> {
|
||||
const targetPath = path.resolve(OBSIDIAN_VAULT_PATH, filename);
|
||||
const safeVaultPath = path.resolve(OBSIDIAN_VAULT_PATH);
|
||||
|
||||
// 核心防御:防止大模型通过传入 "../../" 读取系统敏感文件
|
||||
if (!targetPath.startsWith(safeVaultPath)) {
|
||||
throw new Error(`安全警告:越权访问拦截!禁止读取目录外的文件: ${filename}`);
|
||||
}
|
||||
|
||||
try {
|
||||
const content = await fs.readFile(targetPath, 'utf-8');
|
||||
return content;
|
||||
} catch (error: any) {
|
||||
throw new Error(`无法读取笔记 [${filename}]: 文件可能不存在或无权限。`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存笔记到本地知识库。
|
||||
*
|
||||
* 该函数将内容写入指定文件,执行以下校验和操作:
|
||||
* 1. 验证文件名是否以 .md 结尾。
|
||||
* 2. 验证文件路径是否在知识库目录内,防止路径遍历攻击。
|
||||
* 3. 确保知识库目录存在(若不存在则自动创建)。
|
||||
* 4. 将内容写入文件。
|
||||
*
|
||||
* @param {string} filename - 笔记文件名(必须以 .md 结尾)
|
||||
* @param {string} content - 笔记内容(Markdown 格式)
|
||||
* @returns {Promise<string>} 保存成功的提示信息,包含文件绝对路径
|
||||
* @throws {Error} 当文件名不以 .md 结尾时抛出错误
|
||||
* @throws {Error} 当文件名导致路径越界时抛出错误
|
||||
* @throws {Error} 当目录创建失败或文件写入失败时抛出错误
|
||||
*/
|
||||
|
||||
export async function saveNote(filename: string, content: string): Promise<string> {
|
||||
// 1. 验证文件名是否以 .md 结尾
|
||||
if (!filename.endsWith('.md')) {
|
||||
throw new Error('文件名必须以 .md 结尾');
|
||||
}
|
||||
|
||||
// 2. 防止路径遍历攻击:解析绝对路径,并检查是否在 NOTES_DIR 下
|
||||
const fullPath = path.resolve(OBSIDIAN_VAULT_PATH, filename);
|
||||
const relative = path.relative(OBSIDIAN_VAULT_PATH, fullPath);
|
||||
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
||||
throw new Error('无效的文件名,不允许访问上层目录');
|
||||
}
|
||||
|
||||
// 3. 确保目标目录存在(可选,如果 NOTES_DIR 必须存在则可跳过)
|
||||
await fs.mkdir(OBSIDIAN_VAULT_PATH, { recursive: true });
|
||||
|
||||
// 4. 写入文件
|
||||
await fs.writeFile(fullPath, content, 'utf-8');
|
||||
return `笔记已保存至: ${fullPath}`;
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig", // Uncomment if you want schema validation and the URL is reachable
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "node",
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"sourceMap": true, // 【关键】生成 .js.map 文件,用于 VS Code 断点映射
|
||||
"strict": true, // 开启严格模式
|
||||
"esModuleInterop": true, // 允许默认导入 CommonJS 模块
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true // 允许导入 JSON 文件
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||