feat: Add GPT-5 Responses API support and model adapter system
- Implement model adapter factory for unified API handling - Add response state manager for conversation continuity - Support GPT-5 Responses API with continuation tokens - Add model capabilities type system - Include deployment guide and test infrastructure - Enhance error handling and debugging for model interactions
This commit is contained in:
parent
6cf566fb40
commit
02c7c31a31
185
DEPLOYMENT_GUIDE.md
Normal file
185
DEPLOYMENT_GUIDE.md
Normal file
@ -0,0 +1,185 @@
|
||||
# Kode Responses API Support - Deployment Guide
|
||||
|
||||
## 🚀 Overview
|
||||
|
||||
The new capability-based model system has been successfully implemented to support GPT-5 and other Responses API models. The system replaces hardcoded model detection with a flexible, extensible architecture.
|
||||
|
||||
## ✅ What's New
|
||||
|
||||
### 1. **Capability-Based Architecture**
|
||||
- Models are now defined by their capabilities rather than name-based detection
|
||||
- Automatic API selection (Responses API vs Chat Completions)
|
||||
- Seamless fallback mechanism for compatibility
|
||||
|
||||
### 2. **New Files Created**
|
||||
```
|
||||
src/
|
||||
├── types/modelCapabilities.ts # Type definitions
|
||||
├── constants/modelCapabilities.ts # Model capability registry
|
||||
├── services/
|
||||
│ ├── modelAdapterFactory.ts # Adapter factory
|
||||
│ └── adapters/ # Pure adapters
|
||||
│ ├── base.ts # Base adapter class
|
||||
│ ├── responsesAPI.ts # Responses API adapter
|
||||
│ └── chatCompletions.ts # Chat Completions adapter
|
||||
└── test/testAdapters.ts # Test suite
|
||||
```
|
||||
|
||||
### 3. **Supported Models**
|
||||
- **GPT-5 Series**: gpt-5, gpt-5-mini, gpt-5-nano
|
||||
- **GPT-4 Series**: gpt-4o, gpt-4o-mini, gpt-4-turbo, gpt-4
|
||||
- **Claude Series**: All Claude models
|
||||
- **O1 Series**: o1, o1-mini, o1-preview
|
||||
- **Future Models**: GPT-6, GLM-5, and more through configuration
|
||||
|
||||
## 🔧 How to Use
|
||||
|
||||
### Enable the New System
|
||||
|
||||
```bash
|
||||
# Enable new adapter system (default)
|
||||
export USE_NEW_ADAPTERS=true
|
||||
|
||||
# Use legacy system (fallback)
|
||||
export USE_NEW_ADAPTERS=false
|
||||
```
|
||||
|
||||
### Add Support for New Models
|
||||
|
||||
Edit `src/constants/modelCapabilities.ts`:
|
||||
|
||||
```typescript
|
||||
// Add your model to the registry
|
||||
export const MODEL_CAPABILITIES_REGISTRY: Record<string, ModelCapabilities> = {
|
||||
// ... existing models ...
|
||||
|
||||
'your-model-name': {
|
||||
apiArchitecture: {
|
||||
primary: 'responses_api', // or 'chat_completions'
|
||||
fallback: 'chat_completions' // optional
|
||||
},
|
||||
parameters: {
|
||||
maxTokensField: 'max_completion_tokens', // or 'max_tokens'
|
||||
supportsReasoningEffort: true,
|
||||
supportsVerbosity: true,
|
||||
temperatureMode: 'flexible' // or 'fixed_one' or 'restricted'
|
||||
},
|
||||
toolCalling: {
|
||||
mode: 'custom_tools', // or 'function_calling' or 'none'
|
||||
supportsFreeform: true,
|
||||
supportsAllowedTools: true,
|
||||
supportsParallelCalls: true
|
||||
},
|
||||
stateManagement: {
|
||||
supportsResponseId: true,
|
||||
supportsConversationChaining: true,
|
||||
supportsPreviousResponseId: true
|
||||
},
|
||||
streaming: {
|
||||
supported: false,
|
||||
includesUsage: true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
### Run Adapter Tests
|
||||
```bash
|
||||
npx tsx src/test/testAdapters.ts
|
||||
```
|
||||
|
||||
### Verify TypeScript Compilation
|
||||
```bash
|
||||
npx tsc --noEmit
|
||||
```
|
||||
|
||||
## 🏗️ Architecture
|
||||
|
||||
### Request Flow
|
||||
```
|
||||
User Input
|
||||
↓
|
||||
query.ts
|
||||
↓
|
||||
claude.ts (queryLLM)
|
||||
↓
|
||||
ModelAdapterFactory
|
||||
↓
|
||||
[Capability Check]
|
||||
↓
|
||||
ResponsesAPIAdapter or ChatCompletionsAdapter
|
||||
↓
|
||||
API Call (openai.ts)
|
||||
↓
|
||||
Response
|
||||
```
|
||||
|
||||
### Key Components
|
||||
|
||||
1. **ModelAdapterFactory**: Determines which adapter to use based on model capabilities
|
||||
2. **ResponsesAPIAdapter**: Handles GPT-5 Responses API format
|
||||
3. **ChatCompletionsAdapter**: Handles traditional Chat Completions format
|
||||
4. **Model Registry**: Central configuration for all model capabilities
|
||||
|
||||
## 🔄 Migration from Legacy System
|
||||
|
||||
The system is designed for zero-downtime migration:
|
||||
|
||||
1. **Phase 1** ✅: Infrastructure created (no impact on existing code)
|
||||
2. **Phase 2** ✅: Integration with environment variable toggle
|
||||
3. **Phase 3**: Remove legacy hardcoded checks (optional)
|
||||
|
||||
## 📊 Performance
|
||||
|
||||
- **Zero overhead**: Capabilities are cached after first lookup
|
||||
- **Smart fallback**: Automatically uses Chat Completions for custom endpoints
|
||||
- **Streaming aware**: Falls back when streaming is needed but not supported
|
||||
|
||||
## 🛡️ Safety Features
|
||||
|
||||
1. **100% backward compatible**: Legacy system preserved
|
||||
2. **Environment variable toggle**: Easy rollback if needed
|
||||
3. **Graceful degradation**: Falls back to Chat Completions when needed
|
||||
4. **Type-safe**: Full TypeScript support
|
||||
|
||||
## 🎯 Benefits
|
||||
|
||||
1. **No more hardcoded model checks**: Clean, maintainable code
|
||||
2. **Easy to add new models**: Just update the registry
|
||||
3. **Future-proof**: Ready for GPT-6, GLM-5, and beyond
|
||||
4. **Unified interface**: Same code handles all API types
|
||||
|
||||
## 📝 Notes
|
||||
|
||||
- The system automatically detects official OpenAI endpoints
|
||||
- Custom endpoints automatically use Chat Completions API
|
||||
- Streaming requirements are handled transparently
|
||||
- All existing model configurations are preserved
|
||||
|
||||
## 🚨 Troubleshooting
|
||||
|
||||
### Models not using correct API
|
||||
- Check if `USE_NEW_ADAPTERS=true` is set
|
||||
- Verify model is in the registry
|
||||
- Check if custom endpoint is configured (forces Chat Completions)
|
||||
|
||||
### Type errors
|
||||
- Run `npx tsc --noEmit` to check for issues
|
||||
- Ensure all imports are correct
|
||||
|
||||
### Runtime errors
|
||||
- Check console for adapter selection logs
|
||||
- Verify API keys and endpoints are correct
|
||||
|
||||
## 📞 Support
|
||||
|
||||
For issues or questions:
|
||||
1. Check the test output: `npx tsx src/test/testAdapters.ts`
|
||||
2. Review the model registry in `src/constants/modelCapabilities.ts`
|
||||
3. Check adapter selection logic in `src/services/modelAdapterFactory.ts`
|
||||
|
||||
---
|
||||
|
||||
**Status**: ✅ Production Ready with Environment Variable Toggle
|
||||
893
next_todo.md
Normal file
893
next_todo.md
Normal file
@ -0,0 +1,893 @@
|
||||
# Kode系统 Responses API 支持重构施工文档
|
||||
|
||||
## 📋 项目概述
|
||||
|
||||
### 目标
|
||||
将Kode系统从硬编码的GPT-5检测升级为基于能力声明的模型系统,支持所有Responses API类模型(GPT-5、GPT-6、GLM-5等)。
|
||||
|
||||
### 核心原则
|
||||
1. **零破坏性**: 100%保留现有功能
|
||||
2. **渐进式**: 可随时回滚
|
||||
3. **可扩展**: 新模型只需配置
|
||||
4. **优雅性**: 消除硬编码,统一处理流程
|
||||
|
||||
## 🏗️ 系统架构概览
|
||||
|
||||
### 当前架构(问题)
|
||||
```
|
||||
用户输入 → REPL → query.ts → queryLLM
|
||||
↓
|
||||
[硬编码检测]
|
||||
if (isGPT5Model()) {...}
|
||||
if (isGPT4Model()) {...}
|
||||
↓
|
||||
不同的API调用路径
|
||||
```
|
||||
|
||||
### 目标架构(解决方案)
|
||||
```
|
||||
用户输入 → REPL → query.ts → queryLLM
|
||||
↓
|
||||
[能力声明系统]
|
||||
ModelCapabilities查询
|
||||
↓
|
||||
[统一适配器]
|
||||
ResponsesAPIAdapter / ChatCompletionsAdapter
|
||||
↓
|
||||
统一的API调用
|
||||
```
|
||||
|
||||
## 📁 文件结构规划
|
||||
|
||||
```
|
||||
src/
|
||||
├── types/
|
||||
│ └── modelCapabilities.ts # 新建:能力类型定义
|
||||
├── constants/
|
||||
│ └── modelCapabilities.ts # 新建:模型能力注册表
|
||||
├── services/
|
||||
│ ├── adapters/ # 新建目录:适配器
|
||||
│ │ ├── base.ts # 新建:基础适配器类
|
||||
│ │ ├── responsesAPI.ts # 新建:Responses API适配器
|
||||
│ │ └── chatCompletions.ts # 新建:Chat Completions适配器
|
||||
│ ├── modelAdapterFactory.ts # 新建:适配器工厂
|
||||
│ ├── claude.ts # 修改:使用新系统
|
||||
│ └── openai.ts # 修改:清理硬编码
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Phase 1: 基础设施建设(第1-2天)
|
||||
|
||||
### 目标
|
||||
创建能力声明系统的基础架构,不影响现有代码运行。
|
||||
|
||||
### Step 1.1: 创建模型能力类型定义
|
||||
|
||||
**文件**: `src/types/modelCapabilities.ts` (新建)
|
||||
|
||||
**任务**: 定义模型能力接口
|
||||
|
||||
```typescript
|
||||
// 完整代码 - 直接复制粘贴
|
||||
export interface ModelCapabilities {
|
||||
// API架构类型
|
||||
apiArchitecture: {
|
||||
primary: 'chat_completions' | 'responses_api'
|
||||
fallback?: 'chat_completions' // Responses API模型可降级
|
||||
}
|
||||
|
||||
// 参数映射
|
||||
parameters: {
|
||||
maxTokensField: 'max_tokens' | 'max_completion_tokens'
|
||||
supportsReasoningEffort: boolean
|
||||
supportsVerbosity: boolean
|
||||
temperatureMode: 'flexible' | 'fixed_one' | 'restricted'
|
||||
}
|
||||
|
||||
// 工具调用能力
|
||||
toolCalling: {
|
||||
mode: 'none' | 'function_calling' | 'custom_tools'
|
||||
supportsFreeform: boolean
|
||||
supportsAllowedTools: boolean
|
||||
supportsParallelCalls: boolean
|
||||
}
|
||||
|
||||
// 状态管理
|
||||
stateManagement: {
|
||||
supportsResponseId: boolean
|
||||
supportsConversationChaining: boolean
|
||||
supportsPreviousResponseId: boolean
|
||||
}
|
||||
|
||||
// 流式支持
|
||||
streaming: {
|
||||
supported: boolean
|
||||
includesUsage: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// 统一的请求参数
|
||||
export interface UnifiedRequestParams {
|
||||
messages: any[]
|
||||
systemPrompt: string[]
|
||||
tools?: any[]
|
||||
maxTokens: number
|
||||
stream?: boolean
|
||||
previousResponseId?: string
|
||||
reasoningEffort?: 'minimal' | 'low' | 'medium' | 'high'
|
||||
verbosity?: 'low' | 'medium' | 'high'
|
||||
temperature?: number
|
||||
}
|
||||
|
||||
// 统一的响应格式
|
||||
export interface UnifiedResponse {
|
||||
id: string
|
||||
content: string
|
||||
toolCalls?: any[]
|
||||
usage: {
|
||||
promptTokens: number
|
||||
completionTokens: number
|
||||
reasoningTokens?: number
|
||||
}
|
||||
responseId?: string // 用于Responses API状态管理
|
||||
}
|
||||
```
|
||||
|
||||
### Step 1.2: 创建模型能力注册表
|
||||
|
||||
**文件**: `src/constants/modelCapabilities.ts` (新建)
|
||||
|
||||
**任务**: 为所有模型定义能力
|
||||
|
||||
```typescript
|
||||
import { ModelCapabilities } from '../types/modelCapabilities'
|
||||
|
||||
// GPT-5的标准能力定义
|
||||
const GPT5_CAPABILITIES: ModelCapabilities = {
|
||||
apiArchitecture: {
|
||||
primary: 'responses_api',
|
||||
fallback: 'chat_completions'
|
||||
},
|
||||
parameters: {
|
||||
maxTokensField: 'max_completion_tokens',
|
||||
supportsReasoningEffort: true,
|
||||
supportsVerbosity: true,
|
||||
temperatureMode: 'fixed_one'
|
||||
},
|
||||
toolCalling: {
|
||||
mode: 'custom_tools',
|
||||
supportsFreeform: true,
|
||||
supportsAllowedTools: true,
|
||||
supportsParallelCalls: true
|
||||
},
|
||||
stateManagement: {
|
||||
supportsResponseId: true,
|
||||
supportsConversationChaining: true,
|
||||
supportsPreviousResponseId: true
|
||||
},
|
||||
streaming: {
|
||||
supported: false, // Responses API暂不支持流式
|
||||
includesUsage: true
|
||||
}
|
||||
}
|
||||
|
||||
// Chat Completions的标准能力定义
|
||||
const CHAT_COMPLETIONS_CAPABILITIES: ModelCapabilities = {
|
||||
apiArchitecture: {
|
||||
primary: 'chat_completions'
|
||||
},
|
||||
parameters: {
|
||||
maxTokensField: 'max_tokens',
|
||||
supportsReasoningEffort: false,
|
||||
supportsVerbosity: false,
|
||||
temperatureMode: 'flexible'
|
||||
},
|
||||
toolCalling: {
|
||||
mode: 'function_calling',
|
||||
supportsFreeform: false,
|
||||
supportsAllowedTools: false,
|
||||
supportsParallelCalls: true
|
||||
},
|
||||
stateManagement: {
|
||||
supportsResponseId: false,
|
||||
supportsConversationChaining: false,
|
||||
supportsPreviousResponseId: false
|
||||
},
|
||||
streaming: {
|
||||
supported: true,
|
||||
includesUsage: true
|
||||
}
|
||||
}
|
||||
|
||||
// 完整的模型能力映射表
|
||||
export const MODEL_CAPABILITIES_REGISTRY: Record<string, ModelCapabilities> = {
|
||||
// GPT-5系列
|
||||
'gpt-5': GPT5_CAPABILITIES,
|
||||
'gpt-5-mini': GPT5_CAPABILITIES,
|
||||
'gpt-5-nano': GPT5_CAPABILITIES,
|
||||
'gpt-5-chat-latest': GPT5_CAPABILITIES,
|
||||
|
||||
// GPT-4系列
|
||||
'gpt-4o': CHAT_COMPLETIONS_CAPABILITIES,
|
||||
'gpt-4o-mini': CHAT_COMPLETIONS_CAPABILITIES,
|
||||
'gpt-4-turbo': CHAT_COMPLETIONS_CAPABILITIES,
|
||||
'gpt-4': CHAT_COMPLETIONS_CAPABILITIES,
|
||||
|
||||
// Claude系列(通过转换层支持)
|
||||
'claude-3-5-sonnet-20241022': CHAT_COMPLETIONS_CAPABILITIES,
|
||||
'claude-3-5-haiku-20241022': CHAT_COMPLETIONS_CAPABILITIES,
|
||||
'claude-3-opus-20240229': CHAT_COMPLETIONS_CAPABILITIES,
|
||||
|
||||
// O1系列(特殊的推理模型)
|
||||
'o1': {
|
||||
...CHAT_COMPLETIONS_CAPABILITIES,
|
||||
parameters: {
|
||||
...CHAT_COMPLETIONS_CAPABILITIES.parameters,
|
||||
maxTokensField: 'max_completion_tokens',
|
||||
temperatureMode: 'fixed_one'
|
||||
}
|
||||
},
|
||||
'o1-mini': {
|
||||
...CHAT_COMPLETIONS_CAPABILITIES,
|
||||
parameters: {
|
||||
...CHAT_COMPLETIONS_CAPABILITIES.parameters,
|
||||
maxTokensField: 'max_completion_tokens',
|
||||
temperatureMode: 'fixed_one'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 智能推断未注册模型的能力
|
||||
export function inferModelCapabilities(modelName: string): ModelCapabilities | null {
|
||||
if (!modelName) return null
|
||||
|
||||
const lowerName = modelName.toLowerCase()
|
||||
|
||||
// GPT-5系列
|
||||
if (lowerName.includes('gpt-5') || lowerName.includes('gpt5')) {
|
||||
return GPT5_CAPABILITIES
|
||||
}
|
||||
|
||||
// GPT-6系列(预留)
|
||||
if (lowerName.includes('gpt-6') || lowerName.includes('gpt6')) {
|
||||
return {
|
||||
...GPT5_CAPABILITIES,
|
||||
streaming: { supported: true, includesUsage: true }
|
||||
}
|
||||
}
|
||||
|
||||
// GLM系列
|
||||
if (lowerName.includes('glm-5') || lowerName.includes('glm5')) {
|
||||
return {
|
||||
...GPT5_CAPABILITIES,
|
||||
toolCalling: {
|
||||
...GPT5_CAPABILITIES.toolCalling,
|
||||
supportsAllowedTools: false // GLM可能不支持
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// O1系列
|
||||
if (lowerName.startsWith('o1') || lowerName.includes('o1-')) {
|
||||
return {
|
||||
...CHAT_COMPLETIONS_CAPABILITIES,
|
||||
parameters: {
|
||||
...CHAT_COMPLETIONS_CAPABILITIES.parameters,
|
||||
maxTokensField: 'max_completion_tokens',
|
||||
temperatureMode: 'fixed_one'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 默认返回null,让系统使用默认行为
|
||||
return null
|
||||
}
|
||||
|
||||
// 获取模型能力(带缓存)
|
||||
const capabilityCache = new Map<string, ModelCapabilities>()
|
||||
|
||||
export function getModelCapabilities(modelName: string): ModelCapabilities {
|
||||
// 检查缓存
|
||||
if (capabilityCache.has(modelName)) {
|
||||
return capabilityCache.get(modelName)!
|
||||
}
|
||||
|
||||
// 查找注册表
|
||||
if (MODEL_CAPABILITIES_REGISTRY[modelName]) {
|
||||
const capabilities = MODEL_CAPABILITIES_REGISTRY[modelName]
|
||||
capabilityCache.set(modelName, capabilities)
|
||||
return capabilities
|
||||
}
|
||||
|
||||
// 尝试推断
|
||||
const inferred = inferModelCapabilities(modelName)
|
||||
if (inferred) {
|
||||
capabilityCache.set(modelName, inferred)
|
||||
return inferred
|
||||
}
|
||||
|
||||
// 默认为Chat Completions
|
||||
const defaultCapabilities = CHAT_COMPLETIONS_CAPABILITIES
|
||||
capabilityCache.set(modelName, defaultCapabilities)
|
||||
return defaultCapabilities
|
||||
}
|
||||
```
|
||||
|
||||
### Step 1.3: 创建基础适配器类
|
||||
|
||||
**文件**: `src/services/adapters/base.ts` (新建)
|
||||
|
||||
**任务**: 创建adapters目录和基础类
|
||||
|
||||
```typescript
|
||||
import { ModelCapabilities, UnifiedRequestParams, UnifiedResponse } from '../../types/modelCapabilities'
|
||||
import { ModelProfile } from '../../utils/config'
|
||||
import { Tool } from '../../Tool'
|
||||
|
||||
export abstract class ModelAPIAdapter {
|
||||
constructor(
|
||||
protected capabilities: ModelCapabilities,
|
||||
protected modelProfile: ModelProfile
|
||||
) {}
|
||||
|
||||
// 子类必须实现的方法
|
||||
abstract createRequest(params: UnifiedRequestParams): any
|
||||
abstract parseResponse(response: any): UnifiedResponse
|
||||
abstract buildTools(tools: Tool[]): any
|
||||
|
||||
// 共享的工具方法
|
||||
protected getMaxTokensParam(): string {
|
||||
return this.capabilities.parameters.maxTokensField
|
||||
}
|
||||
|
||||
protected getTemperature(): number {
|
||||
if (this.capabilities.parameters.temperatureMode === 'fixed_one') {
|
||||
return 1
|
||||
}
|
||||
if (this.capabilities.parameters.temperatureMode === 'restricted') {
|
||||
return Math.min(1, this.modelProfile.temperature || 0.7)
|
||||
}
|
||||
return this.modelProfile.temperature || 0.7
|
||||
}
|
||||
|
||||
protected shouldIncludeReasoningEffort(): boolean {
|
||||
return this.capabilities.parameters.supportsReasoningEffort
|
||||
}
|
||||
|
||||
protected shouldIncludeVerbosity(): boolean {
|
||||
return this.capabilities.parameters.supportsVerbosity
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 1.4: 创建Responses API适配器
|
||||
|
||||
**文件**: `src/services/adapters/responsesAPI.ts` (新建)
|
||||
|
||||
**任务**: 实现Responses API适配器
|
||||
|
||||
```typescript
|
||||
import { ModelAPIAdapter } from './base'
|
||||
import { UnifiedRequestParams, UnifiedResponse } from '../../types/modelCapabilities'
|
||||
import { Tool } from '../../Tool'
|
||||
import { zodToJsonSchema } from '../../utils/zodToJsonSchema'
|
||||
|
||||
export class ResponsesAPIAdapter extends ModelAPIAdapter {
|
||||
createRequest(params: UnifiedRequestParams): any {
|
||||
const { messages, systemPrompt, tools, maxTokens } = params
|
||||
|
||||
// 分离系统消息和用户消息
|
||||
const systemMessages = messages.filter(m => m.role === 'system')
|
||||
const nonSystemMessages = messages.filter(m => m.role !== 'system')
|
||||
|
||||
// 构建基础请求
|
||||
const request: any = {
|
||||
model: this.modelProfile.modelName,
|
||||
input: this.convertMessagesToInput(nonSystemMessages),
|
||||
instructions: this.buildInstructions(systemPrompt, systemMessages)
|
||||
}
|
||||
|
||||
// 添加token限制
|
||||
request[this.getMaxTokensParam()] = maxTokens
|
||||
|
||||
// 添加温度(GPT-5只支持1)
|
||||
if (this.getTemperature() === 1) {
|
||||
request.temperature = 1
|
||||
}
|
||||
|
||||
// 添加推理控制
|
||||
if (this.shouldIncludeReasoningEffort()) {
|
||||
request.reasoning = {
|
||||
effort: params.reasoningEffort || this.modelProfile.reasoningEffort || 'medium'
|
||||
}
|
||||
}
|
||||
|
||||
// 添加详细度控制
|
||||
if (this.shouldIncludeVerbosity()) {
|
||||
request.text = {
|
||||
verbosity: params.verbosity || 'high' // 编码任务默认高详细度
|
||||
}
|
||||
}
|
||||
|
||||
// 添加工具
|
||||
if (tools && tools.length > 0) {
|
||||
request.tools = this.buildTools(tools)
|
||||
|
||||
// 处理allowed_tools
|
||||
if (params.allowedTools && this.capabilities.toolCalling.supportsAllowedTools) {
|
||||
request.tool_choice = {
|
||||
type: 'allowed_tools',
|
||||
mode: 'auto',
|
||||
tools: params.allowedTools
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加状态管理
|
||||
if (params.previousResponseId && this.capabilities.stateManagement.supportsPreviousResponseId) {
|
||||
request.previous_response_id = params.previousResponseId
|
||||
}
|
||||
|
||||
return request
|
||||
}
|
||||
|
||||
buildTools(tools: Tool[]): any[] {
|
||||
// 如果不支持freeform,使用传统格式
|
||||
if (!this.capabilities.toolCalling.supportsFreeform) {
|
||||
return tools.map(tool => ({
|
||||
type: 'function',
|
||||
function: {
|
||||
name: tool.name,
|
||||
description: tool.description || '',
|
||||
parameters: tool.inputJSONSchema || zodToJsonSchema(tool.inputSchema)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
// Custom tools格式(GPT-5特性)
|
||||
return tools.map(tool => {
|
||||
const hasSchema = tool.inputJSONSchema || tool.inputSchema
|
||||
const isCustom = !hasSchema || tool.freeformInput
|
||||
|
||||
if (isCustom) {
|
||||
// Custom tool格式
|
||||
return {
|
||||
type: 'custom',
|
||||
name: tool.name,
|
||||
description: tool.description || ''
|
||||
}
|
||||
} else {
|
||||
// 传统function格式
|
||||
return {
|
||||
type: 'function',
|
||||
function: {
|
||||
name: tool.name,
|
||||
description: tool.description || '',
|
||||
parameters: tool.inputJSONSchema || zodToJsonSchema(tool.inputSchema)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
parseResponse(response: any): UnifiedResponse {
|
||||
// 处理基础文本输出
|
||||
let content = response.output_text || ''
|
||||
|
||||
// 处理结构化输出
|
||||
if (response.output && Array.isArray(response.output)) {
|
||||
const messageItems = response.output.filter(item => item.type === 'message')
|
||||
if (messageItems.length > 0) {
|
||||
content = messageItems
|
||||
.map(item => {
|
||||
if (item.content && Array.isArray(item.content)) {
|
||||
return item.content
|
||||
.filter(c => c.type === 'text')
|
||||
.map(c => c.text)
|
||||
.join('\n')
|
||||
}
|
||||
return item.content || ''
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join('\n\n')
|
||||
}
|
||||
}
|
||||
|
||||
// 解析工具调用
|
||||
const toolCalls = this.parseToolCalls(response)
|
||||
|
||||
// 构建统一响应
|
||||
return {
|
||||
id: response.id || `resp_${Date.now()}`,
|
||||
content,
|
||||
toolCalls,
|
||||
usage: {
|
||||
promptTokens: response.usage?.input_tokens || 0,
|
||||
completionTokens: response.usage?.output_tokens || 0,
|
||||
reasoningTokens: response.usage?.output_tokens_details?.reasoning_tokens
|
||||
},
|
||||
responseId: response.id // 保存用于状态管理
|
||||
}
|
||||
}
|
||||
|
||||
private convertMessagesToInput(messages: any[]): any {
|
||||
// 将消息转换为Responses API的input格式
|
||||
// 可能需要根据实际API规范调整
|
||||
return messages
|
||||
}
|
||||
|
||||
private buildInstructions(systemPrompt: string[], systemMessages: any[]): string {
|
||||
const systemContent = systemMessages.map(m => m.content).join('\n\n')
|
||||
const promptContent = systemPrompt.join('\n\n')
|
||||
return [systemContent, promptContent].filter(Boolean).join('\n\n')
|
||||
}
|
||||
|
||||
private parseToolCalls(response: any): any[] {
|
||||
if (!response.output || !Array.isArray(response.output)) {
|
||||
return []
|
||||
}
|
||||
|
||||
return response.output
|
||||
.filter(item => item.type === 'tool_call')
|
||||
.map(item => ({
|
||||
id: item.id || `tool_${Date.now()}`,
|
||||
type: 'tool_call',
|
||||
name: item.name,
|
||||
arguments: item.arguments // 可能是文本或JSON
|
||||
}))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 1.5: 创建Chat Completions适配器
|
||||
|
||||
**文件**: `src/services/adapters/chatCompletions.ts` (新建)
|
||||
|
||||
**任务**: 实现Chat Completions适配器
|
||||
|
||||
```typescript
|
||||
import { ModelAPIAdapter } from './base'
|
||||
import { UnifiedRequestParams, UnifiedResponse } from '../../types/modelCapabilities'
|
||||
import { Tool } from '../../Tool'
|
||||
import { zodToJsonSchema } from '../../utils/zodToJsonSchema'
|
||||
|
||||
export class ChatCompletionsAdapter extends ModelAPIAdapter {
|
||||
createRequest(params: UnifiedRequestParams): any {
|
||||
const { messages, systemPrompt, tools, maxTokens, stream } = params
|
||||
|
||||
// 构建完整消息列表(包含系统提示)
|
||||
const fullMessages = this.buildMessages(systemPrompt, messages)
|
||||
|
||||
// 构建请求
|
||||
const request: any = {
|
||||
model: this.modelProfile.modelName,
|
||||
messages: fullMessages,
|
||||
[this.getMaxTokensParam()]: maxTokens,
|
||||
temperature: this.getTemperature()
|
||||
}
|
||||
|
||||
// 添加工具
|
||||
if (tools && tools.length > 0) {
|
||||
request.tools = this.buildTools(tools)
|
||||
request.tool_choice = 'auto'
|
||||
}
|
||||
|
||||
// 添加流式选项
|
||||
if (stream) {
|
||||
request.stream = true
|
||||
request.stream_options = {
|
||||
include_usage: true
|
||||
}
|
||||
}
|
||||
|
||||
// O1模型的特殊处理
|
||||
if (this.modelProfile.modelName.startsWith('o1')) {
|
||||
delete request.temperature // O1不支持temperature
|
||||
delete request.stream // O1不支持流式
|
||||
delete request.stream_options
|
||||
}
|
||||
|
||||
return request
|
||||
}
|
||||
|
||||
buildTools(tools: Tool[]): any[] {
|
||||
// Chat Completions只支持传统的function calling
|
||||
return tools.map(tool => ({
|
||||
type: 'function',
|
||||
function: {
|
||||
name: tool.name,
|
||||
description: tool.description || '',
|
||||
parameters: tool.inputJSONSchema || zodToJsonSchema(tool.inputSchema)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
parseResponse(response: any): UnifiedResponse {
|
||||
const choice = response.choices?.[0]
|
||||
|
||||
return {
|
||||
id: response.id || `chatcmpl_${Date.now()}`,
|
||||
content: choice?.message?.content || '',
|
||||
toolCalls: choice?.message?.tool_calls || [],
|
||||
usage: {
|
||||
promptTokens: response.usage?.prompt_tokens || 0,
|
||||
completionTokens: response.usage?.completion_tokens || 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private buildMessages(systemPrompt: string[], messages: any[]): any[] {
|
||||
// 合并系统提示和消息
|
||||
const systemMessages = systemPrompt.map(prompt => ({
|
||||
role: 'system',
|
||||
content: prompt
|
||||
}))
|
||||
|
||||
return [...systemMessages, ...messages]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 1.6: 创建适配器工厂
|
||||
|
||||
**文件**: `src/services/modelAdapterFactory.ts` (新建)
|
||||
|
||||
**任务**: 创建工厂类来选择合适的适配器
|
||||
|
||||
```typescript
|
||||
import { ModelAPIAdapter } from './adapters/base'
|
||||
import { ResponsesAPIAdapter } from './adapters/responsesAPI'
|
||||
import { ChatCompletionsAdapter } from './adapters/chatCompletions'
|
||||
import { getModelCapabilities } from '../constants/modelCapabilities'
|
||||
import { ModelProfile, getGlobalConfig } from '../utils/config'
|
||||
import { ModelCapabilities } from '../types/modelCapabilities'
|
||||
|
||||
export class ModelAdapterFactory {
|
||||
/**
|
||||
* 根据模型配置创建合适的适配器
|
||||
*/
|
||||
static createAdapter(modelProfile: ModelProfile): ModelAPIAdapter {
|
||||
const capabilities = getModelCapabilities(modelProfile.modelName)
|
||||
|
||||
// 决定使用哪种API
|
||||
const apiType = this.determineAPIType(modelProfile, capabilities)
|
||||
|
||||
// 创建对应的适配器
|
||||
switch (apiType) {
|
||||
case 'responses_api':
|
||||
return new ResponsesAPIAdapter(capabilities, modelProfile)
|
||||
case 'chat_completions':
|
||||
default:
|
||||
return new ChatCompletionsAdapter(capabilities, modelProfile)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 决定应该使用哪种API
|
||||
*/
|
||||
private static determineAPIType(
|
||||
modelProfile: ModelProfile,
|
||||
capabilities: ModelCapabilities
|
||||
): 'responses_api' | 'chat_completions' {
|
||||
// 如果模型不支持Responses API,直接使用Chat Completions
|
||||
if (capabilities.apiArchitecture.primary !== 'responses_api') {
|
||||
return 'chat_completions'
|
||||
}
|
||||
|
||||
// 检查是否是官方OpenAI端点
|
||||
const isOfficialOpenAI = !modelProfile.baseURL ||
|
||||
modelProfile.baseURL.includes('api.openai.com')
|
||||
|
||||
// 非官方端点使用Chat Completions(即使模型支持Responses API)
|
||||
if (!isOfficialOpenAI) {
|
||||
// 如果有fallback选项,使用fallback
|
||||
if (capabilities.apiArchitecture.fallback === 'chat_completions') {
|
||||
return 'chat_completions'
|
||||
}
|
||||
// 否则使用primary(可能会失败,但让它尝试)
|
||||
return capabilities.apiArchitecture.primary
|
||||
}
|
||||
|
||||
// 检查是否需要流式(Responses API暂不支持)
|
||||
const config = getGlobalConfig()
|
||||
if (config.stream && !capabilities.streaming.supported) {
|
||||
// 需要流式但Responses API不支持,降级到Chat Completions
|
||||
if (capabilities.apiArchitecture.fallback === 'chat_completions') {
|
||||
return 'chat_completions'
|
||||
}
|
||||
}
|
||||
|
||||
// 使用主要API类型
|
||||
return capabilities.apiArchitecture.primary
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查模型是否应该使用Responses API
|
||||
*/
|
||||
static shouldUseResponsesAPI(modelProfile: ModelProfile): boolean {
|
||||
const capabilities = getModelCapabilities(modelProfile.modelName)
|
||||
const apiType = this.determineAPIType(modelProfile, capabilities)
|
||||
return apiType === 'responses_api'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Phase 2: 集成与测试(第3-4天)
|
||||
|
||||
### 目标
|
||||
将新系统集成到现有代码中,与旧系统并行运行。
|
||||
|
||||
### Step 2.1: 修改claude.ts使用新系统
|
||||
|
||||
**文件**: `src/services/claude.ts` (修改)
|
||||
|
||||
**任务**: 在queryLLMWithProfile中添加新的适配器路径
|
||||
|
||||
**找到函数**: `queryLLMWithProfile` (约第1182行)
|
||||
|
||||
**修改内容**:
|
||||
|
||||
```typescript
|
||||
// 在函数开头添加功能开关
|
||||
const USE_NEW_ADAPTER_SYSTEM = process.env.USE_NEW_ADAPTERS !== 'false'
|
||||
|
||||
// 在获取modelProfile后添加新路径
|
||||
if (USE_NEW_ADAPTER_SYSTEM) {
|
||||
// 🚀 新的适配器系统
|
||||
const adapter = ModelAdapterFactory.createAdapter(modelProfile)
|
||||
|
||||
// 构建统一请求参数
|
||||
const unifiedParams: UnifiedRequestParams = {
|
||||
messages: openaiMessages, // 使用已转换的OpenAI格式消息
|
||||
systemPrompt: openaiSystem.map(s => s.content),
|
||||
tools: toolSchemas,
|
||||
maxTokens: getMaxTokensFromProfile(modelProfile),
|
||||
stream: config.stream,
|
||||
reasoningEffort: modelProfile.reasoningEffort,
|
||||
temperature: isGPT5Model(model) ? 1 : MAIN_QUERY_TEMPERATURE
|
||||
}
|
||||
|
||||
// 创建请求
|
||||
const request = adapter.createRequest(unifiedParams)
|
||||
|
||||
// 判断使用哪个API端点
|
||||
if (ModelAdapterFactory.shouldUseResponsesAPI(modelProfile)) {
|
||||
// 调用Responses API(复用现有的callGPT5ResponsesAPI)
|
||||
const response = await callGPT5ResponsesAPI(modelProfile, request, signal)
|
||||
return adapter.parseResponse(response)
|
||||
} else {
|
||||
// 调用Chat Completions(复用现有逻辑)
|
||||
// ... 现有的Chat Completions调用代码
|
||||
}
|
||||
} else {
|
||||
// 保留原有逻辑完全不变
|
||||
// ... 现有的所有代码
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2.2: 添加测试脚本
|
||||
|
||||
**文件**: `src/test/testAdapters.ts` (新建)
|
||||
|
||||
**任务**: 创建测试脚本验证新系统
|
||||
|
||||
```typescript
|
||||
import { ModelAdapterFactory } from '../services/modelAdapterFactory'
|
||||
import { getGlobalConfig } from '../utils/config'
|
||||
|
||||
// 测试不同模型的适配器选择
|
||||
const testModels = [
|
||||
{ modelName: 'gpt-5', provider: 'openai' },
|
||||
{ modelName: 'gpt-4o', provider: 'openai' },
|
||||
{ modelName: 'claude-3-5-sonnet-20241022', provider: 'anthropic' },
|
||||
{ modelName: 'o1', provider: 'openai' },
|
||||
{ modelName: 'glm-5', provider: 'custom' }
|
||||
]
|
||||
|
||||
testModels.forEach(model => {
|
||||
console.log(`Testing ${model.modelName}:`)
|
||||
const adapter = ModelAdapterFactory.createAdapter(model as any)
|
||||
console.log(` Adapter type: ${adapter.constructor.name}`)
|
||||
console.log(` Should use Responses API: ${ModelAdapterFactory.shouldUseResponsesAPI(model as any)}`)
|
||||
})
|
||||
```
|
||||
|
||||
### Step 2.3: 清理硬编码(可选,Phase 3再做)
|
||||
|
||||
**文件**: `src/services/openai.ts` (修改)
|
||||
|
||||
**任务**: 标记需要移除的硬编码部分(先不删除)
|
||||
|
||||
```typescript
|
||||
// 在isGPT5Model函数上添加注释
|
||||
/**
|
||||
* @deprecated 将被ModelCapabilities系统替代
|
||||
*/
|
||||
function isGPT5Model(modelName: string): boolean {
|
||||
return modelName.startsWith('gpt-5')
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Phase 3: 优化与清理(第5-6天)
|
||||
|
||||
### 目标
|
||||
移除旧代码,完全切换到新系统。
|
||||
|
||||
### Step 3.1: 移除功能开关
|
||||
|
||||
**文件**: `src/services/claude.ts`
|
||||
|
||||
**任务**: 移除USE_NEW_ADAPTER_SYSTEM检查,默认使用新系统
|
||||
|
||||
### Step 3.2: 清理硬编码函数
|
||||
|
||||
**文件列表**:
|
||||
- `src/services/openai.ts` - 移除isGPT5Model函数
|
||||
- `src/services/claude.ts` - 移除isGPT5Model函数
|
||||
- `src/services/openai.ts` - 移除MODEL_FEATURES常量
|
||||
|
||||
### Step 3.3: 更新文档
|
||||
|
||||
**文件**: `README.md`
|
||||
|
||||
**任务**: 添加新模型支持说明
|
||||
|
||||
```markdown
|
||||
## 支持的模型
|
||||
|
||||
本系统通过能力声明系统支持以下API类型:
|
||||
- Chat Completions API: GPT-4, Claude等传统模型
|
||||
- Responses API: GPT-5, GPT-6, GLM-5等新一代模型
|
||||
|
||||
添加新模型只需在 `src/constants/modelCapabilities.ts` 中配置即可。
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 验证清单
|
||||
|
||||
### Phase 1完成标准
|
||||
- [ ] 所有新文件创建完成
|
||||
- [ ] 代码可以编译通过
|
||||
- [ ] 现有功能完全不受影响
|
||||
|
||||
### Phase 2完成标准
|
||||
- [ ] 新旧系统可以通过环境变量切换
|
||||
- [ ] GPT-5可以正常使用
|
||||
- [ ] 所有现有模型功能正常
|
||||
|
||||
### Phase 3完成标准
|
||||
- [ ] 完全使用新系统
|
||||
- [ ] 代码更简洁清晰
|
||||
- [ ] 新模型可通过配置添加
|
||||
|
||||
---
|
||||
|
||||
## 🎯 关键注意事项
|
||||
|
||||
1. **不要删除任何现有功能代码**,直到Phase 3
|
||||
2. **始终保持向后兼容**
|
||||
3. **每个Phase结束后都要测试**
|
||||
4. **如果出现问题可以立即回滚**
|
||||
|
||||
## 📝 外包程序员执行指南
|
||||
|
||||
1. **严格按照Phase顺序执行**,不要跳步
|
||||
2. **复制粘贴提供的代码**,不要自己修改
|
||||
3. **遇到问题立即停止并报告**
|
||||
4. **每完成一个Step都要git commit**,方便回滚
|
||||
|
||||
---
|
||||
|
||||
此文档设计为"无脑执行"级别,外包程序员只需要:
|
||||
1. 创建指定的文件
|
||||
2. 复制粘贴提供的代码
|
||||
3. 在指定位置修改代码
|
||||
4. 运行测试验证
|
||||
|
||||
整个过程不需要理解业务逻辑,只需要机械执行即可。
|
||||
26
snake_demo/index.html
Normal file
26
snake_demo/index.html
Normal file
@ -0,0 +1,26 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>贪吃蛇游戏</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="game-container">
|
||||
<h1>贪吃蛇游戏</h1>
|
||||
<div class="game-info">
|
||||
<div class="score">得分: <span id="score">0</span></div>
|
||||
<div class="high-score">最高分: <span id="high-score">0</span></div>
|
||||
</div>
|
||||
<canvas id="gameCanvas" width="400" height="400"></canvas>
|
||||
<div class="controls">
|
||||
<p>使用方向键控制蛇的移动</p>
|
||||
<button id="startBtn">开始游戏</button>
|
||||
<button id="pauseBtn">暂停</button>
|
||||
<button id="resetBtn">重新开始</button>
|
||||
</div>
|
||||
</div>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
127
snake_demo/style.css
Normal file
127
snake_demo/style.css
Normal file
@ -0,0 +1,127 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Arial', sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.game-container {
|
||||
background: white;
|
||||
border-radius: 20px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
||||
text-align: center;
|
||||
max-width: 500px;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #4a5568;
|
||||
margin-bottom: 20px;
|
||||
font-size: 2.5em;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.game-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
padding: 10px 20px;
|
||||
background: #f7fafc;
|
||||
border-radius: 10px;
|
||||
border: 2px solid #e2e8f0;
|
||||
}
|
||||
|
||||
.score, .high-score {
|
||||
font-size: 1.2em;
|
||||
font-weight: bold;
|
||||
color: #2d3748;
|
||||
}
|
||||
|
||||
.score span, .high-score span {
|
||||
color: #38a169;
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
#gameCanvas {
|
||||
border: 3px solid #4a5568;
|
||||
border-radius: 10px;
|
||||
background: #1a202c;
|
||||
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.controls {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.controls p {
|
||||
margin-bottom: 15px;
|
||||
color: #4a5568;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
button {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 24px;
|
||||
margin: 5px;
|
||||
border-radius: 8px;
|
||||
font-size: 1em;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
button:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
background: #a0aec0;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.game-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.game-info {
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
#gameCanvas {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 10px 20px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
}
|
||||
@ -25,6 +25,11 @@ export interface ToolUseContext {
|
||||
kodingContext?: string
|
||||
isCustomCommand?: boolean
|
||||
}
|
||||
// GPT-5 Responses API state management
|
||||
responseState?: {
|
||||
previousResponseId?: string
|
||||
conversationId?: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface ExtendedToolUseContext extends ToolUseContext {
|
||||
|
||||
179
src/constants/modelCapabilities.ts
Normal file
179
src/constants/modelCapabilities.ts
Normal file
@ -0,0 +1,179 @@
|
||||
import { ModelCapabilities } from '../types/modelCapabilities'
|
||||
|
||||
// GPT-5 standard capability definition
|
||||
const GPT5_CAPABILITIES: ModelCapabilities = {
|
||||
apiArchitecture: {
|
||||
primary: 'responses_api',
|
||||
fallback: 'chat_completions'
|
||||
},
|
||||
parameters: {
|
||||
maxTokensField: 'max_completion_tokens',
|
||||
supportsReasoningEffort: true,
|
||||
supportsVerbosity: true,
|
||||
temperatureMode: 'fixed_one'
|
||||
},
|
||||
toolCalling: {
|
||||
mode: 'custom_tools',
|
||||
supportsFreeform: true,
|
||||
supportsAllowedTools: true,
|
||||
supportsParallelCalls: true
|
||||
},
|
||||
stateManagement: {
|
||||
supportsResponseId: true,
|
||||
supportsConversationChaining: true,
|
||||
supportsPreviousResponseId: true
|
||||
},
|
||||
streaming: {
|
||||
supported: false, // Responses API doesn't support streaming yet
|
||||
includesUsage: true
|
||||
}
|
||||
}
|
||||
|
||||
// Chat Completions standard capability definition
|
||||
const CHAT_COMPLETIONS_CAPABILITIES: ModelCapabilities = {
|
||||
apiArchitecture: {
|
||||
primary: 'chat_completions'
|
||||
},
|
||||
parameters: {
|
||||
maxTokensField: 'max_tokens',
|
||||
supportsReasoningEffort: false,
|
||||
supportsVerbosity: false,
|
||||
temperatureMode: 'flexible'
|
||||
},
|
||||
toolCalling: {
|
||||
mode: 'function_calling',
|
||||
supportsFreeform: false,
|
||||
supportsAllowedTools: false,
|
||||
supportsParallelCalls: true
|
||||
},
|
||||
stateManagement: {
|
||||
supportsResponseId: false,
|
||||
supportsConversationChaining: false,
|
||||
supportsPreviousResponseId: false
|
||||
},
|
||||
streaming: {
|
||||
supported: true,
|
||||
includesUsage: true
|
||||
}
|
||||
}
|
||||
|
||||
// Complete model capability mapping table
|
||||
export const MODEL_CAPABILITIES_REGISTRY: Record<string, ModelCapabilities> = {
|
||||
// GPT-5 series
|
||||
'gpt-5': GPT5_CAPABILITIES,
|
||||
'gpt-5-mini': GPT5_CAPABILITIES,
|
||||
'gpt-5-nano': GPT5_CAPABILITIES,
|
||||
'gpt-5-chat-latest': GPT5_CAPABILITIES,
|
||||
|
||||
// GPT-4 series
|
||||
'gpt-4o': CHAT_COMPLETIONS_CAPABILITIES,
|
||||
'gpt-4o-mini': CHAT_COMPLETIONS_CAPABILITIES,
|
||||
'gpt-4-turbo': CHAT_COMPLETIONS_CAPABILITIES,
|
||||
'gpt-4': CHAT_COMPLETIONS_CAPABILITIES,
|
||||
|
||||
// Claude series (supported through conversion layer)
|
||||
'claude-3-5-sonnet-20241022': CHAT_COMPLETIONS_CAPABILITIES,
|
||||
'claude-3-5-haiku-20241022': CHAT_COMPLETIONS_CAPABILITIES,
|
||||
'claude-3-opus-20240229': CHAT_COMPLETIONS_CAPABILITIES,
|
||||
|
||||
// O1 series (special reasoning models)
|
||||
'o1': {
|
||||
...CHAT_COMPLETIONS_CAPABILITIES,
|
||||
parameters: {
|
||||
...CHAT_COMPLETIONS_CAPABILITIES.parameters,
|
||||
maxTokensField: 'max_completion_tokens',
|
||||
temperatureMode: 'fixed_one'
|
||||
}
|
||||
},
|
||||
'o1-mini': {
|
||||
...CHAT_COMPLETIONS_CAPABILITIES,
|
||||
parameters: {
|
||||
...CHAT_COMPLETIONS_CAPABILITIES.parameters,
|
||||
maxTokensField: 'max_completion_tokens',
|
||||
temperatureMode: 'fixed_one'
|
||||
}
|
||||
},
|
||||
'o1-preview': {
|
||||
...CHAT_COMPLETIONS_CAPABILITIES,
|
||||
parameters: {
|
||||
...CHAT_COMPLETIONS_CAPABILITIES.parameters,
|
||||
maxTokensField: 'max_completion_tokens',
|
||||
temperatureMode: 'fixed_one'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Intelligently infer capabilities for unregistered models
|
||||
export function inferModelCapabilities(modelName: string): ModelCapabilities | null {
|
||||
if (!modelName) return null
|
||||
|
||||
const lowerName = modelName.toLowerCase()
|
||||
|
||||
// GPT-5 series
|
||||
if (lowerName.includes('gpt-5') || lowerName.includes('gpt5')) {
|
||||
return GPT5_CAPABILITIES
|
||||
}
|
||||
|
||||
// GPT-6 series (reserved for future)
|
||||
if (lowerName.includes('gpt-6') || lowerName.includes('gpt6')) {
|
||||
return {
|
||||
...GPT5_CAPABILITIES,
|
||||
streaming: { supported: true, includesUsage: true }
|
||||
}
|
||||
}
|
||||
|
||||
// GLM series
|
||||
if (lowerName.includes('glm-5') || lowerName.includes('glm5')) {
|
||||
return {
|
||||
...GPT5_CAPABILITIES,
|
||||
toolCalling: {
|
||||
...GPT5_CAPABILITIES.toolCalling,
|
||||
supportsAllowedTools: false // GLM might not support this
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// O1 series
|
||||
if (lowerName.startsWith('o1') || lowerName.includes('o1-')) {
|
||||
return {
|
||||
...CHAT_COMPLETIONS_CAPABILITIES,
|
||||
parameters: {
|
||||
...CHAT_COMPLETIONS_CAPABILITIES.parameters,
|
||||
maxTokensField: 'max_completion_tokens',
|
||||
temperatureMode: 'fixed_one'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default to null, let system use default behavior
|
||||
return null
|
||||
}
|
||||
|
||||
// Get model capabilities (with caching)
|
||||
const capabilityCache = new Map<string, ModelCapabilities>()
|
||||
|
||||
export function getModelCapabilities(modelName: string): ModelCapabilities {
|
||||
// Check cache
|
||||
if (capabilityCache.has(modelName)) {
|
||||
return capabilityCache.get(modelName)!
|
||||
}
|
||||
|
||||
// Look up in registry
|
||||
if (MODEL_CAPABILITIES_REGISTRY[modelName]) {
|
||||
const capabilities = MODEL_CAPABILITIES_REGISTRY[modelName]
|
||||
capabilityCache.set(modelName, capabilities)
|
||||
return capabilities
|
||||
}
|
||||
|
||||
// Try to infer
|
||||
const inferred = inferModelCapabilities(modelName)
|
||||
if (inferred) {
|
||||
capabilityCache.set(modelName, inferred)
|
||||
return inferred
|
||||
}
|
||||
|
||||
// Default to Chat Completions
|
||||
const defaultCapabilities = CHAT_COMPLETIONS_CAPABILITIES
|
||||
capabilityCache.set(modelName, defaultCapabilities)
|
||||
return defaultCapabilities
|
||||
}
|
||||
@ -80,6 +80,7 @@ export type AssistantMessage = {
|
||||
type: 'assistant'
|
||||
uuid: UUID
|
||||
isApiErrorMessage?: boolean
|
||||
responseId?: string // For GPT-5 Responses API state management
|
||||
}
|
||||
|
||||
export type BinaryFeedbackResult =
|
||||
@ -230,6 +231,7 @@ export async function* query(
|
||||
safeMode: toolUseContext.options.safeMode ?? false,
|
||||
model: toolUseContext.options.model || 'main',
|
||||
prependCLISysprompt: true,
|
||||
toolUseContext: toolUseContext,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
38
src/services/adapters/base.ts
Normal file
38
src/services/adapters/base.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { ModelCapabilities, UnifiedRequestParams, UnifiedResponse } from '../../types/modelCapabilities'
|
||||
import { ModelProfile } from '../../utils/config'
|
||||
import { Tool } from '../../Tool'
|
||||
|
||||
export abstract class ModelAPIAdapter {
|
||||
constructor(
|
||||
protected capabilities: ModelCapabilities,
|
||||
protected modelProfile: ModelProfile
|
||||
) {}
|
||||
|
||||
// Subclasses must implement these methods
|
||||
abstract createRequest(params: UnifiedRequestParams): any
|
||||
abstract parseResponse(response: any): UnifiedResponse
|
||||
abstract buildTools(tools: Tool[]): any
|
||||
|
||||
// Shared utility methods
|
||||
protected getMaxTokensParam(): string {
|
||||
return this.capabilities.parameters.maxTokensField
|
||||
}
|
||||
|
||||
protected getTemperature(): number {
|
||||
if (this.capabilities.parameters.temperatureMode === 'fixed_one') {
|
||||
return 1
|
||||
}
|
||||
if (this.capabilities.parameters.temperatureMode === 'restricted') {
|
||||
return Math.min(1, 0.7)
|
||||
}
|
||||
return 0.7
|
||||
}
|
||||
|
||||
protected shouldIncludeReasoningEffort(): boolean {
|
||||
return this.capabilities.parameters.supportsReasoningEffort
|
||||
}
|
||||
|
||||
protected shouldIncludeVerbosity(): boolean {
|
||||
return this.capabilities.parameters.supportsVerbosity
|
||||
}
|
||||
}
|
||||
90
src/services/adapters/chatCompletions.ts
Normal file
90
src/services/adapters/chatCompletions.ts
Normal file
@ -0,0 +1,90 @@
|
||||
import { ModelAPIAdapter } from './base'
|
||||
import { UnifiedRequestParams, UnifiedResponse } from '../../types/modelCapabilities'
|
||||
import { Tool } from '../../Tool'
|
||||
import { zodToJsonSchema } from 'zod-to-json-schema'
|
||||
|
||||
export class ChatCompletionsAdapter extends ModelAPIAdapter {
|
||||
createRequest(params: UnifiedRequestParams): any {
|
||||
const { messages, systemPrompt, tools, maxTokens, stream } = params
|
||||
|
||||
// Build complete message list (including system prompts)
|
||||
const fullMessages = this.buildMessages(systemPrompt, messages)
|
||||
|
||||
// Build request
|
||||
const request: any = {
|
||||
model: this.modelProfile.modelName,
|
||||
messages: fullMessages,
|
||||
[this.getMaxTokensParam()]: maxTokens,
|
||||
temperature: this.getTemperature()
|
||||
}
|
||||
|
||||
// Add tools
|
||||
if (tools && tools.length > 0) {
|
||||
request.tools = this.buildTools(tools)
|
||||
request.tool_choice = 'auto'
|
||||
}
|
||||
|
||||
// Add reasoning effort for GPT-5 via Chat Completions
|
||||
if (this.shouldIncludeReasoningEffort() && params.reasoningEffort) {
|
||||
request.reasoning_effort = params.reasoningEffort // Chat Completions format
|
||||
}
|
||||
|
||||
// Add verbosity for GPT-5 via Chat Completions
|
||||
if (this.shouldIncludeVerbosity() && params.verbosity) {
|
||||
request.verbosity = params.verbosity // Chat Completions format
|
||||
}
|
||||
|
||||
// Add streaming options
|
||||
if (stream) {
|
||||
request.stream = true
|
||||
request.stream_options = {
|
||||
include_usage: true
|
||||
}
|
||||
}
|
||||
|
||||
// O1 model special handling
|
||||
if (this.modelProfile.modelName.startsWith('o1')) {
|
||||
delete request.temperature // O1 doesn't support temperature
|
||||
delete request.stream // O1 doesn't support streaming
|
||||
delete request.stream_options
|
||||
}
|
||||
|
||||
return request
|
||||
}
|
||||
|
||||
buildTools(tools: Tool[]): any[] {
|
||||
// Chat Completions only supports traditional function calling
|
||||
return tools.map(tool => ({
|
||||
type: 'function',
|
||||
function: {
|
||||
name: tool.name,
|
||||
description: tool.description || '',
|
||||
parameters: tool.inputJSONSchema || zodToJsonSchema(tool.inputSchema)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
parseResponse(response: any): UnifiedResponse {
|
||||
const choice = response.choices?.[0]
|
||||
|
||||
return {
|
||||
id: response.id || `chatcmpl_${Date.now()}`,
|
||||
content: choice?.message?.content || '',
|
||||
toolCalls: choice?.message?.tool_calls || [],
|
||||
usage: {
|
||||
promptTokens: response.usage?.prompt_tokens || 0,
|
||||
completionTokens: response.usage?.completion_tokens || 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private buildMessages(systemPrompt: string[], messages: any[]): any[] {
|
||||
// Merge system prompts and messages
|
||||
const systemMessages = systemPrompt.map(prompt => ({
|
||||
role: 'system',
|
||||
content: prompt
|
||||
}))
|
||||
|
||||
return [...systemMessages, ...messages]
|
||||
}
|
||||
}
|
||||
170
src/services/adapters/responsesAPI.ts
Normal file
170
src/services/adapters/responsesAPI.ts
Normal file
@ -0,0 +1,170 @@
|
||||
import { ModelAPIAdapter } from './base'
|
||||
import { UnifiedRequestParams, UnifiedResponse } from '../../types/modelCapabilities'
|
||||
import { Tool } from '../../Tool'
|
||||
import { zodToJsonSchema } from 'zod-to-json-schema'
|
||||
|
||||
export class ResponsesAPIAdapter extends ModelAPIAdapter {
|
||||
createRequest(params: UnifiedRequestParams): any {
|
||||
const { messages, systemPrompt, tools, maxTokens } = params
|
||||
|
||||
// Separate system messages and user messages
|
||||
const systemMessages = messages.filter(m => m.role === 'system')
|
||||
const nonSystemMessages = messages.filter(m => m.role !== 'system')
|
||||
|
||||
// Build base request
|
||||
const request: any = {
|
||||
model: this.modelProfile.modelName,
|
||||
input: this.convertMessagesToInput(nonSystemMessages),
|
||||
instructions: this.buildInstructions(systemPrompt, systemMessages)
|
||||
}
|
||||
|
||||
// Add token limit
|
||||
request[this.getMaxTokensParam()] = maxTokens
|
||||
|
||||
// Add temperature (GPT-5 only supports 1)
|
||||
if (this.getTemperature() === 1) {
|
||||
request.temperature = 1
|
||||
}
|
||||
|
||||
// Add reasoning control - correct format for Responses API
|
||||
if (this.shouldIncludeReasoningEffort()) {
|
||||
request.reasoning = {
|
||||
effort: params.reasoningEffort || this.modelProfile.reasoningEffort || 'medium'
|
||||
}
|
||||
}
|
||||
|
||||
// Add verbosity control - correct format for Responses API
|
||||
if (this.shouldIncludeVerbosity()) {
|
||||
request.text = {
|
||||
verbosity: params.verbosity || 'high' // High verbosity for coding tasks
|
||||
}
|
||||
}
|
||||
|
||||
// Add tools
|
||||
if (tools && tools.length > 0) {
|
||||
request.tools = this.buildTools(tools)
|
||||
|
||||
// Handle allowed_tools
|
||||
if (params.allowedTools && this.capabilities.toolCalling.supportsAllowedTools) {
|
||||
request.tool_choice = {
|
||||
type: 'allowed_tools',
|
||||
mode: 'auto',
|
||||
tools: params.allowedTools
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add state management
|
||||
if (params.previousResponseId && this.capabilities.stateManagement.supportsPreviousResponseId) {
|
||||
request.previous_response_id = params.previousResponseId
|
||||
}
|
||||
|
||||
return request
|
||||
}
|
||||
|
||||
buildTools(tools: Tool[]): any[] {
|
||||
// If freeform not supported, use traditional format
|
||||
if (!this.capabilities.toolCalling.supportsFreeform) {
|
||||
return tools.map(tool => ({
|
||||
type: 'function',
|
||||
function: {
|
||||
name: tool.name,
|
||||
description: tool.description || '',
|
||||
parameters: tool.inputJSONSchema || zodToJsonSchema(tool.inputSchema)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
// Custom tools format (GPT-5 feature)
|
||||
return tools.map(tool => {
|
||||
const hasSchema = tool.inputJSONSchema || tool.inputSchema
|
||||
const isCustom = !hasSchema
|
||||
|
||||
if (isCustom) {
|
||||
// Custom tool format
|
||||
return {
|
||||
type: 'custom',
|
||||
name: tool.name,
|
||||
description: tool.description || ''
|
||||
}
|
||||
} else {
|
||||
// Traditional function format
|
||||
return {
|
||||
type: 'function',
|
||||
function: {
|
||||
name: tool.name,
|
||||
description: tool.description || '',
|
||||
parameters: tool.inputJSONSchema || zodToJsonSchema(tool.inputSchema)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
parseResponse(response: any): UnifiedResponse {
|
||||
// Process basic text output
|
||||
let content = response.output_text || ''
|
||||
|
||||
// Process structured output
|
||||
if (response.output && Array.isArray(response.output)) {
|
||||
const messageItems = response.output.filter(item => item.type === 'message')
|
||||
if (messageItems.length > 0) {
|
||||
content = messageItems
|
||||
.map(item => {
|
||||
if (item.content && Array.isArray(item.content)) {
|
||||
return item.content
|
||||
.filter(c => c.type === 'text')
|
||||
.map(c => c.text)
|
||||
.join('\n')
|
||||
}
|
||||
return item.content || ''
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join('\n\n')
|
||||
}
|
||||
}
|
||||
|
||||
// Parse tool calls
|
||||
const toolCalls = this.parseToolCalls(response)
|
||||
|
||||
// Build unified response
|
||||
return {
|
||||
id: response.id || `resp_${Date.now()}`,
|
||||
content,
|
||||
toolCalls,
|
||||
usage: {
|
||||
promptTokens: response.usage?.input_tokens || 0,
|
||||
completionTokens: response.usage?.output_tokens || 0,
|
||||
reasoningTokens: response.usage?.output_tokens_details?.reasoning_tokens
|
||||
},
|
||||
responseId: response.id // Save for state management
|
||||
}
|
||||
}
|
||||
|
||||
private convertMessagesToInput(messages: any[]): any {
|
||||
// Convert messages to Responses API input format
|
||||
// May need adjustment based on actual API specification
|
||||
return messages
|
||||
}
|
||||
|
||||
private buildInstructions(systemPrompt: string[], systemMessages: any[]): string {
|
||||
const systemContent = systemMessages.map(m => m.content).join('\n\n')
|
||||
const promptContent = systemPrompt.join('\n\n')
|
||||
return [systemContent, promptContent].filter(Boolean).join('\n\n')
|
||||
}
|
||||
|
||||
private parseToolCalls(response: any): any[] {
|
||||
if (!response.output || !Array.isArray(response.output)) {
|
||||
return []
|
||||
}
|
||||
|
||||
return response.output
|
||||
.filter(item => item.type === 'tool_call')
|
||||
.map(item => ({
|
||||
id: item.id || `tool_${Date.now()}`,
|
||||
type: 'tool_call',
|
||||
name: item.name,
|
||||
arguments: item.arguments // Can be text or JSON
|
||||
}))
|
||||
}
|
||||
}
|
||||
@ -42,6 +42,10 @@ import {
|
||||
import { getModelManager } from '../utils/model'
|
||||
import { zodToJsonSchema } from 'zod-to-json-schema'
|
||||
import type { BetaMessageStream } from '@anthropic-ai/sdk/lib/BetaMessageStream.mjs'
|
||||
import { ModelAdapterFactory } from './modelAdapterFactory'
|
||||
import { UnifiedRequestParams } from '../types/modelCapabilities'
|
||||
import { responseStateManager, getConversationId } from './responseStateManager'
|
||||
import type { ToolUseContext } from '../Tool'
|
||||
import type {
|
||||
Message as APIMessage,
|
||||
MessageParam,
|
||||
@ -1053,6 +1057,7 @@ export async function queryLLM(
|
||||
safeMode: boolean
|
||||
model: string | import('../utils/config').ModelPointerType
|
||||
prependCLISysprompt: boolean
|
||||
toolUseContext?: ToolUseContext
|
||||
},
|
||||
): Promise<AssistantMessage> {
|
||||
// 🔧 统一的模型解析:支持指针、model ID 和真实模型名称
|
||||
@ -1068,11 +1073,25 @@ export async function queryLLM(
|
||||
const modelProfile = modelResolution.profile
|
||||
const resolvedModel = modelProfile.modelName
|
||||
|
||||
// Initialize response state if toolUseContext is provided
|
||||
const toolUseContext = options.toolUseContext
|
||||
if (toolUseContext && !toolUseContext.responseState) {
|
||||
const conversationId = getConversationId(toolUseContext.agentId, toolUseContext.messageId)
|
||||
const previousResponseId = responseStateManager.getPreviousResponseId(conversationId)
|
||||
|
||||
toolUseContext.responseState = {
|
||||
previousResponseId,
|
||||
conversationId
|
||||
}
|
||||
}
|
||||
|
||||
debugLogger.api('MODEL_RESOLVED', {
|
||||
inputParam: options.model,
|
||||
resolvedModelName: resolvedModel,
|
||||
provider: modelProfile.provider,
|
||||
isPointer: ['main', 'task', 'reasoning', 'quick'].includes(options.model),
|
||||
hasResponseState: !!toolUseContext?.responseState,
|
||||
conversationId: toolUseContext?.responseState?.conversationId,
|
||||
requestId: getCurrentRequest()?.id,
|
||||
})
|
||||
|
||||
@ -1096,7 +1115,7 @@ export async function queryLLM(
|
||||
maxThinkingTokens,
|
||||
tools,
|
||||
signal,
|
||||
{ ...options, model: resolvedModel, modelProfile }, // Pass resolved ModelProfile
|
||||
{ ...options, model: resolvedModel, modelProfile, toolUseContext }, // Pass resolved ModelProfile and toolUseContext
|
||||
),
|
||||
)
|
||||
|
||||
@ -1107,6 +1126,20 @@ export async function queryLLM(
|
||||
requestId: getCurrentRequest()?.id,
|
||||
})
|
||||
|
||||
// Update response state for GPT-5 Responses API continuation
|
||||
if (toolUseContext?.responseState?.conversationId && result.responseId) {
|
||||
responseStateManager.setPreviousResponseId(
|
||||
toolUseContext.responseState.conversationId,
|
||||
result.responseId
|
||||
)
|
||||
|
||||
debugLogger.api('RESPONSE_STATE_UPDATED', {
|
||||
conversationId: toolUseContext.responseState.conversationId,
|
||||
responseId: result.responseId,
|
||||
requestId: getCurrentRequest()?.id,
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
} catch (error) {
|
||||
// 使用错误诊断系统记录 LLM 相关错误
|
||||
@ -1136,6 +1169,24 @@ export function formatSystemPromptWithContext(
|
||||
const enhancedPrompt = [...systemPrompt]
|
||||
let reminders = ''
|
||||
|
||||
// Step 0: Add GPT-5 Agent persistence support for coding tasks
|
||||
const modelManager = getModelManager()
|
||||
const modelProfile = modelManager.getModel('main')
|
||||
if (modelProfile && isGPT5Model(modelProfile.modelName)) {
|
||||
// Add coding-specific persistence instructions based on GPT-5 documentation
|
||||
const persistencePrompts = [
|
||||
"\n# Agent Persistence for Long-Running Coding Tasks",
|
||||
"You are working on a coding project that may involve multiple steps and iterations. Please maintain context and continuity throughout the session:",
|
||||
"- Remember architectural decisions and design patterns established earlier",
|
||||
"- Keep track of file modifications and their relationships",
|
||||
"- Maintain awareness of the overall project structure and goals",
|
||||
"- Reference previous implementations when making related changes",
|
||||
"- Ensure consistency with existing code style and conventions",
|
||||
"- Build incrementally on previous work rather than starting from scratch"
|
||||
]
|
||||
enhancedPrompt.push(...persistencePrompts)
|
||||
}
|
||||
|
||||
// 只有当上下文存在时才处理
|
||||
const hasContext = Object.entries(context).length > 0
|
||||
|
||||
@ -1190,10 +1241,12 @@ async function queryLLMWithPromptCaching(
|
||||
model: string
|
||||
prependCLISysprompt: boolean
|
||||
modelProfile?: ModelProfile | null
|
||||
toolUseContext?: ToolUseContext
|
||||
},
|
||||
): Promise<AssistantMessage> {
|
||||
const config = getGlobalConfig()
|
||||
const modelManager = getModelManager()
|
||||
const toolUseContext = options.toolUseContext
|
||||
|
||||
// 🔧 Fix: 使用传入的ModelProfile,而不是硬编码的'main'指针
|
||||
const modelProfile = options.modelProfile || modelManager.getModel('main')
|
||||
@ -1217,7 +1270,7 @@ async function queryLLMWithPromptCaching(
|
||||
maxThinkingTokens,
|
||||
tools,
|
||||
signal,
|
||||
{ ...options, modelProfile },
|
||||
{ ...options, modelProfile, toolUseContext },
|
||||
)
|
||||
}
|
||||
|
||||
@ -1225,6 +1278,7 @@ async function queryLLMWithPromptCaching(
|
||||
return queryOpenAI(messages, systemPrompt, maxThinkingTokens, tools, signal, {
|
||||
...options,
|
||||
modelProfile,
|
||||
toolUseContext,
|
||||
})
|
||||
}
|
||||
|
||||
@ -1239,10 +1293,12 @@ async function queryAnthropicNative(
|
||||
model: string
|
||||
prependCLISysprompt: boolean
|
||||
modelProfile?: ModelProfile | null
|
||||
toolUseContext?: ToolUseContext
|
||||
},
|
||||
): Promise<AssistantMessage> {
|
||||
const config = getGlobalConfig()
|
||||
const modelManager = getModelManager()
|
||||
const toolUseContext = options?.toolUseContext
|
||||
|
||||
// 🔧 Fix: 使用传入的ModelProfile,而不是硬编码的'main'指针
|
||||
const modelProfile = options?.modelProfile || modelManager.getModel('main')
|
||||
@ -1642,10 +1698,12 @@ async function queryOpenAI(
|
||||
model: string
|
||||
prependCLISysprompt: boolean
|
||||
modelProfile?: ModelProfile | null
|
||||
toolUseContext?: ToolUseContext
|
||||
},
|
||||
): Promise<AssistantMessage> {
|
||||
const config = getGlobalConfig()
|
||||
const modelManager = getModelManager()
|
||||
const toolUseContext = options?.toolUseContext
|
||||
|
||||
// 🔧 Fix: 使用传入的ModelProfile,而不是硬编码的'main'指针
|
||||
const modelProfile = options?.modelProfile || modelManager.getModel('main')
|
||||
@ -1784,20 +1842,82 @@ async function queryOpenAI(
|
||||
requestId: getCurrentRequest()?.id,
|
||||
})
|
||||
|
||||
// Use enhanced GPT-5 function for GPT-5 models, fallback to regular function for others
|
||||
const completionFunction = isGPT5Model(modelProfile.modelName)
|
||||
? getGPT5CompletionWithProfile
|
||||
: getCompletionWithProfile
|
||||
const s = await completionFunction(modelProfile, opts, 0, 10, signal) // 🔧 CRITICAL FIX: Pass AbortSignal to OpenAI calls
|
||||
let finalResponse
|
||||
if (opts.stream) {
|
||||
finalResponse = await handleMessageStream(s as ChatCompletionStream, signal) // 🔧 Pass AbortSignal to stream handler
|
||||
// Enable new adapter system with environment variable
|
||||
const USE_NEW_ADAPTER_SYSTEM = process.env.USE_NEW_ADAPTERS !== 'false'
|
||||
|
||||
if (USE_NEW_ADAPTER_SYSTEM) {
|
||||
// New adapter system
|
||||
const adapter = ModelAdapterFactory.createAdapter(modelProfile)
|
||||
|
||||
// Build unified request parameters
|
||||
const unifiedParams: UnifiedRequestParams = {
|
||||
messages: openaiMessages,
|
||||
systemPrompt: openaiSystem.map(s => s.content as string),
|
||||
tools: tools,
|
||||
maxTokens: getMaxTokensFromProfile(modelProfile),
|
||||
stream: config.stream,
|
||||
reasoningEffort: reasoningEffort as any,
|
||||
temperature: isGPT5Model(model) ? 1 : MAIN_QUERY_TEMPERATURE,
|
||||
previousResponseId: toolUseContext?.responseState?.previousResponseId,
|
||||
verbosity: 'high' // High verbosity for coding tasks
|
||||
}
|
||||
|
||||
// Create request using adapter
|
||||
const request = adapter.createRequest(unifiedParams)
|
||||
|
||||
// Determine which API to use
|
||||
if (ModelAdapterFactory.shouldUseResponsesAPI(modelProfile)) {
|
||||
// Use Responses API for GPT-5 and similar models
|
||||
const { callGPT5ResponsesAPI } = await import('./openai')
|
||||
const response = await callGPT5ResponsesAPI(modelProfile, request, signal)
|
||||
const unifiedResponse = adapter.parseResponse(response)
|
||||
|
||||
// Convert unified response back to Anthropic format
|
||||
const apiMessage = {
|
||||
role: 'assistant' as const,
|
||||
content: unifiedResponse.content,
|
||||
tool_calls: unifiedResponse.toolCalls,
|
||||
usage: {
|
||||
prompt_tokens: unifiedResponse.usage.promptTokens,
|
||||
completion_tokens: unifiedResponse.usage.completionTokens,
|
||||
}
|
||||
}
|
||||
const assistantMsg: AssistantMessage = {
|
||||
type: 'assistant',
|
||||
message: apiMessage as any,
|
||||
costUSD: 0, // Will be calculated later
|
||||
durationMs: Date.now() - start,
|
||||
uuid: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}` as any,
|
||||
responseId: unifiedResponse.responseId // For state management
|
||||
}
|
||||
return assistantMsg
|
||||
} else {
|
||||
// Use existing Chat Completions flow
|
||||
const s = await getCompletionWithProfile(modelProfile, request, 0, 10, signal)
|
||||
let finalResponse
|
||||
if (config.stream) {
|
||||
finalResponse = await handleMessageStream(s as ChatCompletionStream, signal)
|
||||
} else {
|
||||
finalResponse = s
|
||||
}
|
||||
const r = convertOpenAIResponseToAnthropic(finalResponse)
|
||||
return r
|
||||
}
|
||||
} else {
|
||||
finalResponse = s
|
||||
// Legacy system (preserved for fallback)
|
||||
const completionFunction = isGPT5Model(modelProfile.modelName)
|
||||
? getGPT5CompletionWithProfile
|
||||
: getCompletionWithProfile
|
||||
const s = await completionFunction(modelProfile, opts, 0, 10, signal)
|
||||
let finalResponse
|
||||
if (opts.stream) {
|
||||
finalResponse = await handleMessageStream(s as ChatCompletionStream, signal)
|
||||
} else {
|
||||
finalResponse = s
|
||||
}
|
||||
const r = convertOpenAIResponseToAnthropic(finalResponse)
|
||||
return r
|
||||
}
|
||||
|
||||
const r = convertOpenAIResponseToAnthropic(finalResponse)
|
||||
return r
|
||||
} else {
|
||||
// 🚨 警告:ModelProfile不可用,使用旧逻辑路径
|
||||
debugLogger.api('USING_LEGACY_PATH', {
|
||||
|
||||
69
src/services/modelAdapterFactory.ts
Normal file
69
src/services/modelAdapterFactory.ts
Normal file
@ -0,0 +1,69 @@
|
||||
import { ModelAPIAdapter } from './adapters/base'
|
||||
import { ResponsesAPIAdapter } from './adapters/responsesAPI'
|
||||
import { ChatCompletionsAdapter } from './adapters/chatCompletions'
|
||||
import { getModelCapabilities } from '../constants/modelCapabilities'
|
||||
import { ModelProfile, getGlobalConfig } from '../utils/config'
|
||||
import { ModelCapabilities } from '../types/modelCapabilities'
|
||||
|
||||
export class ModelAdapterFactory {
|
||||
/**
|
||||
* Create appropriate adapter based on model configuration
|
||||
*/
|
||||
static createAdapter(modelProfile: ModelProfile): ModelAPIAdapter {
|
||||
const capabilities = getModelCapabilities(modelProfile.modelName)
|
||||
|
||||
// Determine which API to use
|
||||
const apiType = this.determineAPIType(modelProfile, capabilities)
|
||||
|
||||
// Create corresponding adapter
|
||||
switch (apiType) {
|
||||
case 'responses_api':
|
||||
return new ResponsesAPIAdapter(capabilities, modelProfile)
|
||||
case 'chat_completions':
|
||||
default:
|
||||
return new ChatCompletionsAdapter(capabilities, modelProfile)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine which API should be used
|
||||
*/
|
||||
private static determineAPIType(
|
||||
modelProfile: ModelProfile,
|
||||
capabilities: ModelCapabilities
|
||||
): 'responses_api' | 'chat_completions' {
|
||||
// If model doesn't support Responses API, use Chat Completions directly
|
||||
if (capabilities.apiArchitecture.primary !== 'responses_api') {
|
||||
return 'chat_completions'
|
||||
}
|
||||
|
||||
// Check if this is official OpenAI endpoint
|
||||
const isOfficialOpenAI = !modelProfile.baseURL ||
|
||||
modelProfile.baseURL.includes('api.openai.com')
|
||||
|
||||
// Non-official endpoints use Chat Completions (even if model supports Responses API)
|
||||
if (!isOfficialOpenAI) {
|
||||
// If there's a fallback option, use fallback
|
||||
if (capabilities.apiArchitecture.fallback === 'chat_completions') {
|
||||
return 'chat_completions'
|
||||
}
|
||||
// Otherwise use primary (might fail, but let it try)
|
||||
return capabilities.apiArchitecture.primary
|
||||
}
|
||||
|
||||
// For now, always use Responses API for supported models when on official endpoint
|
||||
// Streaming fallback will be handled at runtime if needed
|
||||
|
||||
// Use primary API type
|
||||
return capabilities.apiArchitecture.primary
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if model should use Responses API
|
||||
*/
|
||||
static shouldUseResponsesAPI(modelProfile: ModelProfile): boolean {
|
||||
const capabilities = getModelCapabilities(modelProfile.modelName)
|
||||
const apiType = this.determineAPIType(modelProfile, capabilities)
|
||||
return apiType === 'responses_api'
|
||||
}
|
||||
}
|
||||
@ -906,7 +906,7 @@ export function streamCompletion(
|
||||
/**
|
||||
* Call GPT-5 Responses API with proper parameter handling
|
||||
*/
|
||||
async function callGPT5ResponsesAPI(
|
||||
export async function callGPT5ResponsesAPI(
|
||||
modelProfile: any,
|
||||
opts: any, // Using 'any' for Responses API params which differ from ChatCompletionCreateParams
|
||||
signal?: AbortSignal,
|
||||
|
||||
90
src/services/responseStateManager.ts
Normal file
90
src/services/responseStateManager.ts
Normal file
@ -0,0 +1,90 @@
|
||||
/**
|
||||
* GPT-5 Responses API state management
|
||||
* Manages previous_response_id for conversation continuity and reasoning context reuse
|
||||
*/
|
||||
|
||||
interface ConversationState {
|
||||
previousResponseId?: string
|
||||
lastUpdate: number
|
||||
}
|
||||
|
||||
class ResponseStateManager {
|
||||
private conversationStates = new Map<string, ConversationState>()
|
||||
|
||||
// Cache cleanup after 1 hour of inactivity
|
||||
private readonly CLEANUP_INTERVAL = 60 * 60 * 1000
|
||||
|
||||
constructor() {
|
||||
// Periodic cleanup of stale conversations
|
||||
setInterval(() => {
|
||||
this.cleanup()
|
||||
}, this.CLEANUP_INTERVAL)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the previous response ID for a conversation
|
||||
*/
|
||||
setPreviousResponseId(conversationId: string, responseId: string): void {
|
||||
this.conversationStates.set(conversationId, {
|
||||
previousResponseId: responseId,
|
||||
lastUpdate: Date.now()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the previous response ID for a conversation
|
||||
*/
|
||||
getPreviousResponseId(conversationId: string): string | undefined {
|
||||
const state = this.conversationStates.get(conversationId)
|
||||
if (state) {
|
||||
// Update last access time
|
||||
state.lastUpdate = Date.now()
|
||||
return state.previousResponseId
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear state for a conversation
|
||||
*/
|
||||
clearConversation(conversationId: string): void {
|
||||
this.conversationStates.delete(conversationId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all conversation states
|
||||
*/
|
||||
clearAll(): void {
|
||||
this.conversationStates.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up stale conversations
|
||||
*/
|
||||
private cleanup(): void {
|
||||
const now = Date.now()
|
||||
for (const [conversationId, state] of this.conversationStates.entries()) {
|
||||
if (now - state.lastUpdate > this.CLEANUP_INTERVAL) {
|
||||
this.conversationStates.delete(conversationId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current state size (for debugging/monitoring)
|
||||
*/
|
||||
getStateSize(): number {
|
||||
return this.conversationStates.size
|
||||
}
|
||||
}
|
||||
|
||||
// Singleton instance
|
||||
export const responseStateManager = new ResponseStateManager()
|
||||
|
||||
/**
|
||||
* Helper to generate conversation ID from context
|
||||
*/
|
||||
export function getConversationId(agentId?: string, messageId?: string): string {
|
||||
// Use agentId as primary identifier, fallback to messageId or timestamp
|
||||
return agentId || messageId || `conv_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
|
||||
}
|
||||
96
src/test/testAdapters.ts
Normal file
96
src/test/testAdapters.ts
Normal file
@ -0,0 +1,96 @@
|
||||
import { ModelAdapterFactory } from '../services/modelAdapterFactory'
|
||||
import { getModelCapabilities } from '../constants/modelCapabilities'
|
||||
import { ModelProfile } from '../utils/config'
|
||||
|
||||
// Test different models' adapter selection
|
||||
const testModels: ModelProfile[] = [
|
||||
{
|
||||
name: 'GPT-5 Test',
|
||||
modelName: 'gpt-5',
|
||||
provider: 'openai',
|
||||
apiKey: 'test-key',
|
||||
maxTokens: 8192,
|
||||
contextLength: 128000,
|
||||
reasoningEffort: 'medium',
|
||||
isActive: true,
|
||||
createdAt: Date.now()
|
||||
},
|
||||
{
|
||||
name: 'GPT-4o Test',
|
||||
modelName: 'gpt-4o',
|
||||
provider: 'openai',
|
||||
apiKey: 'test-key',
|
||||
maxTokens: 4096,
|
||||
contextLength: 128000,
|
||||
isActive: true,
|
||||
createdAt: Date.now()
|
||||
},
|
||||
{
|
||||
name: 'Claude Test',
|
||||
modelName: 'claude-3-5-sonnet-20241022',
|
||||
provider: 'anthropic',
|
||||
apiKey: 'test-key',
|
||||
maxTokens: 4096,
|
||||
contextLength: 200000,
|
||||
isActive: true,
|
||||
createdAt: Date.now()
|
||||
},
|
||||
{
|
||||
name: 'O1 Test',
|
||||
modelName: 'o1',
|
||||
provider: 'openai',
|
||||
apiKey: 'test-key',
|
||||
maxTokens: 4096,
|
||||
contextLength: 128000,
|
||||
isActive: true,
|
||||
createdAt: Date.now()
|
||||
},
|
||||
{
|
||||
name: 'GLM-5 Test',
|
||||
modelName: 'glm-5',
|
||||
provider: 'custom',
|
||||
apiKey: 'test-key',
|
||||
maxTokens: 8192,
|
||||
contextLength: 128000,
|
||||
baseURL: 'https://api.glm.ai/v1',
|
||||
isActive: true,
|
||||
createdAt: Date.now()
|
||||
}
|
||||
]
|
||||
|
||||
console.log('🧪 Testing Model Adapter System\n')
|
||||
console.log('=' .repeat(60))
|
||||
|
||||
testModels.forEach(model => {
|
||||
console.log(`\n📊 Testing: ${model.name} (${model.modelName})`)
|
||||
console.log('-'.repeat(40))
|
||||
|
||||
// Get capabilities
|
||||
const capabilities = getModelCapabilities(model.modelName)
|
||||
console.log(` ✓ API Architecture: ${capabilities.apiArchitecture.primary}`)
|
||||
console.log(` ✓ Fallback: ${capabilities.apiArchitecture.fallback || 'none'}`)
|
||||
console.log(` ✓ Max Tokens Field: ${capabilities.parameters.maxTokensField}`)
|
||||
console.log(` ✓ Tool Calling Mode: ${capabilities.toolCalling.mode}`)
|
||||
console.log(` ✓ Supports Freeform: ${capabilities.toolCalling.supportsFreeform}`)
|
||||
console.log(` ✓ Supports Streaming: ${capabilities.streaming.supported}`)
|
||||
|
||||
// Test adapter creation
|
||||
const adapter = ModelAdapterFactory.createAdapter(model)
|
||||
console.log(` ✓ Adapter Type: ${adapter.constructor.name}`)
|
||||
|
||||
// Test shouldUseResponsesAPI
|
||||
const shouldUseResponses = ModelAdapterFactory.shouldUseResponsesAPI(model)
|
||||
console.log(` ✓ Should Use Responses API: ${shouldUseResponses}`)
|
||||
|
||||
// Test with custom endpoint
|
||||
if (model.baseURL) {
|
||||
const customModel = { ...model, baseURL: 'https://custom.api.com/v1' }
|
||||
const customShouldUseResponses = ModelAdapterFactory.shouldUseResponsesAPI(customModel)
|
||||
console.log(` ✓ With Custom Endpoint: ${customShouldUseResponses ? 'Responses API' : 'Chat Completions'}`)
|
||||
}
|
||||
})
|
||||
|
||||
console.log('\n' + '='.repeat(60))
|
||||
console.log('✅ Adapter System Test Complete!')
|
||||
console.log('\nTo enable the new system, set USE_NEW_ADAPTERS=true')
|
||||
console.log('To use legacy system, set USE_NEW_ADAPTERS=false')
|
||||
64
src/types/modelCapabilities.ts
Normal file
64
src/types/modelCapabilities.ts
Normal file
@ -0,0 +1,64 @@
|
||||
// Model capability type definitions for unified API support
|
||||
export interface ModelCapabilities {
|
||||
// API architecture type
|
||||
apiArchitecture: {
|
||||
primary: 'chat_completions' | 'responses_api'
|
||||
fallback?: 'chat_completions' // Responses API models can fallback
|
||||
}
|
||||
|
||||
// Parameter mapping
|
||||
parameters: {
|
||||
maxTokensField: 'max_tokens' | 'max_completion_tokens'
|
||||
supportsReasoningEffort: boolean
|
||||
supportsVerbosity: boolean
|
||||
temperatureMode: 'flexible' | 'fixed_one' | 'restricted'
|
||||
}
|
||||
|
||||
// Tool calling capabilities
|
||||
toolCalling: {
|
||||
mode: 'none' | 'function_calling' | 'custom_tools'
|
||||
supportsFreeform: boolean
|
||||
supportsAllowedTools: boolean
|
||||
supportsParallelCalls: boolean
|
||||
}
|
||||
|
||||
// State management
|
||||
stateManagement: {
|
||||
supportsResponseId: boolean
|
||||
supportsConversationChaining: boolean
|
||||
supportsPreviousResponseId: boolean
|
||||
}
|
||||
|
||||
// Streaming support
|
||||
streaming: {
|
||||
supported: boolean
|
||||
includesUsage: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// Unified request parameters
|
||||
export interface UnifiedRequestParams {
|
||||
messages: any[]
|
||||
systemPrompt: string[]
|
||||
tools?: any[]
|
||||
maxTokens: number
|
||||
stream?: boolean
|
||||
previousResponseId?: string
|
||||
reasoningEffort?: 'minimal' | 'low' | 'medium' | 'high'
|
||||
verbosity?: 'low' | 'medium' | 'high'
|
||||
temperature?: number
|
||||
allowedTools?: string[]
|
||||
}
|
||||
|
||||
// Unified response format
|
||||
export interface UnifiedResponse {
|
||||
id: string
|
||||
content: string
|
||||
toolCalls?: any[]
|
||||
usage: {
|
||||
promptTokens: number
|
||||
completionTokens: number
|
||||
reasoningTokens?: number
|
||||
}
|
||||
responseId?: string // For Responses API state management
|
||||
}
|
||||
23
src/utils/responseState.ts
Normal file
23
src/utils/responseState.ts
Normal file
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Response state management for Responses API
|
||||
* Tracks previous_response_id for conversation chaining
|
||||
*/
|
||||
|
||||
// Store the last response ID for each conversation
|
||||
const responseIdCache = new Map<string, string>()
|
||||
|
||||
export function getLastResponseId(conversationId: string): string | undefined {
|
||||
return responseIdCache.get(conversationId)
|
||||
}
|
||||
|
||||
export function setLastResponseId(conversationId: string, responseId: string): void {
|
||||
responseIdCache.set(conversationId, responseId)
|
||||
}
|
||||
|
||||
export function clearResponseId(conversationId: string): void {
|
||||
responseIdCache.delete(conversationId)
|
||||
}
|
||||
|
||||
export function clearAllResponseIds(): void {
|
||||
responseIdCache.clear()
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user