relay-claude.go 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883
  1. package claude
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "io"
  6. "net/http"
  7. "strings"
  8. "github.com/QuantumNous/new-api/common"
  9. "github.com/QuantumNous/new-api/constant"
  10. "github.com/QuantumNous/new-api/dto"
  11. "github.com/QuantumNous/new-api/logger"
  12. "github.com/QuantumNous/new-api/relay/channel/openrouter"
  13. relaycommon "github.com/QuantumNous/new-api/relay/common"
  14. "github.com/QuantumNous/new-api/relay/helper"
  15. "github.com/QuantumNous/new-api/relay/reasonmap"
  16. "github.com/QuantumNous/new-api/service"
  17. "github.com/QuantumNous/new-api/setting/model_setting"
  18. "github.com/QuantumNous/new-api/types"
  19. "github.com/gin-gonic/gin"
  20. )
  21. const (
  22. WebSearchMaxUsesLow = 1
  23. WebSearchMaxUsesMedium = 5
  24. WebSearchMaxUsesHigh = 10
  25. )
  26. func stopReasonClaude2OpenAI(reason string) string {
  27. return reasonmap.ClaudeStopReasonToOpenAIFinishReason(reason)
  28. }
  29. func maybeMarkClaudeRefusal(c *gin.Context, stopReason string) {
  30. if c == nil {
  31. return
  32. }
  33. if strings.EqualFold(stopReason, "refusal") {
  34. common.SetContextKey(c, constant.ContextKeyAdminRejectReason, "claude_stop_reason=refusal")
  35. }
  36. }
  37. func RequestOpenAI2ClaudeComplete(textRequest dto.GeneralOpenAIRequest) *dto.ClaudeRequest {
  38. claudeRequest := dto.ClaudeRequest{
  39. Model: textRequest.Model,
  40. Prompt: "",
  41. StopSequences: nil,
  42. Temperature: textRequest.Temperature,
  43. TopP: textRequest.TopP,
  44. TopK: textRequest.TopK,
  45. Stream: textRequest.Stream,
  46. }
  47. if claudeRequest.MaxTokensToSample == 0 {
  48. claudeRequest.MaxTokensToSample = 4096
  49. }
  50. prompt := ""
  51. for _, message := range textRequest.Messages {
  52. if message.Role == "user" {
  53. prompt += fmt.Sprintf("\n\nHuman: %s", message.StringContent())
  54. } else if message.Role == "assistant" {
  55. prompt += fmt.Sprintf("\n\nAssistant: %s", message.StringContent())
  56. } else if message.Role == "system" {
  57. if prompt == "" {
  58. prompt = message.StringContent()
  59. }
  60. }
  61. }
  62. prompt += "\n\nAssistant:"
  63. claudeRequest.Prompt = prompt
  64. return &claudeRequest
  65. }
  66. func RequestOpenAI2ClaudeMessage(c *gin.Context, textRequest dto.GeneralOpenAIRequest) (*dto.ClaudeRequest, error) {
  67. claudeTools := make([]any, 0, len(textRequest.Tools))
  68. for _, tool := range textRequest.Tools {
  69. if params, ok := tool.Function.Parameters.(map[string]any); ok {
  70. claudeTool := dto.Tool{
  71. Name: tool.Function.Name,
  72. Description: tool.Function.Description,
  73. }
  74. claudeTool.InputSchema = make(map[string]interface{})
  75. if params["type"] != nil {
  76. claudeTool.InputSchema["type"] = params["type"].(string)
  77. }
  78. claudeTool.InputSchema["properties"] = params["properties"]
  79. claudeTool.InputSchema["required"] = params["required"]
  80. for s, a := range params {
  81. if s == "type" || s == "properties" || s == "required" {
  82. continue
  83. }
  84. claudeTool.InputSchema[s] = a
  85. }
  86. claudeTools = append(claudeTools, &claudeTool)
  87. }
  88. }
  89. // Web search tool
  90. // https://docs.anthropic.com/en/docs/agents-and-tools/tool-use/web-search-tool
  91. if textRequest.WebSearchOptions != nil {
  92. webSearchTool := dto.ClaudeWebSearchTool{
  93. Type: "web_search_20250305",
  94. Name: "web_search",
  95. }
  96. // 处理 user_location
  97. if textRequest.WebSearchOptions.UserLocation != nil {
  98. anthropicUserLocation := &dto.ClaudeWebSearchUserLocation{
  99. Type: "approximate", // 固定为 "approximate"
  100. }
  101. // 解析 UserLocation JSON
  102. var userLocationMap map[string]interface{}
  103. if err := json.Unmarshal(textRequest.WebSearchOptions.UserLocation, &userLocationMap); err == nil {
  104. // 检查是否有 approximate 字段
  105. if approximateData, ok := userLocationMap["approximate"].(map[string]interface{}); ok {
  106. if timezone, ok := approximateData["timezone"].(string); ok && timezone != "" {
  107. anthropicUserLocation.Timezone = timezone
  108. }
  109. if country, ok := approximateData["country"].(string); ok && country != "" {
  110. anthropicUserLocation.Country = country
  111. }
  112. if region, ok := approximateData["region"].(string); ok && region != "" {
  113. anthropicUserLocation.Region = region
  114. }
  115. if city, ok := approximateData["city"].(string); ok && city != "" {
  116. anthropicUserLocation.City = city
  117. }
  118. }
  119. }
  120. webSearchTool.UserLocation = anthropicUserLocation
  121. }
  122. // 处理 search_context_size 转换为 max_uses
  123. if textRequest.WebSearchOptions.SearchContextSize != "" {
  124. switch textRequest.WebSearchOptions.SearchContextSize {
  125. case "low":
  126. webSearchTool.MaxUses = WebSearchMaxUsesLow
  127. case "medium":
  128. webSearchTool.MaxUses = WebSearchMaxUsesMedium
  129. case "high":
  130. webSearchTool.MaxUses = WebSearchMaxUsesHigh
  131. }
  132. }
  133. claudeTools = append(claudeTools, &webSearchTool)
  134. }
  135. claudeRequest := dto.ClaudeRequest{
  136. Model: textRequest.Model,
  137. MaxTokens: textRequest.GetMaxTokens(),
  138. StopSequences: nil,
  139. Temperature: textRequest.Temperature,
  140. TopP: textRequest.TopP,
  141. TopK: textRequest.TopK,
  142. Stream: textRequest.Stream,
  143. Tools: claudeTools,
  144. }
  145. // 处理 tool_choice 和 parallel_tool_calls
  146. if textRequest.ToolChoice != nil || textRequest.ParallelTooCalls != nil {
  147. claudeToolChoice := mapToolChoice(textRequest.ToolChoice, textRequest.ParallelTooCalls)
  148. if claudeToolChoice != nil {
  149. claudeRequest.ToolChoice = claudeToolChoice
  150. }
  151. }
  152. if claudeRequest.MaxTokens == 0 {
  153. claudeRequest.MaxTokens = uint(model_setting.GetClaudeSettings().GetDefaultMaxTokens(textRequest.Model))
  154. }
  155. if model_setting.GetClaudeSettings().ThinkingAdapterEnabled &&
  156. strings.HasSuffix(textRequest.Model, "-thinking") {
  157. // 因为BudgetTokens 必须大于1024
  158. if claudeRequest.MaxTokens < 1280 {
  159. claudeRequest.MaxTokens = 1280
  160. }
  161. // BudgetTokens 为 max_tokens 的 80%
  162. claudeRequest.Thinking = &dto.Thinking{
  163. Type: "enabled",
  164. BudgetTokens: common.GetPointer[int](int(float64(claudeRequest.MaxTokens) * model_setting.GetClaudeSettings().ThinkingAdapterBudgetTokensPercentage)),
  165. }
  166. // TODO: 临时处理
  167. // https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking#important-considerations-when-using-extended-thinking
  168. claudeRequest.TopP = 0
  169. claudeRequest.Temperature = common.GetPointer[float64](1.0)
  170. if !model_setting.ShouldPreserveThinkingSuffix(textRequest.Model) {
  171. claudeRequest.Model = strings.TrimSuffix(textRequest.Model, "-thinking")
  172. }
  173. }
  174. if textRequest.ReasoningEffort != "" {
  175. switch textRequest.ReasoningEffort {
  176. case "low":
  177. claudeRequest.Thinking = &dto.Thinking{
  178. Type: "enabled",
  179. BudgetTokens: common.GetPointer[int](1280),
  180. }
  181. case "medium":
  182. claudeRequest.Thinking = &dto.Thinking{
  183. Type: "enabled",
  184. BudgetTokens: common.GetPointer[int](2048),
  185. }
  186. case "high":
  187. claudeRequest.Thinking = &dto.Thinking{
  188. Type: "enabled",
  189. BudgetTokens: common.GetPointer[int](4096),
  190. }
  191. }
  192. }
  193. // 指定了 reasoning 参数,覆盖 budgetTokens
  194. if textRequest.Reasoning != nil {
  195. var reasoning openrouter.RequestReasoning
  196. if err := common.Unmarshal(textRequest.Reasoning, &reasoning); err != nil {
  197. return nil, err
  198. }
  199. budgetTokens := reasoning.MaxTokens
  200. if budgetTokens > 0 {
  201. claudeRequest.Thinking = &dto.Thinking{
  202. Type: "enabled",
  203. BudgetTokens: &budgetTokens,
  204. }
  205. }
  206. }
  207. if textRequest.Stop != nil {
  208. // stop maybe string/array string, convert to array string
  209. switch textRequest.Stop.(type) {
  210. case string:
  211. claudeRequest.StopSequences = []string{textRequest.Stop.(string)}
  212. case []interface{}:
  213. stopSequences := make([]string, 0)
  214. for _, stop := range textRequest.Stop.([]interface{}) {
  215. stopSequences = append(stopSequences, stop.(string))
  216. }
  217. claudeRequest.StopSequences = stopSequences
  218. }
  219. }
  220. formatMessages := make([]dto.Message, 0)
  221. lastMessage := dto.Message{
  222. Role: "tool",
  223. }
  224. for i, message := range textRequest.Messages {
  225. if message.Role == "" {
  226. textRequest.Messages[i].Role = "user"
  227. }
  228. fmtMessage := dto.Message{
  229. Role: message.Role,
  230. Content: message.Content,
  231. }
  232. if message.Role == "tool" {
  233. fmtMessage.ToolCallId = message.ToolCallId
  234. }
  235. if message.Role == "assistant" && message.ToolCalls != nil {
  236. fmtMessage.ToolCalls = message.ToolCalls
  237. }
  238. if lastMessage.Role == message.Role && lastMessage.Role != "tool" {
  239. if lastMessage.IsStringContent() && message.IsStringContent() {
  240. fmtMessage.SetStringContent(strings.Trim(fmt.Sprintf("%s %s", lastMessage.StringContent(), message.StringContent()), "\""))
  241. // delete last message
  242. formatMessages = formatMessages[:len(formatMessages)-1]
  243. }
  244. }
  245. if fmtMessage.Content == nil {
  246. fmtMessage.SetStringContent("...")
  247. }
  248. formatMessages = append(formatMessages, fmtMessage)
  249. lastMessage = fmtMessage
  250. }
  251. claudeMessages := make([]dto.ClaudeMessage, 0)
  252. isFirstMessage := true
  253. // 初始化system消息数组,用于累积多个system消息
  254. var systemMessages []dto.ClaudeMediaMessage
  255. for _, message := range formatMessages {
  256. if message.Role == "system" {
  257. // 根据Claude API规范,system字段使用数组格式更有通用性
  258. if message.IsStringContent() {
  259. systemMessages = append(systemMessages, dto.ClaudeMediaMessage{
  260. Type: "text",
  261. Text: common.GetPointer[string](message.StringContent()),
  262. })
  263. } else {
  264. // 支持复合内容的system消息(虽然不常见,但需要考虑完整性)
  265. for _, ctx := range message.ParseContent() {
  266. if ctx.Type == "text" {
  267. systemMessages = append(systemMessages, dto.ClaudeMediaMessage{
  268. Type: "text",
  269. Text: common.GetPointer[string](ctx.Text),
  270. })
  271. }
  272. // 未来可以在这里扩展对图片等其他类型的支持
  273. }
  274. }
  275. } else {
  276. if isFirstMessage {
  277. isFirstMessage = false
  278. if message.Role != "user" {
  279. // fix: first message is assistant, add user message
  280. claudeMessage := dto.ClaudeMessage{
  281. Role: "user",
  282. Content: []dto.ClaudeMediaMessage{
  283. {
  284. Type: "text",
  285. Text: common.GetPointer[string]("..."),
  286. },
  287. },
  288. }
  289. claudeMessages = append(claudeMessages, claudeMessage)
  290. }
  291. }
  292. claudeMessage := dto.ClaudeMessage{
  293. Role: message.Role,
  294. }
  295. if message.Role == "tool" {
  296. if len(claudeMessages) > 0 && claudeMessages[len(claudeMessages)-1].Role == "user" {
  297. lastMessage := claudeMessages[len(claudeMessages)-1]
  298. if content, ok := lastMessage.Content.(string); ok {
  299. lastMessage.Content = []dto.ClaudeMediaMessage{
  300. {
  301. Type: "text",
  302. Text: common.GetPointer[string](content),
  303. },
  304. }
  305. }
  306. lastMessage.Content = append(lastMessage.Content.([]dto.ClaudeMediaMessage), dto.ClaudeMediaMessage{
  307. Type: "tool_result",
  308. ToolUseId: message.ToolCallId,
  309. Content: message.Content,
  310. })
  311. claudeMessages[len(claudeMessages)-1] = lastMessage
  312. continue
  313. } else {
  314. claudeMessage.Role = "user"
  315. claudeMessage.Content = []dto.ClaudeMediaMessage{
  316. {
  317. Type: "tool_result",
  318. ToolUseId: message.ToolCallId,
  319. Content: message.Content,
  320. },
  321. }
  322. }
  323. } else if message.IsStringContent() && message.ToolCalls == nil {
  324. claudeMessage.Content = message.StringContent()
  325. } else {
  326. claudeMediaMessages := make([]dto.ClaudeMediaMessage, 0)
  327. for _, mediaMessage := range message.ParseContent() {
  328. claudeMediaMessage := dto.ClaudeMediaMessage{
  329. Type: mediaMessage.Type,
  330. }
  331. if mediaMessage.Type == "text" {
  332. claudeMediaMessage.Text = common.GetPointer[string](mediaMessage.Text)
  333. } else {
  334. imageUrl := mediaMessage.GetImageMedia()
  335. claudeMediaMessage.Type = "image"
  336. claudeMediaMessage.Source = &dto.ClaudeMessageSource{
  337. Type: "base64",
  338. }
  339. // 使用统一的文件服务获取图片数据
  340. var source *types.FileSource
  341. if strings.HasPrefix(imageUrl.Url, "http") {
  342. source = types.NewURLFileSource(imageUrl.Url)
  343. } else {
  344. source = types.NewBase64FileSource(imageUrl.Url, "")
  345. }
  346. base64Data, mimeType, err := service.GetBase64Data(c, source, "formatting image for Claude")
  347. if err != nil {
  348. return nil, fmt.Errorf("get file data failed: %s", err.Error())
  349. }
  350. claudeMediaMessage.Source.MediaType = mimeType
  351. claudeMediaMessage.Source.Data = base64Data
  352. }
  353. claudeMediaMessages = append(claudeMediaMessages, claudeMediaMessage)
  354. }
  355. if message.ToolCalls != nil {
  356. for _, toolCall := range message.ParseToolCalls() {
  357. inputObj := make(map[string]any)
  358. if err := json.Unmarshal([]byte(toolCall.Function.Arguments), &inputObj); err != nil {
  359. common.SysLog("tool call function arguments is not a map[string]any: " + fmt.Sprintf("%v", toolCall.Function.Arguments))
  360. continue
  361. }
  362. claudeMediaMessages = append(claudeMediaMessages, dto.ClaudeMediaMessage{
  363. Type: "tool_use",
  364. Id: toolCall.ID,
  365. Name: toolCall.Function.Name,
  366. Input: inputObj,
  367. })
  368. }
  369. }
  370. claudeMessage.Content = claudeMediaMessages
  371. }
  372. claudeMessages = append(claudeMessages, claudeMessage)
  373. }
  374. }
  375. // 设置累积的system消息
  376. if len(systemMessages) > 0 {
  377. claudeRequest.System = systemMessages
  378. }
  379. claudeRequest.Prompt = ""
  380. claudeRequest.Messages = claudeMessages
  381. return &claudeRequest, nil
  382. }
  383. func StreamResponseClaude2OpenAI(reqMode int, claudeResponse *dto.ClaudeResponse) *dto.ChatCompletionsStreamResponse {
  384. var response dto.ChatCompletionsStreamResponse
  385. response.Object = "chat.completion.chunk"
  386. response.Model = claudeResponse.Model
  387. response.Choices = make([]dto.ChatCompletionsStreamResponseChoice, 0)
  388. tools := make([]dto.ToolCallResponse, 0)
  389. fcIdx := 0
  390. if claudeResponse.Index != nil {
  391. fcIdx = *claudeResponse.Index - 1
  392. if fcIdx < 0 {
  393. fcIdx = 0
  394. }
  395. }
  396. var choice dto.ChatCompletionsStreamResponseChoice
  397. if reqMode == RequestModeCompletion {
  398. choice.Delta.SetContentString(claudeResponse.Completion)
  399. finishReason := stopReasonClaude2OpenAI(claudeResponse.StopReason)
  400. if finishReason != "null" {
  401. choice.FinishReason = &finishReason
  402. }
  403. } else {
  404. if claudeResponse.Type == "message_start" {
  405. if claudeResponse.Message != nil {
  406. response.Id = claudeResponse.Message.Id
  407. response.Model = claudeResponse.Message.Model
  408. }
  409. //claudeUsage = &claudeResponse.Message.Usage
  410. choice.Delta.SetContentString("")
  411. choice.Delta.Role = "assistant"
  412. } else if claudeResponse.Type == "content_block_start" {
  413. if claudeResponse.ContentBlock != nil {
  414. // 如果是文本块,尽可能发送首段文本(若存在)
  415. if claudeResponse.ContentBlock.Type == "text" && claudeResponse.ContentBlock.Text != nil {
  416. choice.Delta.SetContentString(*claudeResponse.ContentBlock.Text)
  417. }
  418. if claudeResponse.ContentBlock.Type == "tool_use" {
  419. tools = append(tools, dto.ToolCallResponse{
  420. Index: common.GetPointer(fcIdx),
  421. ID: claudeResponse.ContentBlock.Id,
  422. Type: "function",
  423. Function: dto.FunctionResponse{
  424. Name: claudeResponse.ContentBlock.Name,
  425. Arguments: "",
  426. },
  427. })
  428. }
  429. } else {
  430. return nil
  431. }
  432. } else if claudeResponse.Type == "content_block_delta" {
  433. if claudeResponse.Delta != nil {
  434. choice.Delta.Content = claudeResponse.Delta.Text
  435. switch claudeResponse.Delta.Type {
  436. case "input_json_delta":
  437. tools = append(tools, dto.ToolCallResponse{
  438. Type: "function",
  439. Index: common.GetPointer(fcIdx),
  440. Function: dto.FunctionResponse{
  441. Arguments: *claudeResponse.Delta.PartialJson,
  442. },
  443. })
  444. case "signature_delta":
  445. // 加密的不处理
  446. signatureContent := "\n"
  447. choice.Delta.ReasoningContent = &signatureContent
  448. case "thinking_delta":
  449. choice.Delta.ReasoningContent = claudeResponse.Delta.Thinking
  450. }
  451. }
  452. } else if claudeResponse.Type == "message_delta" {
  453. if claudeResponse.Delta != nil && claudeResponse.Delta.StopReason != nil {
  454. finishReason := stopReasonClaude2OpenAI(*claudeResponse.Delta.StopReason)
  455. if finishReason != "null" {
  456. choice.FinishReason = &finishReason
  457. }
  458. }
  459. //claudeUsage = &claudeResponse.Usage
  460. } else if claudeResponse.Type == "message_stop" {
  461. return nil
  462. } else {
  463. return nil
  464. }
  465. }
  466. if len(tools) > 0 {
  467. choice.Delta.Content = nil // compatible with other OpenAI derivative applications, like LobeOpenAICompatibleFactory ...
  468. choice.Delta.ToolCalls = tools
  469. }
  470. response.Choices = append(response.Choices, choice)
  471. return &response
  472. }
  473. func ResponseClaude2OpenAI(reqMode int, claudeResponse *dto.ClaudeResponse) *dto.OpenAITextResponse {
  474. choices := make([]dto.OpenAITextResponseChoice, 0)
  475. fullTextResponse := dto.OpenAITextResponse{
  476. Id: fmt.Sprintf("chatcmpl-%s", common.GetUUID()),
  477. Object: "chat.completion",
  478. Created: common.GetTimestamp(),
  479. }
  480. var responseText string
  481. var responseThinking string
  482. if len(claudeResponse.Content) > 0 {
  483. responseText = claudeResponse.Content[0].GetText()
  484. if claudeResponse.Content[0].Thinking != nil {
  485. responseThinking = *claudeResponse.Content[0].Thinking
  486. }
  487. }
  488. tools := make([]dto.ToolCallResponse, 0)
  489. thinkingContent := ""
  490. if reqMode == RequestModeCompletion {
  491. choice := dto.OpenAITextResponseChoice{
  492. Index: 0,
  493. Message: dto.Message{
  494. Role: "assistant",
  495. Content: strings.TrimPrefix(claudeResponse.Completion, " "),
  496. Name: nil,
  497. },
  498. FinishReason: stopReasonClaude2OpenAI(claudeResponse.StopReason),
  499. }
  500. choices = append(choices, choice)
  501. } else {
  502. fullTextResponse.Id = claudeResponse.Id
  503. for _, message := range claudeResponse.Content {
  504. switch message.Type {
  505. case "tool_use":
  506. args, _ := json.Marshal(message.Input)
  507. tools = append(tools, dto.ToolCallResponse{
  508. ID: message.Id,
  509. Type: "function", // compatible with other OpenAI derivative applications
  510. Function: dto.FunctionResponse{
  511. Name: message.Name,
  512. Arguments: string(args),
  513. },
  514. })
  515. case "thinking":
  516. // 加密的不管, 只输出明文的推理过程
  517. if message.Thinking != nil {
  518. thinkingContent = *message.Thinking
  519. }
  520. case "text":
  521. responseText = message.GetText()
  522. }
  523. }
  524. }
  525. choice := dto.OpenAITextResponseChoice{
  526. Index: 0,
  527. Message: dto.Message{
  528. Role: "assistant",
  529. },
  530. FinishReason: stopReasonClaude2OpenAI(claudeResponse.StopReason),
  531. }
  532. choice.SetStringContent(responseText)
  533. if len(responseThinking) > 0 {
  534. choice.ReasoningContent = responseThinking
  535. }
  536. if len(tools) > 0 {
  537. choice.Message.SetToolCalls(tools)
  538. }
  539. choice.Message.ReasoningContent = thinkingContent
  540. fullTextResponse.Model = claudeResponse.Model
  541. choices = append(choices, choice)
  542. fullTextResponse.Choices = choices
  543. return &fullTextResponse
  544. }
  545. type ClaudeResponseInfo struct {
  546. ResponseId string
  547. Created int64
  548. Model string
  549. ResponseText strings.Builder
  550. Usage *dto.Usage
  551. Done bool
  552. }
  553. func FormatClaudeResponseInfo(requestMode int, claudeResponse *dto.ClaudeResponse, oaiResponse *dto.ChatCompletionsStreamResponse, claudeInfo *ClaudeResponseInfo) bool {
  554. if claudeInfo == nil {
  555. return false
  556. }
  557. if claudeInfo.Usage == nil {
  558. claudeInfo.Usage = &dto.Usage{}
  559. }
  560. if requestMode == RequestModeCompletion {
  561. claudeInfo.ResponseText.WriteString(claudeResponse.Completion)
  562. } else {
  563. if claudeResponse.Type == "message_start" {
  564. if claudeResponse.Message != nil {
  565. claudeInfo.ResponseId = claudeResponse.Message.Id
  566. claudeInfo.Model = claudeResponse.Message.Model
  567. }
  568. // message_start, 获取usage
  569. if claudeResponse.Message != nil && claudeResponse.Message.Usage != nil {
  570. claudeInfo.Usage.PromptTokens = claudeResponse.Message.Usage.InputTokens
  571. claudeInfo.Usage.PromptTokensDetails.CachedTokens = claudeResponse.Message.Usage.CacheReadInputTokens
  572. claudeInfo.Usage.PromptTokensDetails.CachedCreationTokens = claudeResponse.Message.Usage.CacheCreationInputTokens
  573. claudeInfo.Usage.ClaudeCacheCreation5mTokens = claudeResponse.Message.Usage.GetCacheCreation5mTokens()
  574. claudeInfo.Usage.ClaudeCacheCreation1hTokens = claudeResponse.Message.Usage.GetCacheCreation1hTokens()
  575. claudeInfo.Usage.CompletionTokens = claudeResponse.Message.Usage.OutputTokens
  576. }
  577. } else if claudeResponse.Type == "content_block_delta" {
  578. if claudeResponse.Delta != nil {
  579. if claudeResponse.Delta.Text != nil {
  580. claudeInfo.ResponseText.WriteString(*claudeResponse.Delta.Text)
  581. }
  582. if claudeResponse.Delta.Thinking != nil {
  583. claudeInfo.ResponseText.WriteString(*claudeResponse.Delta.Thinking)
  584. }
  585. }
  586. } else if claudeResponse.Type == "message_delta" {
  587. // 最终的usage获取
  588. if claudeResponse.Usage != nil {
  589. if claudeResponse.Usage.InputTokens > 0 {
  590. // 不叠加,只取最新的
  591. claudeInfo.Usage.PromptTokens = claudeResponse.Usage.InputTokens
  592. }
  593. if claudeResponse.Usage.CacheReadInputTokens > 0 {
  594. claudeInfo.Usage.PromptTokensDetails.CachedTokens = claudeResponse.Usage.CacheReadInputTokens
  595. }
  596. if claudeResponse.Usage.CacheCreationInputTokens > 0 {
  597. claudeInfo.Usage.PromptTokensDetails.CachedCreationTokens = claudeResponse.Usage.CacheCreationInputTokens
  598. }
  599. if cacheCreation5m := claudeResponse.Usage.GetCacheCreation5mTokens(); cacheCreation5m > 0 {
  600. claudeInfo.Usage.ClaudeCacheCreation5mTokens = cacheCreation5m
  601. }
  602. if cacheCreation1h := claudeResponse.Usage.GetCacheCreation1hTokens(); cacheCreation1h > 0 {
  603. claudeInfo.Usage.ClaudeCacheCreation1hTokens = cacheCreation1h
  604. }
  605. if claudeResponse.Usage.OutputTokens > 0 {
  606. claudeInfo.Usage.CompletionTokens = claudeResponse.Usage.OutputTokens
  607. }
  608. claudeInfo.Usage.TotalTokens = claudeInfo.Usage.PromptTokens + claudeInfo.Usage.CompletionTokens
  609. }
  610. // 判断是否完整
  611. claudeInfo.Done = true
  612. } else if claudeResponse.Type == "content_block_start" {
  613. } else {
  614. return false
  615. }
  616. }
  617. if oaiResponse != nil {
  618. oaiResponse.Id = claudeInfo.ResponseId
  619. oaiResponse.Created = claudeInfo.Created
  620. oaiResponse.Model = claudeInfo.Model
  621. }
  622. return true
  623. }
  624. func HandleStreamResponseData(c *gin.Context, info *relaycommon.RelayInfo, claudeInfo *ClaudeResponseInfo, data string, requestMode int) *types.NewAPIError {
  625. var claudeResponse dto.ClaudeResponse
  626. err := common.UnmarshalJsonStr(data, &claudeResponse)
  627. if err != nil {
  628. common.SysLog("error unmarshalling stream response: " + err.Error())
  629. return types.NewError(err, types.ErrorCodeBadResponseBody)
  630. }
  631. if claudeError := claudeResponse.GetClaudeError(); claudeError != nil && claudeError.Type != "" {
  632. return types.WithClaudeError(*claudeError, http.StatusInternalServerError)
  633. }
  634. if claudeResponse.StopReason != "" {
  635. maybeMarkClaudeRefusal(c, claudeResponse.StopReason)
  636. }
  637. if claudeResponse.Delta != nil && claudeResponse.Delta.StopReason != nil {
  638. maybeMarkClaudeRefusal(c, *claudeResponse.Delta.StopReason)
  639. }
  640. if info.RelayFormat == types.RelayFormatClaude {
  641. FormatClaudeResponseInfo(requestMode, &claudeResponse, nil, claudeInfo)
  642. if requestMode == RequestModeCompletion {
  643. } else {
  644. if claudeResponse.Type == "message_start" {
  645. // message_start, 获取usage
  646. if claudeResponse.Message != nil {
  647. info.UpstreamModelName = claudeResponse.Message.Model
  648. }
  649. } else if claudeResponse.Type == "content_block_delta" {
  650. } else if claudeResponse.Type == "message_delta" {
  651. }
  652. }
  653. helper.ClaudeChunkData(c, claudeResponse, data)
  654. } else if info.RelayFormat == types.RelayFormatOpenAI {
  655. response := StreamResponseClaude2OpenAI(requestMode, &claudeResponse)
  656. if !FormatClaudeResponseInfo(requestMode, &claudeResponse, response, claudeInfo) {
  657. return nil
  658. }
  659. err = helper.ObjectData(c, response)
  660. if err != nil {
  661. logger.LogError(c, "send_stream_response_failed: "+err.Error())
  662. }
  663. }
  664. return nil
  665. }
  666. func HandleStreamFinalResponse(c *gin.Context, info *relaycommon.RelayInfo, claudeInfo *ClaudeResponseInfo, requestMode int) {
  667. if requestMode == RequestModeCompletion {
  668. claudeInfo.Usage = service.ResponseText2Usage(c, claudeInfo.ResponseText.String(), info.UpstreamModelName, info.GetEstimatePromptTokens())
  669. } else {
  670. if claudeInfo.Usage.PromptTokens == 0 {
  671. //上游出错
  672. }
  673. if claudeInfo.Usage.CompletionTokens == 0 || !claudeInfo.Done {
  674. if common.DebugEnabled {
  675. common.SysLog("claude response usage is not complete, maybe upstream error")
  676. }
  677. claudeInfo.Usage = service.ResponseText2Usage(c, claudeInfo.ResponseText.String(), info.UpstreamModelName, claudeInfo.Usage.PromptTokens)
  678. }
  679. }
  680. if info.RelayFormat == types.RelayFormatClaude {
  681. //
  682. } else if info.RelayFormat == types.RelayFormatOpenAI {
  683. if info.ShouldIncludeUsage {
  684. response := helper.GenerateFinalUsageResponse(claudeInfo.ResponseId, claudeInfo.Created, info.UpstreamModelName, *claudeInfo.Usage)
  685. err := helper.ObjectData(c, response)
  686. if err != nil {
  687. common.SysLog("send final response failed: " + err.Error())
  688. }
  689. }
  690. helper.Done(c)
  691. }
  692. }
  693. func ClaudeStreamHandler(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo, requestMode int) (*dto.Usage, *types.NewAPIError) {
  694. claudeInfo := &ClaudeResponseInfo{
  695. ResponseId: helper.GetResponseID(c),
  696. Created: common.GetTimestamp(),
  697. Model: info.UpstreamModelName,
  698. ResponseText: strings.Builder{},
  699. Usage: &dto.Usage{},
  700. }
  701. var err *types.NewAPIError
  702. helper.StreamScannerHandler(c, resp, info, func(data string) bool {
  703. err = HandleStreamResponseData(c, info, claudeInfo, data, requestMode)
  704. if err != nil {
  705. return false
  706. }
  707. return true
  708. })
  709. if err != nil {
  710. return nil, err
  711. }
  712. HandleStreamFinalResponse(c, info, claudeInfo, requestMode)
  713. return claudeInfo.Usage, nil
  714. }
  715. func HandleClaudeResponseData(c *gin.Context, info *relaycommon.RelayInfo, claudeInfo *ClaudeResponseInfo, httpResp *http.Response, data []byte, requestMode int) *types.NewAPIError {
  716. var claudeResponse dto.ClaudeResponse
  717. err := common.Unmarshal(data, &claudeResponse)
  718. if err != nil {
  719. return types.NewError(err, types.ErrorCodeBadResponseBody)
  720. }
  721. if claudeError := claudeResponse.GetClaudeError(); claudeError != nil && claudeError.Type != "" {
  722. return types.WithClaudeError(*claudeError, http.StatusInternalServerError)
  723. }
  724. maybeMarkClaudeRefusal(c, claudeResponse.StopReason)
  725. if requestMode == RequestModeCompletion {
  726. claudeInfo.Usage = service.ResponseText2Usage(c, claudeResponse.Completion, info.UpstreamModelName, info.GetEstimatePromptTokens())
  727. } else {
  728. if claudeInfo.Usage == nil {
  729. claudeInfo.Usage = &dto.Usage{}
  730. }
  731. if claudeResponse.Usage != nil {
  732. claudeInfo.Usage.PromptTokens = claudeResponse.Usage.InputTokens
  733. claudeInfo.Usage.CompletionTokens = claudeResponse.Usage.OutputTokens
  734. claudeInfo.Usage.TotalTokens = claudeResponse.Usage.InputTokens + claudeResponse.Usage.OutputTokens
  735. claudeInfo.Usage.PromptTokensDetails.CachedTokens = claudeResponse.Usage.CacheReadInputTokens
  736. claudeInfo.Usage.PromptTokensDetails.CachedCreationTokens = claudeResponse.Usage.CacheCreationInputTokens
  737. claudeInfo.Usage.ClaudeCacheCreation5mTokens = claudeResponse.Usage.GetCacheCreation5mTokens()
  738. claudeInfo.Usage.ClaudeCacheCreation1hTokens = claudeResponse.Usage.GetCacheCreation1hTokens()
  739. }
  740. }
  741. var responseData []byte
  742. switch info.RelayFormat {
  743. case types.RelayFormatOpenAI:
  744. openaiResponse := ResponseClaude2OpenAI(requestMode, &claudeResponse)
  745. openaiResponse.Usage = *claudeInfo.Usage
  746. responseData, err = json.Marshal(openaiResponse)
  747. if err != nil {
  748. return types.NewError(err, types.ErrorCodeBadResponseBody)
  749. }
  750. case types.RelayFormatClaude:
  751. responseData = data
  752. }
  753. if claudeResponse.Usage != nil && claudeResponse.Usage.ServerToolUse != nil && claudeResponse.Usage.ServerToolUse.WebSearchRequests > 0 {
  754. c.Set("claude_web_search_requests", claudeResponse.Usage.ServerToolUse.WebSearchRequests)
  755. }
  756. service.IOCopyBytesGracefully(c, httpResp, responseData)
  757. return nil
  758. }
  759. func ClaudeHandler(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo, requestMode int) (*dto.Usage, *types.NewAPIError) {
  760. defer service.CloseResponseBodyGracefully(resp)
  761. claudeInfo := &ClaudeResponseInfo{
  762. ResponseId: helper.GetResponseID(c),
  763. Created: common.GetTimestamp(),
  764. Model: info.UpstreamModelName,
  765. ResponseText: strings.Builder{},
  766. Usage: &dto.Usage{},
  767. }
  768. responseBody, err := io.ReadAll(resp.Body)
  769. if err != nil {
  770. return nil, types.NewError(err, types.ErrorCodeBadResponseBody)
  771. }
  772. if common.DebugEnabled {
  773. println("responseBody: ", string(responseBody))
  774. }
  775. handleErr := HandleClaudeResponseData(c, info, claudeInfo, resp, responseBody, requestMode)
  776. if handleErr != nil {
  777. return nil, handleErr
  778. }
  779. return claudeInfo.Usage, nil
  780. }
  781. func mapToolChoice(toolChoice any, parallelToolCalls *bool) *dto.ClaudeToolChoice {
  782. var claudeToolChoice *dto.ClaudeToolChoice
  783. // 处理 tool_choice 字符串值
  784. if toolChoiceStr, ok := toolChoice.(string); ok {
  785. switch toolChoiceStr {
  786. case "auto":
  787. claudeToolChoice = &dto.ClaudeToolChoice{
  788. Type: "auto",
  789. }
  790. case "required":
  791. claudeToolChoice = &dto.ClaudeToolChoice{
  792. Type: "any",
  793. }
  794. case "none":
  795. claudeToolChoice = &dto.ClaudeToolChoice{
  796. Type: "none",
  797. }
  798. }
  799. } else if toolChoiceMap, ok := toolChoice.(map[string]interface{}); ok {
  800. // 处理 tool_choice 对象值
  801. if function, ok := toolChoiceMap["function"].(map[string]interface{}); ok {
  802. if toolName, ok := function["name"].(string); ok {
  803. claudeToolChoice = &dto.ClaudeToolChoice{
  804. Type: "tool",
  805. Name: toolName,
  806. }
  807. }
  808. }
  809. }
  810. // 处理 parallel_tool_calls
  811. if parallelToolCalls != nil {
  812. if claudeToolChoice == nil {
  813. // 如果没有 tool_choice,但有 parallel_tool_calls,创建默认的 auto 类型
  814. claudeToolChoice = &dto.ClaudeToolChoice{
  815. Type: "auto",
  816. }
  817. }
  818. // Anthropic schema: tool_choice.type=none does not accept extra fields.
  819. // When tools are disabled, parallel_tool_calls is irrelevant, so we drop it.
  820. if claudeToolChoice.Type != "none" {
  821. // 如果 parallel_tool_calls 为 true,则 disable_parallel_tool_use 为 false
  822. claudeToolChoice.DisableParallelToolUse = !*parallelToolCalls
  823. }
  824. }
  825. return claudeToolChoice
  826. }