初始提交:OCI 面板后端(含 GenAI 网关一期)

This commit is contained in:
Wang Defa
2026-07-09 15:31:04 +08:00
commit b9a3e97e84
168 changed files with 31794 additions and 0 deletions
+165
View File
@@ -0,0 +1,165 @@
package aiwire
import (
"encoding/json"
"strings"
)
// MessagesRequest 是 Anthropic /v1/messages 请求体。
type MessagesRequest struct {
Model string `json:"model"`
MaxTokens int `json:"max_tokens"`
System json.RawMessage `json:"system,omitempty"`
Messages []AnthMessage `json:"messages"`
StopSequences []string `json:"stop_sequences,omitempty"`
Temperature *float64 `json:"temperature,omitempty"`
TopP *float64 `json:"top_p,omitempty"`
TopK *int `json:"top_k,omitempty"`
Stream bool `json:"stream,omitempty"`
Tools []AnthTool `json:"tools,omitempty"`
ToolChoice json.RawMessage `json:"tool_choice,omitempty"`
Metadata json.RawMessage `json:"metadata,omitempty"`
Thinking json.RawMessage `json:"thinking,omitempty"`
}
// SystemText 把顶层 system(string 或块数组)拍平为文本。
func (r MessagesRequest) SystemText() string {
if len(r.System) == 0 {
return ""
}
var s string
if err := json.Unmarshal(r.System, &s); err == nil {
return s
}
var blocks []AnthBlock
if err := json.Unmarshal(r.System, &blocks); err != nil {
return ""
}
var sb strings.Builder
for _, b := range blocks {
if b.Type == "text" {
sb.WriteString(b.Text)
}
}
return sb.String()
}
// AnthMessage 是 Anthropic 消息;Content 兼容 string 与块数组。
type AnthMessage struct {
Role string `json:"role"`
Content AnthContent `json:"content"`
}
// AnthContent 是 string | []AnthBlock 联合。
type AnthContent struct {
Text string
Blocks []AnthBlock
isArray bool
}
func (c *AnthContent) UnmarshalJSON(b []byte) error {
trimmed := strings.TrimSpace(string(b))
if trimmed == "null" {
return nil
}
if len(trimmed) > 0 && trimmed[0] == '[' {
c.isArray = true
return json.Unmarshal(b, &c.Blocks)
}
return json.Unmarshal(b, &c.Text)
}
func (c AnthContent) MarshalJSON() ([]byte, error) {
if c.isArray {
return json.Marshal(c.Blocks)
}
return json.Marshal(c.Text)
}
// AllBlocks 统一为块数组视图(字符串形态包装为单 text 块)。
func (c AnthContent) AllBlocks() []AnthBlock {
if c.isArray {
return c.Blocks
}
if c.Text == "" {
return nil
}
return []AnthBlock{{Type: "text", Text: c.Text}}
}
// AnthBlock 是内容块;按 Type 取字段(text / tool_use / tool_result / image / thinking)。
type AnthBlock struct {
Type string `json:"type"`
Text string `json:"text,omitempty"`
// tool_use
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Input json.RawMessage `json:"input,omitempty"`
// tool_result
ToolUseID string `json:"tool_use_id,omitempty"`
Content json.RawMessage `json:"content,omitempty"`
IsError bool `json:"is_error,omitempty"`
// image
Source json.RawMessage `json:"source,omitempty"`
}
// ResultText 把 tool_result 的 content(string 或块数组)拍平为文本。
func (b AnthBlock) ResultText() string {
if len(b.Content) == 0 {
return ""
}
var s string
if err := json.Unmarshal(b.Content, &s); err == nil {
return s
}
var blocks []AnthBlock
if err := json.Unmarshal(b.Content, &blocks); err != nil {
return string(b.Content)
}
var sb strings.Builder
for _, blk := range blocks {
if blk.Type == "text" {
sb.WriteString(blk.Text)
}
}
return sb.String()
}
// AnthTool 是 Anthropic 工具定义(input_schema 即 JSON Schema)。
type AnthTool struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
InputSchema json.RawMessage `json:"input_schema,omitempty"`
}
// MessagesResponse 是非流式响应体。
type MessagesResponse struct {
ID string `json:"id"`
Type string `json:"type"`
Role string `json:"role"`
Model string `json:"model"`
Content []AnthBlock `json:"content"`
StopReason string `json:"stop_reason"`
StopSequence *string `json:"stop_sequence"`
Usage AnthUsage `json:"usage"`
}
// AnthUsage 是 Anthropic 用量结构。
type AnthUsage struct {
InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"`
// CacheReadInputTokens 是缓存命中读取数;OCI 隐式缓存无「写入」计数,cache_creation 恒缺
CacheReadInputTokens int `json:"cache_read_input_tokens,omitempty"`
}
// AnthErrorBody 是 Anthropic 格式错误响应。
type AnthErrorBody struct {
Type string `json:"type"`
Error AnthErrorDetail `json:"error"`
}
// AnthErrorDetail 是错误明细。
type AnthErrorDetail struct {
Type string `json:"type"`
Message string `json:"message"`
}
+31
View File
@@ -0,0 +1,31 @@
package aiwire
// EmbeddingsRequest 是 OpenAI /v1/embeddings 请求体(input 兼容 string 与 []string)。
type EmbeddingsRequest struct {
Model string `json:"model"`
Input StringList `json:"input"`
EncodingFormat string `json:"encoding_format,omitempty"`
Dimensions *int `json:"dimensions,omitempty"`
User string `json:"user,omitempty"`
}
// Embedding 是一条向量结果。
type Embedding struct {
Object string `json:"object"`
Index int `json:"index"`
Embedding []float32 `json:"embedding"`
}
// EmbeddingsResponse 是 /v1/embeddings 响应体。
type EmbeddingsResponse struct {
Object string `json:"object"`
Data []Embedding `json:"data"`
Model string `json:"model"`
Usage *EmbedUsage `json:"usage,omitempty"`
}
// EmbedUsage 是 embeddings 用量(无输出侧计数)。
type EmbedUsage struct {
PromptTokens int `json:"prompt_tokens"`
TotalTokens int `json:"total_tokens"`
}
+282
View File
@@ -0,0 +1,282 @@
// Package aiwire 定义 AI 网关的线格式类型:OpenAI Chat Completions(兼作网关内部
// 中间表示 IR,因 OCI GENERIC 格式即其镜像)与 Anthropic Messages。零内部依赖,
// oci 层与 service 层共同引用。
package aiwire
import (
"encoding/json"
"strings"
)
// ChatRequest 是 OpenAI /v1/chat/completions 请求体;TopK 为 OCI/Anthropic 扩展字段。
type ChatRequest struct {
Model string `json:"model"`
Messages []ChatMessage `json:"messages"`
MaxTokens *int `json:"max_tokens,omitempty"`
MaxCompletionTokens *int `json:"max_completion_tokens,omitempty"`
Temperature *float64 `json:"temperature,omitempty"`
TopP *float64 `json:"top_p,omitempty"`
TopK *int `json:"top_k,omitempty"`
FrequencyPenalty *float64 `json:"frequency_penalty,omitempty"`
PresencePenalty *float64 `json:"presence_penalty,omitempty"`
Stop StringList `json:"stop,omitempty"`
Seed *int `json:"seed,omitempty"`
N *int `json:"n,omitempty"`
Stream bool `json:"stream,omitempty"`
StreamOptions *StreamOptions `json:"stream_options,omitempty"`
Tools []Tool `json:"tools,omitempty"`
ToolChoice json.RawMessage `json:"tool_choice,omitempty"`
ResponseFormat *ResponseFormat `json:"response_format,omitempty"`
ReasoningEffort string `json:"reasoning_effort,omitempty"`
}
// StringList 兼容 OpenAI stop 的 string 与 []string 两种线格式。
type StringList []string
func (s *StringList) UnmarshalJSON(b []byte) error {
if len(b) > 0 && b[0] == '"' {
var one string
if err := json.Unmarshal(b, &one); err != nil {
return err
}
*s = StringList{one}
return nil
}
var many []string
if err := json.Unmarshal(b, &many); err != nil {
return err
}
*s = many
return nil
}
// StreamOptions 控制流式末尾是否附带 usage 事件。
type StreamOptions struct {
IncludeUsage bool `json:"include_usage"`
}
// ChatMessage 是请求与非流式响应共用的消息结构。
type ChatMessage struct {
Role string `json:"role"`
Content Content `json:"content"`
Name string `json:"name,omitempty"`
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
ToolCallID string `json:"tool_call_id,omitempty"`
}
// Content 兼容 string 与多模态块数组两种线格式;一期仅透传文本。
type Content struct {
// Text 在原始值为字符串时填充
Text string
// Parts 在原始值为数组时填充
Parts []ContentPart
// isArray 记录原始形态,序列化时保形
isArray bool
}
// ContentPart 是块数组中的一段;text 与 image_url 被网关处理,其余类型拒绝。
type ContentPart struct {
Type string `json:"type"`
Text string `json:"text,omitempty"`
ImageURL *ImageURL `json:"image_url,omitempty"`
}
// ImageURL 是 image_url 块负载;url 为 data URI(base64)或公网地址,与 OCI ImageUrl 同构。
type ImageURL struct {
URL string `json:"url"`
Detail string `json:"detail,omitempty"`
}
func (c *Content) UnmarshalJSON(b []byte) error {
trimmed := strings.TrimSpace(string(b))
if trimmed == "null" {
return nil
}
if len(trimmed) > 0 && trimmed[0] == '[' {
c.isArray = true
return json.Unmarshal(b, &c.Parts)
}
return json.Unmarshal(b, &c.Text)
}
func (c Content) MarshalJSON() ([]byte, error) {
if c.isArray {
return json.Marshal(c.Parts)
}
return json.Marshal(c.Text)
}
// NewTextContent 构造字符串形态的内容。
func NewTextContent(text string) Content {
return Content{Text: text}
}
// NewPartsContent 构造块数组形态的内容(多模态消息用)。
func NewPartsContent(parts []ContentPart) Content {
return Content{Parts: parts, isArray: true}
}
// JoinText 返回全部文本内容(数组形态拼接 text 块)。
func (c Content) JoinText() string {
if !c.isArray {
return c.Text
}
var sb strings.Builder
for _, p := range c.Parts {
if p.Type == "" || p.Type == "text" {
sb.WriteString(p.Text)
}
}
return sb.String()
}
// HasUnsupported 报告是否含网关无法承接的内容块(text / image_url 之外的类型)。
func (c Content) HasUnsupported() bool {
for _, p := range c.Parts {
switch p.Type {
case "", "text":
case "image_url":
if p.ImageURL == nil || p.ImageURL.URL == "" {
return true
}
default:
return true
}
}
return false
}
// Tool 是 function 工具定义。
type Tool struct {
Type string `json:"type"`
Function FunctionDef `json:"function"`
}
// FunctionDef 描述工具的名称与 JSON Schema 参数。
type FunctionDef struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
Parameters json.RawMessage `json:"parameters,omitempty"`
}
// ToolCall 是助手消息中的工具调用。
type ToolCall struct {
ID string `json:"id"`
Type string `json:"type"`
Function FunctionCall `json:"function"`
}
// FunctionCall 携带调用名与 JSON 编码的实参。
type FunctionCall struct {
Name string `json:"name"`
Arguments string `json:"arguments"`
}
// ResponseFormat 对应 response_format(json_object / json_schema)。
type ResponseFormat struct {
Type string `json:"type"`
JSONSchema json.RawMessage `json:"json_schema,omitempty"`
}
// ChatResponse 是非流式响应体。
type ChatResponse struct {
ID string `json:"id"`
Object string `json:"object"`
Created int64 `json:"created"`
Model string `json:"model"`
Choices []Choice `json:"choices"`
Usage *Usage `json:"usage,omitempty"`
}
// Choice 是一条候选回复。
type Choice struct {
Index int `json:"index"`
Message ChatMessage `json:"message"`
FinishReason string `json:"finish_reason"`
}
// Usage 是 token 用量。
type Usage struct {
PromptTokens int `json:"prompt_tokens"`
CompletionTokens int `json:"completion_tokens"`
TotalTokens int `json:"total_tokens"`
// PromptTokensDetails 携带缓存命中细分;OCI 为隐式 prompt 缓存,只有读取计数
PromptTokensDetails *PromptTokensDetails `json:"prompt_tokens_details,omitempty"`
}
// PromptTokensDetails 是输入 token 细分(OpenAI prompt_tokens_details 同构)。
type PromptTokensDetails struct {
CachedTokens int `json:"cached_tokens"`
}
// CachedTokens 返回缓存命中读取的 token 数;无细分时为 0。
func (u *Usage) CachedTokens() int {
if u == nil || u.PromptTokensDetails == nil {
return 0
}
return u.PromptTokensDetails.CachedTokens
}
// ChatChunk 是流式增量事件体(chat.completion.chunk)。
type ChatChunk struct {
ID string `json:"id"`
Object string `json:"object"`
Created int64 `json:"created"`
Model string `json:"model"`
Choices []ChunkChoice `json:"choices"`
Usage *Usage `json:"usage,omitempty"`
}
// ChunkChoice 是流式事件中的增量候选。
type ChunkChoice struct {
Index int `json:"index"`
Delta Delta `json:"delta"`
FinishReason *string `json:"finish_reason"`
}
// Delta 是流式增量内容;Content 为纯文本片段。
type Delta struct {
Role string `json:"role,omitempty"`
Content string `json:"content,omitempty"`
ToolCalls []ToolCallDelta `json:"tool_calls,omitempty"`
}
// ToolCallDelta 是工具调用的流式片段;Index 标识聚合目标。
type ToolCallDelta struct {
Index int `json:"index"`
ID string `json:"id,omitempty"`
Type string `json:"type,omitempty"`
Function FunctionCallDelta `json:"function"`
}
// FunctionCallDelta 是函数名 / 实参的增量片段。
type FunctionCallDelta struct {
Name string `json:"name,omitempty"`
Arguments string `json:"arguments,omitempty"`
}
// Model 是 /v1/models 列表项。
type Model struct {
ID string `json:"id"`
Object string `json:"object"`
Created int64 `json:"created"`
OwnedBy string `json:"owned_by"`
}
// ModelList 是 /v1/models 响应体。
type ModelList struct {
Object string `json:"object"`
Data []Model `json:"data"`
}
// ErrorBody 是 OpenAI 格式错误响应。
type ErrorBody struct {
Error ErrorDetail `json:"error"`
}
// ErrorDetail 是错误明细。
type ErrorDetail struct {
Message string `json:"message"`
Type string `json:"type"`
Code string `json:"code,omitempty"`
}
+219
View File
@@ -0,0 +1,219 @@
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"`
}
// Response 是 /responses 响应对象。
type Response struct {
ID string `json:"id"`
Object string `json:"object"`
CreatedAt int64 `json:"created_at"`
Status string `json:"status"`
Model string `json:"model"`
Output []RespOutItem `json:"output"`
Usage *RespUsage `json:"usage,omitempty"`
Error *RespError `json:"error"`
IncompleteDetails *RespIncomplete `json:"incomplete_details"`
Store bool `json:"store"`
}
// RespOutItem 是 output 数组项:message 或 function_call。
type RespOutItem struct {
Type string `json:"type"`
ID string `json:"id"`
Status string `json:"status,omitempty"`
Role string `json:"role,omitempty"`
Content []RespOutPart `json:"content,omitempty"`
CallID string `json:"call_id,omitempty"`
Name string `json:"name,omitempty"`
Arguments string `json:"arguments,omitempty"`
}
// RespOutPart 是 message item 的内容部件(output_text)。
type RespOutPart struct {
Type string `json:"type"`
Text string `json:"text"`
Annotations []any `json:"annotations"`
}
// 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"`
}