Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/cfool_try-agent.json
T

1 line
17 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"repo": "cfool/try-agent", "files": {"src/model/client.ts": {"98fcf3fd123acffb549349a97baa8a1ced09810e": {"t": 1785768203.0420024, "n": 3499, "h": "6c748ce187825f9e", "c": "import { DeepSeekProvider } from \"./providers/deepseek.js\";\nimport { GeminiProvider } from \"./providers/gemini.js\";\nimport { TencentProvider } from \"./providers/tencent.js\";\nimport type { Message, ModelProvider, SendMessageOptions, SendMessageResult, StreamChunk } from \"./providers/types.js\";\nimport { ZhiPuProvider } from \"./providers/zhipu.js\";\n\nexport interface ModelConfig {\n name: string;\n alias?: string;\n provider: ModelProvider;\n}\n\nexport class ModelClient {\n private providers = new Map<string, ModelConfig>();\n private currentModel: string | null = null;\n\n registerModel(model: ModelConfig): void {\n this.providers.set(model.name, model);\n if (!this.currentModel) {\n this.currentModel = model.name;\n }\n }\n\n use(model: string): void {\n if (!this.providers.has(model)) {\n throw new Error(\n `Unknown model \"${model}\". Available: ${[...this.providers.keys()].join(\", \")}`\n );\n }\n this.currentModel = model;\n }\n\n getActiveModel(): ModelConfig {\n if (!this.currentModel || !this.providers.has(this.currentModel)) {\n throw new Error(\"No model registered.\");\n }\n return this.providers.get(this.currentModel)!;\n }\n\n /** Return all registered models with their active status. */\n listModels(): Array<{ name: string; alias?: string; active: boolean }> {\n return [...this.providers.values()].map((m) => ({\n name: m.name,\n alias: m.alias,\n active: m.name === this.currentModel,\n }));\n }\n\n async sendMessage(messages: Message[], options?: SendMessageOptions): Promise<SendMessageResult> {\n const modelName = options?.model ?? this.currentModel;\n if (!modelName || !this.providers.has(modelName)) {\n throw new Error(\n `Model \"${modelName}\" not found. Available: ${[...this.providers.keys()].join(\", \")}`\n );\n }\n const provider = this.providers.get(modelName)!.provider;\n return provider.sendMessage(messages, options);\n }\n\n async *streamMessage(\n messages: Message[],\n options?: SendMessageOptions\n ): AsyncGenerator<StreamChunk, void, unknown> {\n const modelName = options?.model ?? this.currentModel;\n if (!modelName || !this.providers.has(modelName)) {\n throw new Error(\n `Model \"${modelName}\" not found. Available: ${[...this.providers.keys()].join(\", \")}`\n );\n }\n const provider = this.providers.get(modelName)!.provider;\n yield* provider.streamMessage(messages, options);\n }\n}\n\nconst client = new ModelClient();\n\n// Register available providers based on env vars\nconst geminiKey = process.env.GEMINI_API_KEY;\nif (geminiKey) {\n client.registerModel({\n name: 'gemini-3-flash-preview',\n alias: 'Gemini-3-Flash',\n provider: new GeminiProvider({ apiKey: geminiKey, model: 'gemini-3-flash-preview' }),\n });\n}\n\nconst deepseekKey = process.env.DEEPSEEK_API_KEY;\nif (deepseekKey) {\n client.registerModel({\n name: 'deepseek-v3.2',\n alias: 'DeepSeek-V3.2',\n provider: new DeepSeekProvider({ apiKey: deepseekKey, model: 'deepseek-chat' })\n });\n}\n\nconst zhipuKey = process.env.ZHIPU_API_KEY;\nif (zhipuKey) {\n client.registerModel({\n name: 'glm-5',\n alias: 'GLM-5',\n provider: new ZhiPuProvider({ apiKey: zhipuKey, model: 'glm-5' })\n });\n}\n\nconst tencentKey = process.env.TENCENT_API_KEY;\nif (tencentKey) {\n client.registerModel({\n name: 'hunyuan-turbos-latest',\n alias: 'Hunyuan-Turbos',\n provider: new TencentProvider({ apiKey: tencentKey, model: 'hunyuan-turbos-latest' })\n });\n}\n\nexport { client };\n"}}, "docs/01b-multi-model.md": {"98fcf3fd123acffb549349a97baa8a1ced09810e": {"t": 1785768879.2355077, "n": 5853, "h": "ad43ebb958ce8639", "c": "---\ntitle: 多模型支持\nnav_order: 2\nparent: 实战开发\n---\n\n# 第 1.5 阶段:给 AI 换个\"大脑\" (Multi-Model Support)\n\n**Branch:** `01b-multi-model`\n\n在[上一章](./01-hello-ai.md)我们给 AI 换了各种\"人设\"(System Prompt),但无论人设怎么变,驱动它的\"大脑\"始终是同一个模型。\n\n如果说 System Prompt 是演员的**剧本**,那模型供应商 (Model Provider) 就是演员的**大脑**。同一个剧本,让不同的演员来演,效果天差地别。\n\n这个项目就像一个\"大脑\"插拔机器人,我们内置了对 **Gemini**、**DeepSeek**、**智谱 (ZhiPu)** 和 **腾讯混元 (Tencent Hunyuan)** 的支持。你可以随时给你的 AI 代理换个\"脑子\"!\n\n## 为什么要换\"大脑\"?\n\n不同的\"大脑\"有不同的特长:\n\n* **Gemini (gemini-3-flash-preview)**: Google 出品,综合能力强,像个全能学霸。\n* **DeepSeek (deepseek-v3.2)**: 在代码和推理方面有奇效,是个逻辑鬼才。\n* **智谱 (glm-5)**: 中文理解能力超群,是个本土文学家。\n* **腾讯混元 (hunyuan-turbos-latest)**: 腾讯自研大模型,国内访问快、中文能力强。\n* **成本与速度**: 不同模型的定价和响应速度也不同。\n\n换着用,总能找到最适合你当前任务的那个\"大脑\"。\n\n## 核心问题:各家 API 协议不统一\n\n要支持多模型,第一个绕不开的问题就是——**各家供应商的 API 协议各不相同**。\n\n举个例子,同样是\"发送一条消息并调用工具\",Gemini 和 OpenAI 系的请求格式天差地别:\n\n| 差异点 | Gemini 原生 API | OpenAI 兼容 API (DeepSeek / 智谱 / 混元) |\n|---|---|---|\n| 端点格式 | `models/{model}:generateContent` | `/chat/completions` |\n| 角色名称 | `user` / `model` | `user` / `assistant` / `system` |\n| System Prompt | 顶层 `system_instruction` 字段 | `role: \"system\"` 的消息 |\n| 工具声明 | `tools: [{ function_declarations: [...] }]` | `tools: [{ type: \"function\", function: {...} }]` |\n| 工具调用结果 | `role: \"user\"` + `functionResponse` 部分 | `role: \"tool\"` + `tool_call_id` |\n| 工具调用参数 | 直接是 JSON 对象 (`args: {...}`) | 序列化为字符串 (`arguments: \"{...}\"`) |\n| 认证方式 | URL 参数 `?key=xxx` | Header `Authorization: Bearer xxx` |\n\n> 关于工具调用的协议差异,在03/04,给agent增加工具调用的章节中会有相关代码\n\n如果你要做一个支持多模型供应商的 Agent,就必须考虑这些协议差异,并做好协议转换。好消息是,目前大部分国内模型供应商(DeepSeek、智谱、腾讯混元等)都提供了兼容 OpenAI 格式的 API,这大大降低了适配成本——你只需要处理两种协议:**Gemini 原生** 和 **OpenAI 兼容**。\n\n## 架构设计:Provider 抽象层\n\n理解了协议差异问题后,我们的解决方案就很自然了:**统一接口 + 分层抽象**。\n\n```\nModelClient (统一入口:注册、切换、路由)\n │\n ├── GeminiProvider (Gemini 原生协议适配)\n │ 转换: Message ↔ GeminiContent\n │ 角色映射: \"model\" → \"model\", \"tool\" → \"user\" + functionResponse\n │\n └── OpenAICompatibleProvider (OpenAI 协议适配)\n 转换: Message ↔ ChatMessage\n 角色映射: \"model\" → \"assistant\", \"tool\" → \"tool\" + tool_call_id\n │\n ├── DeepSeekProvider (baseUrl: api.deepseek.com)\n ├── ZhiPuProvider (baseUrl: open.bigmodel.cn/api/paas/v4)\n └── TencentProvider (baseUrl: api.hunyuan.cloud.tencent.com/v1)\n```\n\n### 第一层:统一的 ModelProvider 接口\n\n不管底层协议怎么变,上层代码只看一个接口:\n\n```typescript\n// src/model/providers/types.ts\nexport interface ModelProvider {\n name: string;\n sendMessage(\n messages: Message[],\n options?: SendMessageOptions\n ): Promise<SendMessageResult>;\n}\n```\n\n`SendMessageOptions` 支持传入 `systemInstruction` 和 `tools`(函数声明),`SendMessageResult` 会返回文本或函数调用请求。上层的 Chat、Agent Loop、工具调用等逻辑完全不需要关心底层用的是哪家 API。\n\n### 第二层:协议适配\n\n每个 Provider 的核心工作就是**协议转换**——把统一的 `Message` 翻译成各家 API 能理解的格式,再把响应翻译回来。\n\n**GeminiProvider** 需要处理 Gemini 特有的格式(比如工具结果要以 `role: \"user\"` 发送,函数参数直接是 JSON 对象),以及 Gemini 独有的 `thoughtSignature` 字段。\n\n**OpenAICompatibleProvider** 则处理 OpenAI 格式的转换(`role: \"model\"` → `\"assistant\"`、函数参数需要 `JSON.stringify` 序列化等)。由于 DeepSeek、智谱、腾讯混元都兼容这套格式,它们只需继承这个基类,提供自己的 `baseUrl` 就行:\n\n```typescript\n// src/model/providers/deepseek.ts — 只需 5 行就完成一个新供应商的接入\nexport class DeepSeekProvider extends OpenAICompatibleProvider {\n constructor(options: { apiKey: string; model: string }) {\n super({\n name: \"deepseek\",\n apiKey: options.apiKey,\n model: options.model,\n baseUrl: \"https://api.deepseek.com\",\n });\n }\n}\n```\n\n这意味着,如果你将来要接入一个新的 OpenAI 兼容供应商(比如 Moonshot、百川等),只需要写一个类似的子类,改个 `baseUrl` 就完事了。\n\n### 第三层:ModelClient 注册与切换\n\n`ModelClient` 负责模型的注册和运行时切换。每个模型以 `name`(模型标识)和 `alias`(显示名)注册:\n\n```typescript\n// src/model/client.ts\nclient.registerModel({\n name: 'deepseek-v3.2',\n alias: 'DeepSeek-V3.2',\n provider: new DeepSeekProvider({ apiKey: deepseekKey, model: 'deepseek-chat' })\n});\n```\n\n`name` 是你在 `/use` 命令和 `MODEL` 环境变量中使用的标识符,`alias` 是展示给用户看的友好名称。\n\n## 如何换\"大脑\"?\n\n三步走:搞到 Key -> 配置 Key -> 切换着用。\n\n### 1. 搞到 API Keys\n\n就像你需要一把钥匙才能打开 Gemini 的门一样,你也需要其他家的钥匙。\n\n* **Gemini**: 去 [Google AI Studio](https://aistudio.google.com/) 获取 API Key。\n* **DeepSeek**: 去 [DeepSeek 官网](https://platform.deepseek.com/api_keys) 注册并获取 API Key。\n* **智谱**: 去 [智谱开放平台](https://open.bigmodel.cn/overview) 注册并获取 API Key。\n* **腾讯混元**: 去 [腾讯混元控制台](https://hunyuan.cloud.tencent.com/#/app/apiKeyMana) 获取 API Key。\n\n### 2. 配置 `.env` 文件\n\n把你新搞到的钥匙串在钥匙链 (`.env` 文件) 上:\n\n```env\n# Google Gemini (全能学霸)\nGEMINI_API_KEY=AIzaSyDxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n# DeepSeek (逻辑鬼才)\nDEEPSEEK_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx\n\n# 智谱 (本土文学家)\nZHIPU_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxx\n\n# 腾讯混元 (国产之光)\nTENCENT_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx\n```\n\n程序启动时会自动检测哪些 Key 存在,并注册对应的模型。**不需要的供应商可以不配置**,只要至少配了一个就行。\n\n### 3. 切换\"大脑\"\n\n我们提供了两种切换方式:\n\n#### 方式一:指定默认模型\n\n想让你的 AI 默认就用某个模型?在 `.env` 里用 `MODEL` 环境变量指定模型名:\n\n```env\n# 指定默认模型(填模型注册名,不是 provider 名)\nMODEL=hunyuan-turbos-latest\n```\n\n支持的模型名:\n\n| 模型名 | 别名 | 供应商 |\n|---|---|---|\n| `gemini-3-flash-preview` | Gemini-3-Flash | Google |\n| `deepseek-v3.2` | DeepSeek-V3.2 | DeepSeek |\n| `glm-5` | GLM-5 | 智谱 |\n| `hunyuan-turbos-latest` | Hunyuan-Turbos | 腾讯 |\n\n如果不设置 `MODEL`,程序会使用第一个成功注册的模型。\n\n#### 方式二:运行时热插拔\n\n更酷的是,你可以在聊天过程中,随时给 AI 换\"脑\"!就像给机器人换芯片一样。\n\n只要在聊天框里输入 `/use` 命令 + **模型名**:\n\n```\nYou: /use deepseek-v3.2\n```\n> 系统会回应:\n> `Switched to Model: DeepSeek-V3.2`\n\n然后,你的下一句话就会由 DeepSeek 来处理了。\n\n```\nYou: /use glm-5\n```\n> 系统会回应:\n> `Switched to Model: GLM-5`\n\n切换到腾讯混元:\n\n```\nYou: /use hunyuan-turbos-latest\n```\n\n想换回 Gemini?也一样:\n\n```\nYou: /use gemini-3-flash-preview\n```\n\n> **注意**:`/use` 后面跟的是模型注册名(即上表中\"模型名\"列的值),不是供应商名称。如果输入了不存在的名称,系统会提示可用的模型列表。\n\n> **提示**:在 [第 9 部分:TUI](./09-tui.md) 中,`/use` 命令将升级为交互式的 `/model` 命令,通过选择面板切换模型,无需手动输入模型名。\n\n**动手试试:**\n1. 配置好至少两个模型的 API Key。\n2. 启动项目 (`npm start`)。\n3. 问一个相同的问题,比如\"用 Python 写一个快速排序\",然后用 `/use` 命令切换模型,看看不同\"大脑\"给出的代码风格和解释有什么不一样。\n\n**能力:** 可以在多个\"大脑\"之间无缝切换,为不同的任务选择最合适的 AI 模型。\n**类比:** 你现在拥有一个复仇者联盟,而不是只有一个钢铁侠。\n"}}, "docs/SETUP.md": {"98fcf3fd123acffb549349a97baa8a1ced09810e": {"t": 1785768891.702059, "n": 2456, "h": "567d5cac9e693d68", "c": "---\ntitle: 环境配置\nnav_order: 1\nparent: 准备工作\n---\n\n# 环境配置 (Setup)\n\n在开始构建 Agent 之前,我们需要准备好开发环境。\n\n## 1. 前置要求\n\n确保你的电脑上安装了:\n\n* **Node.js**: 推荐 v18 或更高版本。\n* **Git**: 用于版本控制。\n* **包管理器**: npm (随 Node.js 安装), pnpm, 或 yarn。\n\n## 2. 获取 Gemini API Key\n\n本项目使用 Google Gemini 模型。你需要一个 API Key:\n\n1. 访问 [Google AI Studio](https://aistudio.google.com/)。\n2. 登录你的 Google 账号。\n3. 点击 **\"Get API key\"**。\n4. 点击 **\"Create API key\"**。\n5. 复制生成的 Key(以 `AIza` 开头)。\n\n> **注意**:Gemini API 目前有免费额度,足够开发测试使用。\n\n## 3. 安装项目依赖\n\n在终端中执行:\n\n```bash\n# 1. 克隆项目 (如果你还没克隆)\n# git clone <repository-url>\n# cd try_agent\n\n# 2. 安装依赖\nnpm install\n```\n\n## 4. 配置环境变量\n\n项目根目录下需要一个 `.env` 文件来存储敏感配置(如 API Key)。\n\n1. 新建一个配置文件:\n ```bash\n touch .env\n ```\n\n2. 编辑 `.env` 文件,填入你的 API Key:\n ```env\n GEMINI_API_KEY=AIzaSyDxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n ```\n\n## 5. 配置多模型供应商 (可选)\n\n除了 Google Gemini,本项目还支持 DeepSeek、智谱 (ZhiPu) 和腾讯混元 (Tencent Hunyuan)。\n\n1. **获取 API Key**:\n * **DeepSeek**: 访问 [DeepSeek 官网](https://platform.deepseek.com/api_keys) 获取 API Key。\n * **智谱**: 访问 [智谱开放平台](https://open.bigmodel.cn/overview) 获取 API Key。\n * **腾讯混元**: 访问 [腾讯混元控制台](https://cloud.tencent.com/product/hunyuan) 获取 API Key。\n\n2. **配置环境变量**:\n 在 `.env` 文件中,添加相应模型的 API Key(不需要的供应商可以不配):\n ```env\n # ... 其他配置\n DEEPSEEK_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx\n ZHIPU_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxx\n TENCENT_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx\n ```\n\n3. **指定默认模型 (可选)**:\n 你可以通过 `MODEL` 环境变量设置启动时默认使用的模型。值为模型注册名:\n ```env\n # ... 其他配置\n MODEL=hunyuan-turbos-latest\n ```\n 可选值:`gemini-3-flash-preview`、`deepseek-v3.2`、`glm-5`、`hunyuan-turbos-latest`。\n 如果不设置,程序将使用第一个成功注册的模型。\n\n## 6. 运行与切换模型\n\n一切准备就绪,运行项目:\n\n```bash\nnpm start\n```\n\n如果看到类似下面的输出,说明配置成功!\n\n```text\n[DEBUG] Using preferred model: hunyuan-turbos-latest\nCurrent Model: Hunyuan-Turbos\nAI Chat (type '/exit' to quit, '/new' to start a new chat, '/use <model>' to switch, '/agents' to list agents, '/skills' to list skills)\n\nYou:\n```\n\n### 在运行时切换模型\n\n你可以在聊天中随时使用 `/use` 命令切换模型,参数为模型注册名。\n\n* 切换到 DeepSeek:\n ```\n You: /use deepseek-v3.2\n ```\n* 切换到智谱:\n ```\n You: /use glm-5\n ```\n* 切换到腾讯混元:\n ```\n You: /use hunyuan-turbos-latest\n ```\n* 切换回 Gemini:\n ```\n You: /use gemini-3-flash-preview\n ```\n\n> **提示**:在 [第 9 部分:TUI](./09-tui.md) 中,`/use` 命令将升级为交互式的 `/model` 命令,通过选择面板切换模型,无需手动输入模型名。\n\n## 下一步\n\n环境配置完成后,请阅读 [**第 0 章:核心概念**](./00-concepts.md),了解 Agent 背后的基本原理。\n"}}}}