package service import ( "encoding/json" "fmt" "strings" "oci-portal/internal/aiwire" ) // respRejectStateful 拒绝有状态特性(网关无状态)。 func respRejectStateful(req aiwire.RespRequest) error { if req.PreviousResponseID != "" { return fmt.Errorf("previous_response_id 不支持:网关不保存历史响应,请在 input 中自带完整上下文(store:false 模式)") } if len(req.Conversation) > 0 && string(req.Conversation) != "null" { return fmt.Errorf("conversation 不支持:网关不保存对话状态") } if req.Background != nil && *req.Background { return fmt.Errorf("background 模式不支持") } return nil } // RespPassthroughValidate 校验直通请求:模型必填,有状态特性不支持,工具类型 // 只放行 function 与 Oracle 文档化的服务端工具(web_search / x_search / // code_interpreter / mcp)。 func RespPassthroughValidate(req aiwire.RespRequest) error { if strings.TrimSpace(req.Model) == "" { return fmt.Errorf("model 不能为空") } if err := respRejectStateful(req); err != nil { return err } for _, t := range req.Tools { switch t.Type { case "function", "web_search", "x_search", "code_interpreter", "mcp": default: return fmt.Errorf("不支持的工具类型 %q:服务端工具仅支持 web_search / x_search / code_interpreter / mcp", t.Type) } } return nil } // RespPassthroughBody 以原始请求体为基构造上游 body:强制 store:false(禁上游 // 存态),stream 原样保留(流式直通);用 json.Number 保真未知字段与数值。 func RespPassthroughBody(raw []byte) ([]byte, error) { dec := json.NewDecoder(strings.NewReader(string(raw))) dec.UseNumber() var body map[string]any if err := dec.Decode(&body); err != nil { return nil, fmt.Errorf("解析请求体: %w", err) } body["store"] = false return json.Marshal(body) } // RespPassthroughUsage 从直通响应提取用量;缺失时返回 nil(日志记零)。 func RespPassthroughUsage(payload []byte) *aiwire.Usage { var root struct { Usage *aiwire.RespUsage `json:"usage"` } if json.Unmarshal(payload, &root) != nil { return nil } return usageFromResp(root.Usage) } // usageFromResp 换算 Responses usage 为 OpenAI 口径(缓存命中透传);nil 原样返回。 func usageFromResp(u *aiwire.RespUsage) *aiwire.Usage { if u == nil { return nil } usage := &aiwire.Usage{PromptTokens: u.InputTokens, CompletionTokens: u.OutputTokens, TotalTokens: u.TotalTokens} if cached := u.InputTokensDetails.CachedTokens; cached > 0 { usage.PromptTokensDetails = &aiwire.PromptTokensDetails{CachedTokens: cached} } return usage } // RespStreamCompletedUsage 从一行 SSE data JSON 中提取 response.completed 事件的 // usage;非 completed 事件或解析失败返回 nil。流式直通逐行喂入,最后一次非 nil 生效。 func RespStreamCompletedUsage(data []byte) *aiwire.Usage { var ev struct { Type string `json:"type"` Response json.RawMessage `json:"response"` } if json.Unmarshal(data, &ev) != nil || ev.Type != "response.completed" || len(ev.Response) == 0 { return nil } return RespPassthroughUsage(ev.Response) } // RespStreamErrorMsg 从一行 SSE data JSON 中提取 error / response.failed 事件的 // 错误消息;非错误事件返回空串。流式直通据此把上游错误写入调用日志。 func RespStreamErrorMsg(data []byte) string { var ev respStreamEvent if json.Unmarshal(data, &ev) != nil { return "" } switch ev.Type { case "error": if ev.Message != "" { return ev.Message } return "上游返回错误事件 error" case "response.failed": if m := respErrorMsg(ev.Response); m != "" { return m } return "上游返回错误事件 response.failed" } return "" }