feat: 初始化 AI Gateway 项目
实现支持 OpenAI 和 Anthropic 双协议的统一大模型 API 网关 MVP 版本,包含: - OpenAI 和 Anthropic 协议代理 - 供应商和模型管理 - 用量统计 - 前端配置界面
This commit is contained in:
14
openspec/config.yaml
Normal file
14
openspec/config.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
schema: spec-driven
|
||||
|
||||
context: |
|
||||
- **优先阅读README.md**获取项目结构与开发规范,所有代码风格、命名、注解、依赖、API等规范以README为准
|
||||
- 新增代码优先复用已有组件、工具、依赖库,不引入新依赖
|
||||
- 涉及模块结构、API、实体等变更时同步更新README.md
|
||||
- Git提交: 仅中文; 格式"类型: 简短描述", 类型: feat/fix/refactor/docs/style/test/chore; 多行描述空行后写详细说明
|
||||
- 禁止创建git操作task
|
||||
- 积极使用subagents精心设计并行任务,节省上下文空间,加速任务执行
|
||||
- 优先使用提问工具对用户进行提问
|
||||
|
||||
rules:
|
||||
proposal:
|
||||
- 仔细审查每一个过往spec判断是否存在Modified Capabilities
|
||||
178
openspec/specs/anthropic-protocol-proxy/spec.md
Normal file
178
openspec/specs/anthropic-protocol-proxy/spec.md
Normal file
@@ -0,0 +1,178 @@
|
||||
# Anthropic 协议代理
|
||||
|
||||
## Purpose
|
||||
|
||||
TBD - 提供 Anthropic Messages API 的代理功能,通过协议转换实现与 OpenAI 兼容供应商的互操作
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: 支持 Anthropic Messages API 端点
|
||||
|
||||
网关 SHALL 提供 Anthropic Messages API 端点 `POST /v1/messages` 供外部应用调用。
|
||||
|
||||
#### Scenario: 成功的非流式请求
|
||||
|
||||
- **WHEN** 应用发送 POST 请求到 `/v1/messages`,携带有效的 Anthropic 请求格式(非流式)
|
||||
- **THEN** 网关 SHALL 将 Anthropic 请求转换为 OpenAI 格式
|
||||
- **THEN** 网关 SHALL 将转换后的请求转发到配置的供应商
|
||||
- **THEN** 网关 SHALL 将 OpenAI 响应转换回 Anthropic 格式
|
||||
- **THEN** 网关 SHALL 将转换后的响应返回给应用
|
||||
|
||||
#### Scenario: 成功的流式请求
|
||||
|
||||
- **WHEN** 应用发送 POST 请求到 `/v1/messages`,携带 `stream: true`
|
||||
- **THEN** 网关 SHALL 将 Anthropic 请求转换为 OpenAI 格式
|
||||
- **THEN** 网关 SHALL 将转换后的请求转发给供应商
|
||||
- **THEN** 网关 SHALL 将 OpenAI 流事件转换为 Anthropic 流事件
|
||||
- **THEN** 网关 SHALL 使用 SSE 格式将转换后的事件流式返回给应用
|
||||
|
||||
### Requirement: 将 Anthropic 请求转换为 OpenAI 格式
|
||||
|
||||
网关 SHALL 将 Anthropic Messages API 请求转换为 OpenAI Chat Completions API 格式。
|
||||
|
||||
#### Scenario: System 消息转换
|
||||
|
||||
- **WHEN** Anthropic 请求包含 `system` 字段
|
||||
- **THEN** 网关 SHALL 将其转换为 `messages` 数组中 `role: "system"` 的消息
|
||||
|
||||
#### Scenario: Messages 转换
|
||||
|
||||
- **WHEN** Anthropic 请求包含 `messages` 数组
|
||||
- **THEN** 网关 SHALL 在转换后的 OpenAI 请求中保留这些消息
|
||||
- **THEN** 网关 SHALL 保留每条消息的 role 和 content
|
||||
|
||||
#### Scenario: Tools 转换
|
||||
|
||||
- **WHEN** Anthropic 请求包含带有 `input_schema` 的 `tools`
|
||||
- **THEN** 网关 SHALL 将每个工具转换为 OpenAI 格式,使用 `function.parameters` 替代 `input_schema`
|
||||
- **THEN** 网关 SHALL 保留工具名称和描述
|
||||
|
||||
#### Scenario: Tool choice 转换
|
||||
|
||||
- **WHEN** Anthropic 请求包含 `type: "auto"` 的 `tool_choice`
|
||||
- **THEN** 网关 SHALL 将其转换为 OpenAI 格式的 `"auto"`
|
||||
- **WHEN** Anthropic 请求包含 `type: "any"` 的 `tool_choice`
|
||||
- **THEN** 网关 SHALL 将其转换为 OpenAI 格式的 `"auto"`
|
||||
- **WHEN** Anthropic 请求包含 `type: "tool"` 和 `name` 的 `tool_choice`
|
||||
- **THEN** 网关 SHALL 将其转换为 OpenAI 格式的 `{"type": "function", "function": {"name": <name>}}`
|
||||
|
||||
#### Scenario: Tool result 转换
|
||||
|
||||
- **WHEN** Anthropic 请求包含用户消息,其 `content` 数组包含 `type: "tool_result"` 块
|
||||
- **THEN** 网关 SHALL 将每个工具结果转换为 `role: "tool"` 的消息
|
||||
- **THEN** 网关 SHALL 从 `tool_use_id` 设置 `tool_call_id`
|
||||
- **THEN** 网关 SHALL 保留 content
|
||||
|
||||
#### Scenario: Max tokens 处理
|
||||
|
||||
- **WHEN** Anthropic 请求包含 `max_tokens`
|
||||
- **THEN** 网关 SHALL 在 OpenAI 请求中包含它作为 `max_tokens`
|
||||
- **WHEN** Anthropic 请求不包含 `max_tokens`
|
||||
- **THEN** 网关 SHALL 设置默认值(4096)以满足 Anthropic 的要求
|
||||
|
||||
### Requirement: 将 OpenAI 响应转换为 Anthropic 格式
|
||||
|
||||
网关 SHALL 将 OpenAI Chat Completions API 响应转换为 Anthropic Messages API 格式。
|
||||
|
||||
#### Scenario: Content 转换
|
||||
|
||||
- **WHEN** OpenAI 响应包含 `choices[0].message.content`
|
||||
- **THEN** 网关 SHALL 将其转换为 Anthropic 格式的 `content: [{"type": "text", "text": <content>}]`
|
||||
|
||||
#### Scenario: Tool calls 转换
|
||||
|
||||
- **WHEN** OpenAI 响应包含 `choices[0].message.tool_calls`
|
||||
- **THEN** 网关 SHALL 将每个工具调用转换为 `type: "tool_use"` 的内容块
|
||||
- **THEN** 网关 SHALL 从 `tool_calls[].id` 设置 `id`
|
||||
- **THEN** 网关 SHALL 从 `tool_calls[].function.name` 设置 `name`
|
||||
- **THEN** 网关 SHALL 解析 `arguments` JSON 字符串并将其设置为 `input` 对象
|
||||
|
||||
#### Scenario: Finish reason 转换
|
||||
|
||||
- **WHEN** OpenAI 响应的 `finish_reason` 为 `"stop"`
|
||||
- **THEN** 网关 SHALL 在 Anthropic 响应中设置 `stop_reason: "end_turn"`
|
||||
- **WHEN** OpenAI 响应的 `finish_reason` 为 `"tool_calls"`
|
||||
- **THEN** 网关 SHALL 在 Anthropic 响应中设置 `stop_reason: "tool_use"`
|
||||
|
||||
#### Scenario: Usage 转换
|
||||
|
||||
- **WHEN** OpenAI 响应包含带有 `prompt_tokens` 和 `completion_tokens` 的 `usage`
|
||||
- **THEN** 网关 SHALL 转换为 Anthropic 格式,使用 `input_tokens` 和 `output_tokens`
|
||||
|
||||
### Requirement: 转换流式事件
|
||||
|
||||
网关 SHALL 实时将 OpenAI 流事件转换为 Anthropic 流事件。
|
||||
|
||||
#### Scenario: Message start 事件
|
||||
|
||||
- **WHEN** 网关开始流式传输 Anthropic 响应
|
||||
- **THEN** 网关 SHALL 发送带有消息元数据的 `message_start` 事件
|
||||
|
||||
#### Scenario: Content block start 事件
|
||||
|
||||
- **WHEN** OpenAI 流开始返回内容
|
||||
- **THEN** 网关 SHALL 发送带有 `type: "text"` 的 `content_block_start` 事件
|
||||
|
||||
#### Scenario: Content delta 事件
|
||||
|
||||
- **WHEN** OpenAI 流发送带有内容的 delta
|
||||
- **THEN** 网关 SHALL 发送带有 `type: "text_delta"` 的 `content_block_delta` 事件,包含文本
|
||||
|
||||
#### Scenario: Tool use 流式传输
|
||||
|
||||
- **WHEN** OpenAI 流发送工具调用 delta
|
||||
- **THEN** 网关 SHALL 缓冲 `arguments` 块
|
||||
- **THEN** 网关 SHALL 在工具调用开始时发送带有 `type: "tool_use"` 的 `content_block_start`
|
||||
- **THEN** 网关 SHALL 发送带有部分 JSON 的 `input_delta` 事件
|
||||
|
||||
#### Scenario: Content block stop 事件
|
||||
|
||||
- **WHEN** 内容块完成
|
||||
- **THEN** 网关 SHALL 发送 `content_block_stop` 事件
|
||||
|
||||
#### Scenario: Message stop 事件
|
||||
|
||||
- **WHEN** OpenAI 流完成
|
||||
- **THEN** 网关 SHALL 发送 `message_stop` 事件
|
||||
|
||||
### Requirement: 支持 Anthropic 特有功能
|
||||
|
||||
网关 SHALL 支持映射到 OpenAI 能力的 Anthropic 特有功能。
|
||||
|
||||
#### Scenario: System prompt 作为独立字段
|
||||
|
||||
- **WHEN** Anthropic 请求包含 `system` 字段
|
||||
- **THEN** 网关 SHALL 将其作为 OpenAI 格式的 system 消息处理
|
||||
|
||||
#### Scenario: 必需的 max_tokens
|
||||
|
||||
- **WHEN** 收到 Anthropic 请求
|
||||
- **THEN** 网关 SHALL 确保 `max_tokens` 存在(如果未提供则使用默认值)
|
||||
|
||||
### Requirement: 处理纯文本内容
|
||||
|
||||
网关 SHALL 在 Anthropic 请求和响应中支持纯文本内容。
|
||||
|
||||
#### Scenario: 消息中的文本内容
|
||||
|
||||
- **WHEN** Anthropic 请求在消息中包含文本内容
|
||||
- **THEN** 网关 SHALL 正确处理和转发文本内容
|
||||
|
||||
#### Scenario: 拒绝多模态内容
|
||||
|
||||
- **WHEN** Anthropic 请求包含多模态内容(图片、文档)
|
||||
- **THEN** 网关 SHALL 返回错误,指示 MVP 不支持多模态内容
|
||||
|
||||
### Requirement: 保留请求元数据
|
||||
|
||||
网关 SHALL 在转换过程中保留请求元数据。
|
||||
|
||||
#### Scenario: 模型名称保留
|
||||
|
||||
- **WHEN** Anthropic 请求指定模型名称
|
||||
- **THEN** 网关 SHALL 在转换后的 OpenAI 请求中保留模型名称
|
||||
|
||||
#### Scenario: 自定义参数
|
||||
|
||||
- **WHEN** Anthropic 请求包含自定义参数(temperature, top_p 等)
|
||||
- **THEN** 网关 SHALL 在转换后的请求中保留这些参数
|
||||
208
openspec/specs/frontend-config-ui/spec.md
Normal file
208
openspec/specs/frontend-config-ui/spec.md
Normal file
@@ -0,0 +1,208 @@
|
||||
# 前端配置界面
|
||||
|
||||
## Purpose
|
||||
|
||||
TBD - 提供供应商、模型配置和用量统计的前端管理界面
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: 提供供应商管理页面
|
||||
|
||||
前端 SHALL 提供用于管理供应商配置的网页。
|
||||
|
||||
#### Scenario: 显示供应商列表
|
||||
|
||||
- **WHEN** 加载供应商管理页面
|
||||
- **THEN** 前端 SHALL 显示所有已配置供应商的列表
|
||||
- **THEN** 每个供应商 SHALL 显示 id, name, base_url 和 enabled 状态
|
||||
- **THEN** API Key SHALL 被掩码
|
||||
|
||||
#### Scenario: 添加新供应商
|
||||
|
||||
- **WHEN** 用户点击"添加供应商"按钮
|
||||
- **THEN** 前端 SHALL 显示输入供应商详情的表单
|
||||
- **THEN** 表单 SHALL 包含 id, name, api_key, base_url 字段
|
||||
- **WHEN** 用户提交包含有效数据的表单
|
||||
- **THEN** 前端 SHALL 向 `/api/providers` 发送 POST 请求
|
||||
- **THEN** 前端 SHALL 刷新供应商列表
|
||||
|
||||
#### Scenario: 编辑现有供应商
|
||||
|
||||
- **WHEN** 用户点击供应商的"编辑"按钮
|
||||
- **THEN** 前端 SHALL 显示预填充供应商当前数据的表单
|
||||
- **WHEN** 用户提交包含更新数据的表单
|
||||
- **THEN** 前端 SHALL 向 `/api/providers/:id` 发送 PUT 请求
|
||||
- **THEN** 前端 SHALL 刷新供应商列表
|
||||
|
||||
#### Scenario: 删除供应商
|
||||
|
||||
- **WHEN** 用户点击供应商的"删除"按钮
|
||||
- **THEN** 前端 SHALL 提示确认
|
||||
- **WHEN** 用户确认删除
|
||||
- **THEN** 前端 SHALL 向 `/api/providers/:id` 发送 DELETE 请求
|
||||
- **THEN** 前端 SHALL 刷新供应商列表
|
||||
|
||||
### Requirement: 提供模型管理界面
|
||||
|
||||
前端 SHALL 在供应商页面中提供管理模型配置的界面。
|
||||
|
||||
#### Scenario: 显示供应商的模型
|
||||
|
||||
- **WHEN** 选择或展开供应商
|
||||
- **THEN** 前端 SHALL 显示该供应商的模型列表
|
||||
- **THEN** 每个模型 SHALL 显示 model_name 和 enabled 状态
|
||||
|
||||
#### Scenario: 为供应商添加模型
|
||||
|
||||
- **WHEN** 用户点击供应商的"添加模型"
|
||||
- **THEN** 前端 SHALL 显示输入 model_name 的表单
|
||||
- **WHEN** 用户提交表单
|
||||
- **THEN** 前端 SHALL 向 `/api/models` 发送 POST 请求,携带 provider_id
|
||||
- **THEN** 前端 SHALL 刷新模型列表
|
||||
|
||||
#### Scenario: 编辑模型
|
||||
|
||||
- **WHEN** 用户点击模型的"编辑"
|
||||
- **THEN** 前端 SHALL 显示编辑 model_name 的表单
|
||||
- **WHEN** 用户提交表单
|
||||
- **THEN** 前端 SHALL 向 `/api/models/:id` 发送 PUT 请求
|
||||
- **THEN** 前端 SHALL 刷新模型列表
|
||||
|
||||
#### Scenario: 删除模型
|
||||
|
||||
- **WHEN** 用户点击模型的"删除"
|
||||
- **THEN** 前端 SHALL 提示确认
|
||||
- **WHEN** 用户确认删除
|
||||
- **THEN** 前端 SHALL 向 `/api/models/:id` 发送 DELETE 请求
|
||||
- **THEN** 前端 SHALL 刷新模型列表
|
||||
|
||||
### Requirement: 提供统计查看页面
|
||||
|
||||
前端 SHALL 提供查看用量统计的页面。
|
||||
|
||||
#### Scenario: 显示统计概览
|
||||
|
||||
- **WHEN** 加载统计页面
|
||||
- **THEN** 前端 SHALL 显示所有供应商和模型的统计
|
||||
- **THEN** 前端 SHALL 按供应商和模型分组显示请求计数
|
||||
|
||||
#### Scenario: 按供应商过滤统计
|
||||
|
||||
- **WHEN** 用户从下拉菜单选择供应商
|
||||
- **THEN** 前端 SHALL 过滤统计,仅显示该供应商的数据
|
||||
|
||||
#### Scenario: 按模型过滤统计
|
||||
|
||||
- **WHEN** 用户从下拉菜单选择模型
|
||||
- **THEN** 前端 SHALL 过滤统计,仅显示该模型的数据
|
||||
|
||||
#### Scenario: 按日期范围过滤统计
|
||||
|
||||
- **WHEN** 用户选择开始和结束日期
|
||||
- **THEN** 前端 SHALL 过滤统计,仅显示该范围内的数据
|
||||
|
||||
### Requirement: 优雅处理 API 错误
|
||||
|
||||
前端 SHALL 处理 API 错误并显示用户友好的消息。
|
||||
|
||||
#### Scenario: API 请求失败
|
||||
|
||||
- **WHEN** API 请求失败(网络错误、4xx、5xx)
|
||||
- **THEN** 前端 SHALL 向用户显示错误消息
|
||||
- **THEN** 错误消息 SHALL 具有描述性和可操作性
|
||||
|
||||
#### Scenario: 验证错误
|
||||
|
||||
- **WHEN** 用户提交包含无效数据的表单
|
||||
- **THEN** 前端 SHALL 在相关字段旁显示验证错误
|
||||
- **THEN** 前端 SHALL 阻止表单提交
|
||||
|
||||
### Requirement: 提供响应式布局
|
||||
|
||||
前端 SHALL 提供适应不同屏幕尺寸的响应式布局。
|
||||
|
||||
#### Scenario: 桌面布局
|
||||
|
||||
- **WHEN** 在桌面屏幕上查看前端
|
||||
- **THEN** 布局 SHALL 使用多列设计以高效利用空间
|
||||
|
||||
#### Scenario: 移动布局
|
||||
|
||||
- **WHEN** 在移动屏幕上查看前端
|
||||
- **THEN** 布局 SHALL 适应为单列设计
|
||||
- **THEN** 所有功能 SHALL 保持可访问
|
||||
|
||||
### Requirement: 使用无组件库的最小 UI
|
||||
|
||||
前端 SHALL 使用自定义组件,不使用外部 UI 库。
|
||||
|
||||
#### Scenario: 自定义组件
|
||||
|
||||
- **WHEN** 实现前端
|
||||
- **THEN** 它 SHALL 使用自定义 HTML/CSS 组件
|
||||
- **THEN** 它 SHALL NOT 使用外部 UI 库,如 Ant Design、Material-UI 或 shadcn/ui
|
||||
|
||||
#### Scenario: SCSS 样式
|
||||
|
||||
- **WHEN** 编写样式
|
||||
- **THEN** 前端 SHALL 使用 SCSS 进行样式设计
|
||||
- **THEN** 样式 SHALL 有组织且可维护
|
||||
|
||||
### Requirement: 提供导航
|
||||
|
||||
前端 SHALL 在不同页面间提供导航。
|
||||
|
||||
#### Scenario: 导航到供应商页面
|
||||
|
||||
- **WHEN** 用户点击导航中的"供应商"
|
||||
- **THEN** 前端 SHALL 导航到供应商管理页面
|
||||
|
||||
#### Scenario: 导航到统计页面
|
||||
|
||||
- **WHEN** 用户点击导航中的"统计"
|
||||
- **THEN** 前端 SHALL 导航到统计查看页面
|
||||
|
||||
### Requirement: 使用 React 和 TypeScript
|
||||
|
||||
前端 SHALL 使用 React 和 TypeScript 实现。
|
||||
|
||||
#### Scenario: TypeScript 使用
|
||||
|
||||
- **WHEN** 编写前端代码
|
||||
- **THEN** 它 SHALL 使用 TypeScript 提供类型安全
|
||||
- **THEN** 所有组件和函数 SHALL 具有适当的类型定义
|
||||
|
||||
#### Scenario: React 组件
|
||||
|
||||
- **WHEN** 实现 UI
|
||||
- **THEN** 它 SHALL 使用 React 函数组件
|
||||
- **THEN** 它 SHALL 使用 React hooks 进行状态管理
|
||||
|
||||
### Requirement: 使用 Vite 构建
|
||||
|
||||
前端 SHALL 使用 Vite 作为构建工具。
|
||||
|
||||
#### Scenario: 开发服务器
|
||||
|
||||
- **WHEN** 在开发模式下启动前端
|
||||
- **THEN** Vite SHALL 使用热模块替换服务应用
|
||||
|
||||
#### Scenario: 生产构建
|
||||
|
||||
- **WHEN** 为生产构建前端
|
||||
- **THEN** Vite SHALL 生成优化的静态文件
|
||||
|
||||
### Requirement: 与后端 API 通信
|
||||
|
||||
前端 SHALL 使用 fetch 或类似方法与后端 API 通信。
|
||||
|
||||
#### Scenario: API 基础 URL 配置
|
||||
|
||||
- **WHEN** 前端发起 API 请求
|
||||
- **THEN** 它 SHALL 使用配置的后端 API 基础 URL(默认:http://localhost:9826)
|
||||
|
||||
#### Scenario: API 客户端封装
|
||||
|
||||
- **WHEN** 进行 API 调用
|
||||
- **THEN** 它们 SHALL 封装在专用的 API 客户端模块中
|
||||
- **THEN** 错误处理 SHALL 集中化
|
||||
174
openspec/specs/model-management/spec.md
Normal file
174
openspec/specs/model-management/spec.md
Normal file
@@ -0,0 +1,174 @@
|
||||
# 模型管理
|
||||
|
||||
## Purpose
|
||||
|
||||
TBD - 提供模型配置的管理功能,模型关联到供应商
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: 创建模型配置
|
||||
|
||||
网关 SHALL 允许为供应商创建新的模型配置。
|
||||
|
||||
#### Scenario: 使用有效数据创建模型
|
||||
|
||||
- **WHEN** 向 `/api/models` 发送 POST 请求,携带有效的模型数据(id, provider_id, model_name)
|
||||
- **THEN** 网关 SHALL 在数据库中创建新的模型记录
|
||||
- **THEN** 网关 SHALL 返回创建的模型,状态码为 201
|
||||
- **THEN** 模型 SHALL 默认启用
|
||||
|
||||
#### Scenario: 使用不存在的供应商创建模型
|
||||
|
||||
- **WHEN** 向 `/api/models` 发送 POST 请求,携带不存在的 provider_id
|
||||
- **THEN** 网关 SHALL 返回错误,状态码为 400 (Bad Request)
|
||||
- **THEN** 错误 SHALL 指示供应商不存在
|
||||
|
||||
#### Scenario: 使用重复 ID 创建模型
|
||||
|
||||
- **WHEN** 向 `/api/models` 发送 POST 请求,携带已存在的 ID
|
||||
- **THEN** 网关 SHALL 返回错误,状态码为 409 (Conflict)
|
||||
|
||||
#### Scenario: 创建模型时缺少必需字段
|
||||
|
||||
- **WHEN** 向 `/api/models` 发送 POST 请求,缺少必需字段(id, provider_id 或 model_name)
|
||||
- **THEN** 网关 SHALL 返回错误,状态码为 400 (Bad Request)
|
||||
- **THEN** 错误 SHALL 指示缺少哪些字段
|
||||
|
||||
### Requirement: 列出所有模型
|
||||
|
||||
网关 SHALL 允许获取所有模型配置。
|
||||
|
||||
#### Scenario: 成功列出模型
|
||||
|
||||
- **WHEN** 向 `/api/models` 发送 GET 请求
|
||||
- **THEN** 网关 SHALL 返回所有模型的列表
|
||||
- **THEN** 每个模型 SHALL 包含 id, provider_id, model_name, enabled, created_at
|
||||
|
||||
#### Scenario: 列出模型时为空
|
||||
|
||||
- **WHEN** 向 `/api/models` 发送 GET 请求,且不存在模型
|
||||
- **THEN** 网关 SHALL 返回空列表
|
||||
|
||||
### Requirement: 按供应商列出模型
|
||||
|
||||
网关 SHALL 允许获取特定供应商的模型。
|
||||
|
||||
#### Scenario: 列出存在供应商的模型
|
||||
|
||||
- **WHEN** 向 `/api/models?provider_id=<provider_id>` 发送 GET 请求
|
||||
- **THEN** 网关 SHALL 返回指定供应商的模型列表
|
||||
|
||||
#### Scenario: 列出不存在供应商的模型
|
||||
|
||||
- **WHEN** 向 `/api/models?provider_id=<non_existent_id>` 发送 GET 请求
|
||||
- **THEN** 网关 SHALL 返回空列表
|
||||
|
||||
### Requirement: 获取特定模型
|
||||
|
||||
网关 SHALL 允许通过 ID 获取特定模型。
|
||||
|
||||
#### Scenario: 获取存在的模型
|
||||
|
||||
- **WHEN** 向 `/api/models/:id` 发送 GET 请求,携带有效的模型 ID
|
||||
- **THEN** 网关 SHALL 返回模型详情
|
||||
|
||||
#### Scenario: 获取不存在的模型
|
||||
|
||||
- **WHEN** 向 `/api/models/:id` 发送 GET 请求,携带不存在的 ID
|
||||
- **THEN** 网关 SHALL 返回错误,状态码为 404 (Not Found)
|
||||
|
||||
### Requirement: 更新模型配置
|
||||
|
||||
网关 SHALL 允许更新现有模型配置。
|
||||
|
||||
#### Scenario: 使用有效数据更新模型
|
||||
|
||||
- **WHEN** 向 `/api/models/:id` 发送 PUT 请求,携带有效的模型数据
|
||||
- **THEN** 网关 SHALL 更新数据库中的模型记录
|
||||
- **THEN** 网关 SHALL 返回更新后的模型
|
||||
|
||||
#### Scenario: 更新不存在的模型
|
||||
|
||||
- **WHEN** 向 `/api/models/:id` 发送 PUT 请求,携带不存在的 ID
|
||||
- **THEN** 网关 SHALL 返回错误,状态码为 404 (Not Found)
|
||||
|
||||
#### Scenario: 更新模型供应商
|
||||
|
||||
- **WHEN** 向 `/api/models/:id` 发送 PUT 请求,携带新的 provider_id
|
||||
- **THEN** 网关 SHALL 验证新供应商是否存在
|
||||
- **THEN** 网关 SHALL 更新模型的供应商关联
|
||||
|
||||
#### Scenario: 部分更新
|
||||
|
||||
- **WHEN** 向 `/api/models/:id` 发送 PUT 请求,仅包含部分字段
|
||||
- **THEN** 网关 SHALL 仅更新提供的字段
|
||||
- **THEN** 网关 SHALL 保留未更改的字段
|
||||
|
||||
### Requirement: 删除模型配置
|
||||
|
||||
网关 SHALL 允许删除模型配置。
|
||||
|
||||
#### Scenario: 删除存在的模型
|
||||
|
||||
- **WHEN** 向 `/api/models/:id` 发送 DELETE 请求,携带有效的模型 ID
|
||||
- **THEN** 网关 SHALL 删除模型记录
|
||||
- **THEN** 网关 SHALL 返回状态码 204 (No Content)
|
||||
|
||||
#### Scenario: 删除不存在的模型
|
||||
|
||||
- **WHEN** 向 `/api/models/:id` 发送 DELETE 请求,携带不存在的 ID
|
||||
- **THEN** 网关 SHALL 返回错误,状态码为 404 (Not Found)
|
||||
|
||||
### Requirement: 启用和禁用模型
|
||||
|
||||
网关 SHALL 支持启用和禁用模型。
|
||||
|
||||
#### Scenario: 禁用模型
|
||||
|
||||
- **WHEN** 模型的 `enabled` 字段设置为 false
|
||||
- **THEN** 网关 SHALL 不向该模型路由请求
|
||||
- **THEN** 模型 SHALL 保留在数据库中
|
||||
|
||||
#### Scenario: 启用模型
|
||||
|
||||
- **WHEN** 已禁用模型的 `enabled` 字段设置为 true
|
||||
- **THEN** 网关 SHALL 恢复向该模型路由请求
|
||||
|
||||
### Requirement: 验证模型配置
|
||||
|
||||
网关 SHALL 验证模型配置数据。
|
||||
|
||||
#### Scenario: 验证供应商存在
|
||||
|
||||
- **WHEN** 创建或更新模型时携带 provider_id
|
||||
- **THEN** 网关 SHALL 验证供应商存在于数据库中
|
||||
|
||||
#### Scenario: 验证必需字段
|
||||
|
||||
- **WHEN** 创建或更新模型
|
||||
- **THEN** 网关 SHALL 验证 id, provider_id 和 model_name 存在且非空
|
||||
|
||||
### Requirement: 支持透明的模型名称
|
||||
|
||||
网关 SHALL 使用模型名称透明传输,不做转换。
|
||||
|
||||
#### Scenario: 模型名称保留
|
||||
|
||||
- **WHEN** 模型配置了 model_name
|
||||
- **THEN** 网关 SHALL 在路由请求时使用该确切名称
|
||||
- **THEN** 网关 SHALL 不修改或转换模型名称
|
||||
|
||||
#### Scenario: 不同供应商的同名模型
|
||||
|
||||
- **WHEN** 多个供应商拥有相同 model_name 的模型
|
||||
- **THEN** 每个模型 SHALL 通过其唯一 ID 和 provider_id 区分
|
||||
- **THEN** 网关 SHALL 基于模型名称和供应商关联的组合进行路由
|
||||
|
||||
### Requirement: 随供应商级联删除
|
||||
|
||||
网关 SHALL 在删除关联供应商时删除模型。
|
||||
|
||||
#### Scenario: 供应商删除级联到模型
|
||||
|
||||
- **WHEN** 供应商被删除
|
||||
- **THEN** 该供应商关联的所有模型 SHALL 自动删除
|
||||
129
openspec/specs/openai-protocol-proxy/spec.md
Normal file
129
openspec/specs/openai-protocol-proxy/spec.md
Normal file
@@ -0,0 +1,129 @@
|
||||
# OpenAI 协议代理
|
||||
|
||||
## Purpose
|
||||
|
||||
TBD - 提供 OpenAI Chat Completions API 的代理功能
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: 支持 OpenAI Chat Completions API 端点
|
||||
|
||||
网关 SHALL 提供 OpenAI Chat Completions API 端点 `POST /v1/chat/completions` 供外部应用调用。
|
||||
|
||||
#### Scenario: 成功的非流式请求
|
||||
|
||||
- **WHEN** 应用发送 POST 请求到 `/v1/chat/completions`,携带有效的 OpenAI 请求格式(非流式)
|
||||
- **THEN** 网关 SHALL 将请求转发到配置的供应商
|
||||
- **THEN** 网关 SHALL 将供应商的响应以 OpenAI 格式返回给应用
|
||||
|
||||
#### Scenario: 成功的流式请求
|
||||
|
||||
- **WHEN** 应用发送 POST 请求到 `/v1/chat/completions`,携带 `stream: true`
|
||||
- **THEN** 网关 SHALL 将请求转发到配置的供应商
|
||||
- **THEN** 网关 SHALL 使用 SSE 格式将响应流式返回给应用
|
||||
- **THEN** 网关 SHALL 在流完成时发送 `data: [DONE]`
|
||||
|
||||
### Requirement: 支持 Function Calling
|
||||
|
||||
网关 SHALL 在非流式和流式模式下都支持 OpenAI Function Calling。
|
||||
|
||||
#### Scenario: 非流式函数调用
|
||||
|
||||
- **WHEN** 应用发送包含 `tools` 定义的请求
|
||||
- **AND** 供应商返回包含 `tool_calls` 的响应
|
||||
- **THEN** 网关 SHALL 在响应中原样转发 `tool_calls`
|
||||
|
||||
#### Scenario: 流式函数调用
|
||||
|
||||
- **WHEN** 应用发送包含 `tools` 定义的流式请求
|
||||
- **AND** 供应商在 delta 块中流式返回 `tool_calls`
|
||||
- **THEN** 网关 SHALL 将 `tool_calls` 块流式发送给应用
|
||||
- **THEN** 网关 SHALL 在完成时设置 `finish_reason: "tool_calls"`
|
||||
|
||||
#### Scenario: 工具结果提交
|
||||
|
||||
- **WHEN** 应用发送包含 `role: "tool"` 消息的后续请求,携带函数结果
|
||||
- **THEN** 网关 SHALL 将工具结果原样转发给供应商
|
||||
|
||||
### Requirement: 根据模型名称路由请求
|
||||
|
||||
网关 SHALL 根据请求中的 `model` 字段将请求路由到相应的供应商。
|
||||
|
||||
#### Scenario: 有效模型路由
|
||||
|
||||
- **WHEN** 请求包含存在于配置模型中的 `model` 字段
|
||||
- **AND** 该模型已启用
|
||||
- **THEN** 网关 SHALL 将请求路由到该模型关联的供应商
|
||||
|
||||
#### Scenario: 模型未找到
|
||||
|
||||
- **WHEN** 请求包含不存在于配置模型中的 `model` 字段
|
||||
- **THEN** 网关 SHALL 返回带有适当错误消息的错误响应
|
||||
|
||||
#### Scenario: 模型已禁用
|
||||
|
||||
- **WHEN** 请求包含已禁用模型的 `model` 字段
|
||||
- **THEN** 网关 SHALL 返回错误响应,指示模型不可用
|
||||
|
||||
### Requirement: 对 OpenAI 兼容供应商透明代理
|
||||
|
||||
网关 SHALL 对 OpenAI 兼容供应商的请求和响应进行透明转发,不做修改。
|
||||
|
||||
#### Scenario: 请求转发
|
||||
|
||||
- **WHEN** 网关收到 OpenAI 协议请求
|
||||
- **AND** 目标供应商是 OpenAI 兼容的
|
||||
- **THEN** 网关 SHALL 将请求体原样转发给供应商
|
||||
- **THEN** 网关 SHALL 在 Authorization 头中设置供应商的 API Key
|
||||
- **THEN** 网关 SHALL 使用供应商的 base URL
|
||||
|
||||
#### Scenario: 响应转发
|
||||
|
||||
- **WHEN** 供应商返回响应
|
||||
- **THEN** 网关 SHALL 将响应体原样返回给应用
|
||||
- **THEN** 网关 SHALL 保留所有响应头和状态码
|
||||
|
||||
### Requirement: 处理供应商错误
|
||||
|
||||
网关 SHALL 将供应商错误透明返回给应用。
|
||||
|
||||
#### Scenario: 供应商返回错误
|
||||
|
||||
- **WHEN** 供应商返回错误响应(4xx 或 5xx)
|
||||
- **THEN** 网关 SHALL 将相同的错误响应返回给应用
|
||||
- **THEN** 网关 SHALL 保留错误消息和状态码
|
||||
|
||||
#### Scenario: 供应商超时
|
||||
|
||||
- **WHEN** 供应商在超时时间内未响应
|
||||
- **THEN** 网关 SHALL 向应用返回超时错误
|
||||
|
||||
#### Scenario: 供应商连接失败
|
||||
|
||||
- **WHEN** 网关无法连接到供应商
|
||||
- **THEN** 网关 SHALL 向应用返回连接错误
|
||||
|
||||
### Requirement: 支持标准 OpenAI 请求字段
|
||||
|
||||
网关 SHALL 支持所有标准 OpenAI Chat Completions API 请求字段。
|
||||
|
||||
#### Scenario: 支持标准字段
|
||||
|
||||
- **WHEN** 请求包含标准字段(model, messages, temperature, max_tokens, top_p, frequency_penalty, presence_penalty, stop, n, stream, tools, tool_choice, user)
|
||||
- **THEN** 网关 SHALL 接受并将所有字段转发给供应商
|
||||
|
||||
### Requirement: 维护流式连接稳定性
|
||||
|
||||
网关 SHALL 维护稳定的流式连接并优雅处理中断。
|
||||
|
||||
#### Scenario: 流中断
|
||||
|
||||
- **WHEN** 供应商流在传输过程中中断
|
||||
- **THEN** 网关 SHALL 优雅关闭客户端连接
|
||||
- **THEN** 网关 SHALL 记录中断日志以便调试
|
||||
|
||||
#### Scenario: 客户端提前断开
|
||||
|
||||
- **WHEN** 客户端在流完成前断开连接
|
||||
- **THEN** 网关 SHALL 取消供应商请求
|
||||
- **THEN** 网关 SHALL 释放相关资源
|
||||
151
openspec/specs/provider-management/spec.md
Normal file
151
openspec/specs/provider-management/spec.md
Normal file
@@ -0,0 +1,151 @@
|
||||
# 供应商管理
|
||||
|
||||
## Purpose
|
||||
|
||||
TBD - 提供供应商配置的管理功能(创建、查询、更新、删除)
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: 创建供应商配置
|
||||
|
||||
网关 SHALL 允许通过管理 API 创建新的供应商配置。
|
||||
|
||||
#### Scenario: 使用有效数据创建供应商
|
||||
|
||||
- **WHEN** 向 `/api/providers` 发送 POST 请求,携带有效的供应商数据(id, name, api_key, base_url)
|
||||
- **THEN** 网关 SHALL 在数据库中创建新的供应商记录
|
||||
- **THEN** 网关 SHALL 返回创建的供应商,状态码为 201
|
||||
- **THEN** 供应商 SHALL 默认启用
|
||||
|
||||
#### Scenario: 使用重复 ID 创建供应商
|
||||
|
||||
- **WHEN** 向 `/api/providers` 发送 POST 请求,携带已存在的 ID
|
||||
- **THEN** 网关 SHALL 返回错误,状态码为 409 (Conflict)
|
||||
|
||||
#### Scenario: 创建供应商时缺少必需字段
|
||||
|
||||
- **WHEN** 向 `/api/providers` 发送 POST 请求,缺少必需字段(id, name, api_key 或 base_url)
|
||||
- **THEN** 网关 SHALL 返回错误,状态码为 400 (Bad Request)
|
||||
- **THEN** 错误 SHALL 指示缺少哪些字段
|
||||
|
||||
### Requirement: 列出所有供应商
|
||||
|
||||
网关 SHALL 允许获取所有供应商配置。
|
||||
|
||||
#### Scenario: 成功列出供应商
|
||||
|
||||
- **WHEN** 向 `/api/providers` 发送 GET 请求
|
||||
- **THEN** 网关 SHALL 返回所有供应商的列表
|
||||
- **THEN** 每个供应商 SHALL 包含 id, name, api_key(已掩码), base_url, enabled, created_at, updated_at
|
||||
- **THEN** api_key SHALL 被掩码(仅显示最后 4 个字符)
|
||||
|
||||
#### Scenario: 列出供应商时为空
|
||||
|
||||
- **WHEN** 向 `/api/providers` 发送 GET 请求,且不存在供应商
|
||||
- **THEN** 网关 SHALL 返回空列表
|
||||
|
||||
### Requirement: 获取特定供应商
|
||||
|
||||
网关 SHALL 允许通过 ID 获取特定供应商。
|
||||
|
||||
#### Scenario: 获取存在的供应商
|
||||
|
||||
- **WHEN** 向 `/api/providers/:id` 发送 GET 请求,携带有效的供应商 ID
|
||||
- **THEN** 网关 SHALL 返回供应商详情
|
||||
- **THEN** api_key SHALL 被掩码
|
||||
|
||||
#### Scenario: 获取不存在的供应商
|
||||
|
||||
- **WHEN** 向 `/api/providers/:id` 发送 GET 请求,携带不存在的 ID
|
||||
- **THEN** 网关 SHALL 返回错误,状态码为 404 (Not Found)
|
||||
|
||||
### Requirement: 更新供应商配置
|
||||
|
||||
网关 SHALL 允许更新现有供应商配置。
|
||||
|
||||
#### Scenario: 使用有效数据更新供应商
|
||||
|
||||
- **WHEN** 向 `/api/providers/:id` 发送 PUT 请求,携带有效的供应商数据
|
||||
- **THEN** 网关 SHALL 更新数据库中的供应商记录
|
||||
- **THEN** 网关 SHALL 返回更新后的供应商
|
||||
- **THEN** updated_at 时间戳 SHALL 被更新
|
||||
|
||||
#### Scenario: 更新不存在的供应商
|
||||
|
||||
- **WHEN** 向 `/api/providers/:id` 发送 PUT 请求,携带不存在的 ID
|
||||
- **THEN** 网关 SHALL 返回错误,状态码为 404 (Not Found)
|
||||
|
||||
#### Scenario: 部分更新
|
||||
|
||||
- **WHEN** 向 `/api/providers/:id` 发送 PUT 请求,仅包含部分字段
|
||||
- **THEN** 网关 SHALL 仅更新提供的字段
|
||||
- **THEN** 网关 SHALL 保留未更改的字段
|
||||
|
||||
### Requirement: 删除供应商配置
|
||||
|
||||
网关 SHALL 允许删除供应商配置。
|
||||
|
||||
#### Scenario: 删除存在的供应商
|
||||
|
||||
- **WHEN** 向 `/api/providers/:id` 发送 DELETE 请求,携带有效的供应商 ID
|
||||
- **THEN** 网关 SHALL 删除供应商记录
|
||||
- **THEN** 网关 SHALL 删除所有关联的模型(CASCADE)
|
||||
- **THEN** 网关 SHALL 返回状态码 204 (No Content)
|
||||
|
||||
#### Scenario: 删除不存在的供应商
|
||||
|
||||
- **WHEN** 向 `/api/providers/:id` 发送 DELETE 请求,携带不存在的 ID
|
||||
- **THEN** 网关 SHALL 返回错误,状态码为 404 (Not Found)
|
||||
|
||||
### Requirement: 启用和禁用供应商
|
||||
|
||||
网关 SHALL 支持启用和禁用供应商。
|
||||
|
||||
#### Scenario: 禁用供应商
|
||||
|
||||
- **WHEN** 供应商的 `enabled` 字段设置为 false
|
||||
- **THEN** 网关 SHALL 不向该供应商路由请求
|
||||
- **THEN** 供应商 SHALL 保留在数据库中
|
||||
|
||||
#### Scenario: 启用供应商
|
||||
|
||||
- **WHEN** 已禁用供应商的 `enabled` 字段设置为 true
|
||||
- **THEN** 网关 SHALL 恢复向该供应商路由请求
|
||||
|
||||
### Requirement: 验证供应商配置
|
||||
|
||||
网关 SHALL 验证供应商配置数据。
|
||||
|
||||
#### Scenario: 验证 base_url 格式
|
||||
|
||||
- **WHEN** 创建或更新供应商时使用无效的 base_url 格式
|
||||
- **THEN** 网关 SHALL 返回错误,状态码为 400 (Bad Request)
|
||||
|
||||
#### Scenario: 验证必需字段
|
||||
|
||||
- **WHEN** 创建或更新供应商
|
||||
- **THEN** 网关 SHALL 验证 id, name, api_key 和 base_url 存在且非空
|
||||
|
||||
### Requirement: 安全存储供应商配置
|
||||
|
||||
网关 SHALL 安全存储供应商 API Key。
|
||||
|
||||
#### Scenario: 存储 API Key
|
||||
|
||||
- **WHEN** 创建或更新供应商时携带 API Key
|
||||
- **THEN** 网关 SHALL 将 API Key 存储在数据库中
|
||||
|
||||
#### Scenario: 在响应中掩码 API Key
|
||||
|
||||
- **WHEN** 在 API 响应中返回供应商数据
|
||||
- **THEN** API Key SHALL 被掩码(仅显示最后 4 个字符)
|
||||
|
||||
### Requirement: 仅支持 OpenAI 兼容供应商
|
||||
|
||||
网关 SHALL 在 MVP 中仅支持 OpenAI 兼容供应商。
|
||||
|
||||
#### Scenario: 供应商类型验证
|
||||
|
||||
- **WHEN** 创建供应商
|
||||
- **THEN** 供应商类型 SHALL 隐式设置为 "openai-compatible"
|
||||
- **THEN** MVP 中 SHALL 不支持其他供应商类型
|
||||
155
openspec/specs/usage-statistics/spec.md
Normal file
155
openspec/specs/usage-statistics/spec.md
Normal file
@@ -0,0 +1,155 @@
|
||||
# 用量统计
|
||||
|
||||
## Purpose
|
||||
|
||||
TBD - 提供请求用量统计的记录和查询功能
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: 记录请求统计
|
||||
|
||||
网关 SHALL 为每次 API 调用记录请求统计。
|
||||
|
||||
#### Scenario: 记录成功请求
|
||||
|
||||
- **WHEN** 请求成功转发到供应商
|
||||
- **THEN** 网关 SHALL 增加该供应商和模型的请求计数
|
||||
- **THEN** 网关 SHALL 记录当前日期的统计
|
||||
|
||||
#### Scenario: 记录流式请求
|
||||
|
||||
- **WHEN** 流式请求成功完成
|
||||
- **THEN** 网关 SHALL 增加该供应商和模型的请求计数
|
||||
- **THEN** 网关 SHALL 在流结束后记录统计
|
||||
|
||||
#### Scenario: 不记录失败请求
|
||||
|
||||
- **WHEN** 请求在到达供应商前失败(路由错误、验证错误)
|
||||
- **THEN** 网关 SHALL NOT 增加请求计数
|
||||
|
||||
#### Scenario: 记录供应商错误
|
||||
|
||||
- **WHEN** 请求到达供应商但供应商返回错误
|
||||
- **THEN** 网关 SHALL 仍然增加请求计数(请求已被处理)
|
||||
|
||||
### Requirement: 按供应商查询统计
|
||||
|
||||
网关 SHALL 允许按供应商过滤查询统计。
|
||||
|
||||
#### Scenario: 查询特定供应商的统计
|
||||
|
||||
- **WHEN** 向 `/api/stats?provider_id=<provider_id>` 发送 GET 请求
|
||||
- **THEN** 网关 SHALL 仅返回指定供应商的统计
|
||||
|
||||
#### Scenario: 查询不存在供应商的统计
|
||||
|
||||
- **WHEN** 向 `/api/stats?provider_id=<non_existent_id>` 发送 GET 请求
|
||||
- **THEN** 网关 SHALL 返回空结果或零计数
|
||||
|
||||
### Requirement: 按模型查询统计
|
||||
|
||||
网关 SHALL 允许按模型过滤查询统计。
|
||||
|
||||
#### Scenario: 查询特定模型的统计
|
||||
|
||||
- **WHEN** 向 `/api/stats?model_name=<model_name>` 发送 GET 请求
|
||||
- **THEN** 网关 SHALL 仅返回指定模型的统计
|
||||
|
||||
#### Scenario: 查询不存在模型的统计
|
||||
|
||||
- **WHEN** 向 `/api/stats?model_name=<non_existent_name>` 发送 GET 请求
|
||||
- **THEN** 网关 SHALL 返回空结果或零计数
|
||||
|
||||
### Requirement: 按日期范围查询统计
|
||||
|
||||
网关 SHALL 允许在日期范围内查询统计。
|
||||
|
||||
#### Scenario: 使用日期范围查询统计
|
||||
|
||||
- **WHEN** 向 `/api/stats?start=<start_date>&end=<end_date>` 发送 GET 请求
|
||||
- **THEN** 网关 SHALL 仅返回指定范围内的日期统计
|
||||
- **THEN** 日期格式 SHALL 为 YYYY-MM-DD
|
||||
|
||||
#### Scenario: 不使用日期范围查询统计
|
||||
|
||||
- **WHEN** 向 `/api/stats` 发送 GET 请求,不带 start 和 end 参数
|
||||
- **THEN** 网关 SHALL 返回所有可用日期的统计
|
||||
|
||||
#### Scenario: 仅使用开始日期查询统计
|
||||
|
||||
- **WHEN** 向 `/api/stats?start=<start_date>` 发送 GET 请求
|
||||
- **THEN** 网关 SHALL 返回从开始日期到当前日期的统计
|
||||
|
||||
#### Scenario: 仅使用结束日期查询统计
|
||||
|
||||
- **WHEN** 向 `/api/stats?end=<end_date>` 发送 GET 请求
|
||||
- **THEN** 网关 SHALL 返回从最早可用日期到结束日期的统计
|
||||
|
||||
### Requirement: 聚合统计
|
||||
|
||||
网关 SHALL 按日期聚合统计。
|
||||
|
||||
#### Scenario: 同一天多次请求
|
||||
|
||||
- **WHEN** 同一天对同一供应商和模型发起多次请求
|
||||
- **THEN** 网关 SHALL 为该天维护单条统计记录
|
||||
- **THEN** 请求计数 SHALL 为所有请求的总和
|
||||
|
||||
#### Scenario: 跨多天请求
|
||||
|
||||
- **WHEN** 跨不同天发起请求
|
||||
- **THEN** 网关 SHALL 为每一天维护独立的统计记录
|
||||
|
||||
### Requirement: 以结构化格式返回统计
|
||||
|
||||
网关 SHALL 以结构化 JSON 格式返回统计。
|
||||
|
||||
#### Scenario: 统计响应格式
|
||||
|
||||
- **WHEN** 查询统计
|
||||
- **THEN** 响应 SHALL 为统计对象数组
|
||||
- **THEN** 每个对象 SHALL 包含 provider_id, model_name, request_count 和 date
|
||||
|
||||
#### Scenario: 空统计
|
||||
|
||||
- **WHEN** 没有统计匹配查询条件
|
||||
- **THEN** 网关 SHALL 返回空数组
|
||||
|
||||
### Requirement: 支持并发统计记录
|
||||
|
||||
网关 SHALL 支持并发请求统计记录而无冲突。
|
||||
|
||||
#### Scenario: 并发请求
|
||||
|
||||
- **WHEN** 同时处理多个并发请求
|
||||
- **THEN** 网关 SHALL 正确为每个请求增加请求计数
|
||||
- **THEN** 不 SHALL 因并发写入而丢失统计
|
||||
|
||||
### Requirement: 仅将统计限制为请求计数
|
||||
|
||||
网关 SHALL 在 MVP 中仅记录请求计数,不记录其他指标。
|
||||
|
||||
#### Scenario: 仅请求计数
|
||||
|
||||
- **WHEN** 记录统计
|
||||
- **THEN** 网关 SHALL 仅跟踪请求数量
|
||||
- **THEN** 网关 SHALL NOT 在 MVP 中跟踪 token 使用、成本、延迟或其他指标
|
||||
|
||||
### Requirement: 为新组合初始化统计
|
||||
|
||||
网关 SHALL 为新的供应商-模型-日期组合自动创建统计记录。
|
||||
|
||||
#### Scenario: 组合的首次请求
|
||||
|
||||
- **WHEN** 在新日期首次对供应商-模型组合发起请求
|
||||
- **THEN** 网关 SHALL 创建新的统计记录,request_count = 1
|
||||
|
||||
### Requirement: 查询所有统计
|
||||
|
||||
网关 SHALL 允许不带过滤条件查询所有统计。
|
||||
|
||||
#### Scenario: 查询所有统计
|
||||
|
||||
- **WHEN** 向 `/api/stats` 发送 GET 请求,不带任何查询参数
|
||||
- **THEN** 网关 SHALL 返回所有可用统计
|
||||
- **THEN** 结果 SHALL 按日期排序(最近的在前)
|
||||
Reference in New Issue
Block a user