本次提交对LLM服务进行了全面重构,主要变更包括: - 引入LangChain库,支持多模型提供商(OpenAI、Gemini) - 重构错误处理机制,增加更精细的错误类型 - 优化模型实例缓存和配置验证 - 更新服务层接口,提高代码可读性和可维护性 - 调整模型配置类型,增加provider字段 - 更新单元测试和集成测试,适配新的服务架构 - 添加LangChain相关依赖 重构目标: - 提高代码的可扩展性 - 统一模型调用接口 - 改进错误处理和类型安全 - 支持更多模型提供商
12 KiB
12 KiB
项目经验总结(优化版)
📋 快速索引
架构设计
API集成规范
-
配置分离
- 业务逻辑与API配置解耦
- 统一接口格式(OpenAI兼容)
- 独立管理提示词模板
// 配置示例 export default { baseURL: "https://api.openai.com/v1", models: ["gpt-4", "gpt-3.5"] } -
模块化结构
├─ api/ # API封装 ├─ services/ # 业务逻辑 ├─ config/ # 全局配置 ├─ components/# UI组件 └─ prompts/ # 提示模板库
LLM服务架构
| 设计要点 | 实现方案 |
|---|---|
| 接口标准化 | 统一使用OpenAI格式 |
| 多服务商兼容 | Provider标识区分(如Gemini) |
| 动态配置更新 | 支持热加载配置 |
| 敏感信息管理 | 环境变量+本地加密存储 |
错误处理
典型问题与修复
| 问题场景 | 解决方案 | 发生日期 |
|---|---|---|
| 模板ID与模型Key混淆 | 明确功能ID与API Key分离 | 2024-03-22 |
| 状态同步异常 | 增加状态同步处理函数 | 2024-03-22 |
| 全局currentProvider污染 | 显式传递模型参数 | 2024-03-22 |
处理原则
- 开发环境:保留完整错误堆栈
- 生产环境:友好提示+日志记录
- 通用规则:
try { await apiCall(); } catch (err) { console.error("[API Error]", err.context); throw new Error("服务调用失败,请检查配置"); }
测试规范
核心要点
-
环境变量
// Vite项目必须使用import.meta.env const apiKey = import.meta.env.VITE_GEMINI_KEY; -
测试架构
- 集中管理多服务商配置
- 独立测试数据库
- 模拟异常场景(网络错误/无效Token)
-
用例设计
describe("API测试", () => { it("应正确处理多轮对话", async () => { const response = await sendRequest(messages); expect(response.content).toMatch(/优化建议/); }); });
Vue开发
组件规范
-
Composable使用
// 正确用法 const { data } = useFetch(); // 错误用法 onMounted(() => { const { data } = useFetch(); // 禁止在生命周期内调用 }); -
状态同步方案
<!-- 父组件 --> <ModelManager @update="handleModelUpdate" /> <!-- 子组件 --> <script setup> const emit = defineEmits(['update']); const updateModel = () => { emit('update', newModels); } </script>
UI设计准则
| 分类 | 规范 |
|---|---|
| 布局系统 | 基于Tailwind的响应式网格 |
| 主题管理 | CSS变量+透明度层级控制 |
| 交互反馈 | 加载状态/禁用状态/过渡动画 |
工具与配置
NPM管理
# 常用命令
npm outdated # 检查可更新包
ncu -u "eslint*" # 安全更新指定包
版本策略
| 符号 | 含义 | 示例 |
|---|---|---|
| ^ | 允许小版本更新 | ^1.2.3 → 1.3.0 |
| ~ | 仅允许补丁更新 | ~1.2.3 → 1.2.4 |
YAML处理
-
常见错误
# 错误示例 -invalid: 缺少空格 tags: -test # 列表格式错误 -
修复方案
# 正确格式 valid_key: "值" tags: - test - template
关键重构经验
服务层优化(2024-03-19)
-
类型安全
interface ModelConfig { name: string; baseURL: string; // 非可选字段 models: string[]; } -
错误处理改进
- 统一继承自
LLMError基类 - 批量验证错误收集
class ValidationError extends LLMError { constructor(errors: string[]) { super(`配置验证失败: ${errors.join(', ')}`); } } - 统一继承自
配置管理(2024-03-21)
-
模型启用验证
function validateConfig(config) { if (!config.apiKey) { throw new Error("API Key为必填项"); } } -
环境变量处理
/// <reference types="vite/client" /> interface ImportMetaEnv { VITE_GEMINI_KEY: string; }
LLM 服务重构经验
LangChain 集成
- 日期:[当前日期]
- 场景:将原生 API 调用重构为 LangChain 实现
- 主要改进:
- 更好的可扩展性
- 统一的接口抽象
- 内置的错误处理
- 支持高级特性
最佳实践
- 使用模型实例缓存提高性能
- 统一消息格式转换
- 保持公共接口稳定
- 分层错误处理
注意事项
- 需要安装相关依赖:
npm install langchain @langchain/core @langchain/openai @langchain/anthropic - 不同模型提供商的配置参数可能不同
- 建议使用 TypeScript 获得更好的类型支持
未来扩展
- 支持更多模型提供商
- 添加高级功能:
- 链式调用
- 代理功能
- 向量存储
- 记忆管理
- 性能优化:
- 并发请求
- 流式响应
- 缓存策略
API密钥管理经验
设计决策
- 采用用户自管理API密钥的方式
- 启用OpenAI库的浏览器支持
- 提供完整的密钥管理指导
原因
- 应用定位为个人工具
- 用户可以完全控制自己的API使用
- 避免后端服务的开销和维护
- 保持应用的简单性和可移植性
安全考虑
- 密钥由用户自行创建和管理
- 建议用户为应用单独创建密钥
- 推荐在OpenAI平台设置使用限制
- 提供密钥安全使用的最佳实践指导
用户体验优化
- 提供详细的API密钥获取教程
- 添加密钥验证功能
- 显示当前密钥的使用状态
- 提供密钥使用的安全提示
技术实现
const model = new OpenAI({
apiKey: userProvidedKey,
baseURL: modelConfig.baseURL,
dangerouslyAllowBrowser: true // 必要的配置
});
最佳实践建议
- 在本地存储中加密保存密钥
- 提供清除密钥的功能
- 定期提醒用户检查密钥使用情况
- 提供密钥轮换的指导
测试修复经验总结
1. 常见测试失败原因
- 方法名不匹配:实现变更后未同步更新测试用例
- 错误消息不一致:测试期望与实际实现的错误消息格式不匹配
- 模板验证逻辑不一致:测试用例未反映最新的业务逻辑
2. 修复策略
- 统一错误消息格式:确保测试用例与实际代码的错误消息完全匹配
- 移除多余的模拟:删除不必要的模拟调用
- 添加缺失的模拟:确保所有必要的依赖都被正确模拟
3. 最佳实践
- 错误消息管理:统一定义错误消息,避免分散管理
- 测试用例维护:及时更新测试用例以反映最新的代码行为
- Mock 策略:只模拟必要的方法,避免过度模拟
- 测试覆盖:关注边界条件和异常场景的测试
4. 改进建议
- 使用常量定义错误消息
- 完善错误处理的类型定义
- 添加更多边界条件测试
- 创建测试辅助函数简化重复设置
5. 测试代码示例
// 错误消息常量
const ERROR_MESSAGES = {
TEMPLATE_NOT_FOUND: '模板不存在或无效',
MANAGER_NOT_INITIALIZED: '模板管理器未初始化',
MODEL_NOT_FOUND: '模型不存在'
} as const;
// 测试辅助函数
function setupMocks(options: {
templateContent?: string,
modelExists?: boolean,
messageResult?: string
}) {
const { templateContent, modelExists = true, messageResult } = options;
if (modelExists) {
vi.spyOn(modelManager, 'getModel').mockReturnValue(mockModelConfig);
} else {
vi.spyOn(modelManager, 'getModel').mockReturnValue(undefined);
}
if (templateContent !== undefined) {
vi.spyOn(templateManager, 'getTemplate').mockResolvedValue({
...mockTemplate,
template: templateContent
});
}
if (messageResult) {
vi.spyOn(llmService, 'sendMessage').mockResolvedValue(messageResult);
}
}
6. 测试改进经验(2024-03-22)
边界条件测试要点
-
输入验证
- 空字符串处理
- 超长输入处理
- 特殊字符处理
- 无效参数处理
-
服务异常处理
- 超时处理
- 空响应处理
- 网络错误处理
- 服务降级处理
-
状态记录验证
- 历史记录完整性
- 状态变更正确性
- 关联关系准确性
- 时序记录准确性
测试代码组织
describe('功能测试组', () => {
// 基础测试
describe('基本功能', () => {
it('正常场景', async () => {});
it('异常场景', async () => {});
});
// 边界条件
describe('边界条件', () => {
it('空输入', async () => {});
it('超长输入', async () => {});
it('特殊字符', async () => {});
});
// 状态管理
describe('状态管理', () => {
it('状态记录', async () => {});
it('状态查询', async () => {});
it('状态关联', async () => {});
});
});
测试优化技巧
-
Mock 优化
// 统一的 Mock 设置 function setupServiceMocks(options: MockOptions) { const { templateContent, modelResponse, shouldFail } = options; if (shouldFail) { vi.spyOn(service, 'call').mockRejectedValue(new Error('预期的错误')); } else { vi.spyOn(service, 'call').mockResolvedValue(modelResponse); } } -
错误处理验证
// 统一的错误处理验证 expect(() => { // 触发错误的操作 }).rejects.toThrow(expect.objectContaining({ name: 'ValidationError', message: expect.stringContaining('预期的错误消息') })); -
测试数据管理
// 集中管理测试数据 const TEST_DATA = { validPrompt: '有效的提示词', invalidPrompt: '', longPrompt: 'a'.repeat(10000), specialChars: '!@#$%^&*()', } as const;
改进效果
- 测试覆盖率提升
- 边界场景更完善
- 代码可维护性提高
- 测试执行效率提升
LangChain消息类型修复经验
问题描述
在使用LangChain进行消息处理时,遇到类型不匹配的问题:
Argument of type 'AIMessage' is not assignable to parameter of type 'AIMessageChunk'
原因分析
- LangChain的消息类型有多种,包括
AIMessage和AIMessageChunk AIMessageChunk是专门用于流式响应的消息类型- 在调用
invoke方法时,返回类型为AIMessageChunk
解决方案
- 将导入语句从
AIMessage改为AIMessageChunk:
import { AIMessageChunk } from '@langchain/core/messages'
- 更新所有使用
AIMessage的地方为AIMessageChunk
最佳实践
- 使用正确的消息类型:
AIMessage: 用于普通消息AIMessageChunk: 用于流式响应HumanMessage: 用于用户输入SystemMessage: 用于系统消息
- 在mock测试时确保使用正确的消息类型
- 参考LangChain文档中的类型定义