feat: add thinking mode support with configurable output formats

This commit is contained in:
Quorinex
2026-02-04 17:42:30 +08:00
parent c9891b60fd
commit 01e9d0577c
9 changed files with 877 additions and 113 deletions

View File

@@ -30,18 +30,41 @@ var modelMap = map[string]string{
"gpt-3.5-turbo": "claude-sonnet-4.5",
}
func MapModel(model string) string {
// Thinking 模式提示
const ThinkingModePrompt = `<thinking_mode>enabled</thinking_mode>
<max_thinking_length>200000</max_thinking_length>`
// ParseModelAndThinking 解析模型名称,返回实际模型和是否启用 thinking
func ParseModelAndThinking(model string, thinkingSuffix string) (string, bool) {
lower := strings.ToLower(model)
thinking := false
// 使用配置的后缀检查
suffixLower := strings.ToLower(thinkingSuffix)
if strings.HasSuffix(lower, suffixLower) {
thinking = true
model = model[:len(model)-len(thinkingSuffix)]
lower = strings.ToLower(model)
}
// 映射模型
for k, v := range modelMap {
if strings.Contains(lower, k) {
return v
return v, thinking
}
}
// 如果已经是有效的 Kiro 模型,直接返回
if strings.HasPrefix(lower, "claude-") {
return model
return model, thinking
}
return "claude-sonnet-4.5"
return "claude-sonnet-4.5", thinking
}
func MapModel(model string) string {
mapped, _ := ParseModelAndThinking(model, "-thinking")
return mapped
}
// ==================== Claude API 类型 ====================
@@ -106,13 +129,18 @@ type ClaudeUsage struct {
const maxToolDescLen = 10237
func ClaudeToKiro(req *ClaudeRequest) *KiroPayload {
func ClaudeToKiro(req *ClaudeRequest, thinking bool) *KiroPayload {
modelID := MapModel(req.Model)
origin := "AI_EDITOR"
// 提取系统提示
systemPrompt := extractSystemPrompt(req.System)
// 如果启用 thinking 模式,注入 thinking 提示
if thinking {
systemPrompt = ThinkingModePrompt + "\n\n" + systemPrompt
}
// 注入时间戳
timestamp := time.Now().Format(time.RFC3339)
systemPrompt = "[Context: Current time is " + timestamp + "]\n\n" + systemPrompt
@@ -507,7 +535,7 @@ type OpenAIUsage struct {
// ==================== OpenAI -> Kiro 转换 ====================
func OpenAIToKiro(req *OpenAIRequest) *KiroPayload {
func OpenAIToKiro(req *OpenAIRequest, thinking bool) *KiroPayload {
modelID := MapModel(req.Model)
origin := "AI_EDITOR"
@@ -525,6 +553,11 @@ func OpenAIToKiro(req *OpenAIRequest) *KiroPayload {
}
}
// 如果启用 thinking 模式,注入 thinking 提示
if thinking {
systemPrompt = ThinkingModePrompt + "\n\n" + systemPrompt
}
// 注入时间戳
timestamp := time.Now().Format(time.RFC3339)
systemPrompt = "[Context: Current time is " + timestamp + "]\n\n" + systemPrompt
@@ -809,3 +842,89 @@ func KiroToOpenAIResponse(content string, toolUses []KiroToolUse, inputTokens, o
},
}
}
// extractThinkingFromContent 从内容中提取 <thinking> 标签内的内容
func extractThinkingFromContent(content string) (string, string) {
var reasoning string
result := content
for {
start := strings.Index(result, "<thinking>")
if start == -1 {
break
}
end := strings.Index(result[start:], "</thinking>")
if end == -1 {
break
}
end += start
// 提取 thinking 内容
thinkingContent := result[start+10 : end]
reasoning += thinkingContent
// 从结果中移除 thinking 标签
result = result[:start] + result[end+11:]
}
return strings.TrimSpace(result), reasoning
}
// KiroToOpenAIResponseWithReasoning 带 reasoning_content 的 OpenAI 响应
func KiroToOpenAIResponseWithReasoning(content, reasoningContent string, toolUses []KiroToolUse, inputTokens, outputTokens int, model, thinkingFormat string) map[string]interface{} {
finishReason := "stop"
message := map[string]interface{}{
"role": "assistant",
}
if len(toolUses) > 0 {
message["content"] = nil
toolCalls := make([]map[string]interface{}, len(toolUses))
for i, tu := range toolUses {
args, _ := json.Marshal(tu.Input)
toolCalls[i] = map[string]interface{}{
"id": tu.ToolUseID,
"type": "function",
"function": map[string]string{
"name": tu.Name,
"arguments": string(args),
},
}
}
message["tool_calls"] = toolCalls
finishReason = "tool_calls"
} else {
// 根据配置格式化 thinking 输出
if reasoningContent != "" {
switch thinkingFormat {
case "thinking":
message["content"] = "<thinking>" + reasoningContent + "</thinking>" + content
case "think":
message["content"] = "<think>" + reasoningContent + "</think>" + content
default: // "reasoning_content"
message["content"] = content
message["reasoning_content"] = reasoningContent
}
} else {
message["content"] = content
}
}
return map[string]interface{}{
"id": "chatcmpl-" + uuid.New().String(),
"object": "chat.completion",
"created": time.Now().Unix(),
"model": model,
"choices": []map[string]interface{}{{
"index": 0,
"message": message,
"finish_reason": finishReason,
}},
"usage": map[string]int{
"prompt_tokens": inputTokens,
"completion_tokens": outputTokens,
"total_tokens": inputTokens + outputTokens,
},
}
}