Files
oci-portal/internal/aiwire/responses.go
T
wangdefa 489cb49cb3
CI / test (push) Successful in 31s
Release / release (push) Successful in 54s
AI 网关切换 OpenAI 兼容面并移除 chat 端点,新增模型黑白名单
2026-07-12 17:48:28 +08:00

187 lines
5.8 KiB
Go

package aiwire
import (
"encoding/json"
"strings"
)
// RespRequest 是 POST /responses 请求体(无状态子集;有状态字段仅用于拒绝判定)。
type RespRequest struct {
Model string `json:"model"`
Input RespInput `json:"input"`
Instructions string `json:"instructions,omitempty"`
MaxOutputTokens *int `json:"max_output_tokens,omitempty"`
Temperature *float64 `json:"temperature,omitempty"`
TopP *float64 `json:"top_p,omitempty"`
Tools []RespTool `json:"tools,omitempty"`
ToolChoice json.RawMessage `json:"tool_choice,omitempty"`
ParallelToolCalls *bool `json:"parallel_tool_calls,omitempty"`
Text *RespText `json:"text,omitempty"`
Reasoning *RespReasoning `json:"reasoning,omitempty"`
Stream bool `json:"stream,omitempty"`
Store *bool `json:"store,omitempty"`
PreviousResponseID string `json:"previous_response_id,omitempty"`
Conversation json.RawMessage `json:"conversation,omitempty"`
Background *bool `json:"background,omitempty"`
}
// RespInput 兼容 string 与 item 数组两种形态。
type RespInput struct {
Text string
Items []RespItem
IsArray bool
}
func (in *RespInput) UnmarshalJSON(b []byte) error {
t := strings.TrimSpace(string(b))
if strings.HasPrefix(t, "\"") {
return json.Unmarshal(b, &in.Text)
}
in.IsArray = true
return json.Unmarshal(b, &in.Items)
}
// RespItem 承载 input 数组的各形态:message / function_call / function_call_output。
type RespItem struct {
Type string `json:"type,omitempty"` // 缺省视为 message
Role string `json:"role,omitempty"`
Content RespContent `json:"content,omitempty"`
ID string `json:"id,omitempty"`
CallID string `json:"call_id,omitempty"`
Name string `json:"name,omitempty"`
Arguments string `json:"arguments,omitempty"`
Output json.RawMessage `json:"output,omitempty"` // string 或部件数组
Status string `json:"status,omitempty"`
}
// OutputText 拍平 function_call_output 的 output(string 或部件数组)。
func (it RespItem) OutputText() string {
if len(it.Output) == 0 {
return ""
}
var s string
if err := json.Unmarshal(it.Output, &s); err == nil {
return s
}
var parts []RespPart
if err := json.Unmarshal(it.Output, &parts); err != nil {
return string(it.Output)
}
texts := make([]string, 0, len(parts))
for _, p := range parts {
texts = append(texts, p.Text)
}
return strings.Join(texts, "\n")
}
// RespContent 兼容 string 与 part 数组两种形态。
type RespContent struct {
Text string
Parts []RespPart
IsArray bool
}
func (c *RespContent) UnmarshalJSON(b []byte) error {
t := strings.TrimSpace(string(b))
if strings.HasPrefix(t, "\"") {
return json.Unmarshal(b, &c.Text)
}
c.IsArray = true
return json.Unmarshal(b, &c.Parts)
}
// JoinText 拍平内容为纯文本(input_text / output_text / refusal)。
func (c RespContent) JoinText() string {
if !c.IsArray {
return c.Text
}
texts := make([]string, 0, len(c.Parts))
for _, p := range c.Parts {
if p.Text != "" {
texts = append(texts, p.Text)
}
}
return strings.Join(texts, "\n")
}
// HasUnsupported 报告是否含网关无法承接的部件(文本 / input_image 之外,或依赖服务端文件存储)。
func (c RespContent) HasUnsupported() bool {
for _, p := range c.Parts {
switch p.Type {
case "", "input_text", "output_text", "text", "refusal":
case "input_image":
if p.ImageURL == "" || p.FileID != "" {
return true
}
default:
return true
}
}
return false
}
// RespPart 是 content 数组里的一个部件;input_image 的 image_url 为字符串(URL 或 data URI)。
type RespPart struct {
Type string `json:"type"`
Text string `json:"text,omitempty"`
ImageURL string `json:"image_url,omitempty"`
Detail string `json:"detail,omitempty"`
FileID string `json:"file_id,omitempty"`
}
// RespTool 是 Responses 扁平工具定义(仅支持 type=function)。
type RespTool struct {
Type string `json:"type"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Parameters json.RawMessage `json:"parameters,omitempty"`
Strict *bool `json:"strict,omitempty"`
}
// RespText 收纳输出格式配置(text.format)。
type RespText struct {
Format *RespTextFormat `json:"format,omitempty"`
}
// RespTextFormat 是扁平的 format 对象(text / json_object / json_schema)。
type RespTextFormat struct {
Type string `json:"type"`
Name string `json:"name,omitempty"`
Schema json.RawMessage `json:"schema,omitempty"`
Strict *bool `json:"strict,omitempty"`
}
// RespReasoning 只透传 effort;summary 等忽略。
type RespReasoning struct {
Effort string `json:"effort,omitempty"`
}
// RespUsage 是 Responses 口径的用量(input/output 命名)。
type RespUsage struct {
InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"`
TotalTokens int `json:"total_tokens"`
InputTokensDetails RespInDetails `json:"input_tokens_details"`
OutputTokensDetails RespOutDetails `json:"output_tokens_details"`
}
// RespInDetails / RespOutDetails 恒零值输出(OCI 不提供细分)。
type RespInDetails struct {
CachedTokens int `json:"cached_tokens"`
}
type RespOutDetails struct {
ReasoningTokens int `json:"reasoning_tokens"`
}
// RespError 是 Response.error 字段。
type RespError struct {
Code string `json:"code"`
Message string `json:"message"`
}
// RespIncomplete 说明 status=incomplete 的原因。
type RespIncomplete struct {
Reason string `json:"reason"`
}