mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-09-20 08:57:57 +00:00
feat: ModelRouter — unified OpenAI-compatible multi-source LLM gateway
- Lua adapters per upstream (transform_request/response/stream_chunk, build_headers signing hooks) - AUTO priority routing with per-model kind (chat/image), explicit source/model routing - Per-source concurrency caps with queueing, exponential backoff, AUTO failover - OpenAI-compatible API: chat completions, SSE streaming, image generations, models - Gateway key auth, web UI for adapter/source management, runtime persistence - e2e test running the real binary against mocked upstreams
This commit is contained in:
120
internal/types/types.go
Normal file
120
internal/types/types.go
Normal file
@ -0,0 +1,120 @@
|
||||
// Package types defines the unified (OpenAI-compatible) wire format that the
|
||||
// gateway exposes to its clients, plus the unified internal representation.
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ---- OpenAI wire request (gateway input) ----
|
||||
|
||||
type ChatRequest struct {
|
||||
Model string `json:"model"`
|
||||
Messages []ChatMessage `json:"messages"`
|
||||
Temperature *float64 `json:"temperature,omitempty"`
|
||||
MaxTokens int `json:"max_tokens,omitempty"`
|
||||
Stream bool `json:"stream,omitempty"`
|
||||
Tools []interface{} `json:"tools,omitempty"`
|
||||
ToolChoice interface{} `json:"tool_choice,omitempty"`
|
||||
DisableThinking bool `json:"disable_thinking"`
|
||||
ExtraBody map[string]interface{} `json:"-"`
|
||||
}
|
||||
|
||||
func (r *ChatRequest) MarshalJSON() ([]byte, error) {
|
||||
type Alias ChatRequest
|
||||
data, err := json.Marshal((*Alias)(r))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(r.ExtraBody) == 0 {
|
||||
return data, nil
|
||||
}
|
||||
var raw map[string]interface{}
|
||||
if err := json.Unmarshal(data, &raw); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range r.ExtraBody {
|
||||
raw[k] = v
|
||||
}
|
||||
return json.Marshal(raw)
|
||||
}
|
||||
|
||||
// ChatMessage supports both plain string content and multimodal arrays
|
||||
// (RawMessage preserves whatever the client sent for the adapter to process).
|
||||
type ChatMessage struct {
|
||||
Role string `json:"role"`
|
||||
Content json.RawMessage `json:"content,omitempty"`
|
||||
ReasoningContent string `json:"reasoning_content,omitempty"`
|
||||
ToolCallID string `json:"tool_call_id,omitempty"`
|
||||
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
|
||||
}
|
||||
|
||||
func StringContent(s string) json.RawMessage { b, _ := json.Marshal(s); return b }
|
||||
|
||||
type ToolCall struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Arguments map[string]interface{} `json:"arguments"`
|
||||
}
|
||||
|
||||
// ---- Unified internal representation (what adapters produce) ----
|
||||
|
||||
type UnifiedResponse struct {
|
||||
Content string `json:"content"`
|
||||
ReasoningContent string `json:"reasoning_content,omitempty"`
|
||||
FinishReason string `json:"finish_reason,omitempty"`
|
||||
TokenUsage TokenUsage `json:"token_usage"`
|
||||
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
|
||||
// ImageData used by image-generation adapters.
|
||||
ImageData []ImageData `json:"image_data,omitempty"`
|
||||
}
|
||||
|
||||
type TokenUsage struct {
|
||||
Prompt int `json:"prompt"`
|
||||
Completion int `json:"completion"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
type ImageData struct {
|
||||
B64JSON string `json:"b64_json,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Revised string `json:"revised_prompt,omitempty"`
|
||||
}
|
||||
|
||||
// ---- Image generation (OpenAI /v1/images/generations wire) ----
|
||||
|
||||
type ImageGenRequest struct {
|
||||
Model string `json:"model"`
|
||||
Prompt string `json:"prompt"`
|
||||
N int `json:"n,omitempty"`
|
||||
Size string `json:"size,omitempty"`
|
||||
ResponseFormat string `json:"response_format,omitempty"`
|
||||
}
|
||||
|
||||
type ImageGenResponse struct {
|
||||
Created int64 `json:"created"`
|
||||
Data []ImageData `json:"data"`
|
||||
}
|
||||
|
||||
// ---- Unified streaming chunk produced by adapters ----
|
||||
|
||||
type UnifiedChunk struct {
|
||||
Content string `json:"content"`
|
||||
Done bool `json:"done"`
|
||||
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
|
||||
ReasoningContent string `json:"reasoning_content,omitempty"`
|
||||
}
|
||||
|
||||
// Meta passed to Lua build_headers hook
|
||||
type BuildMeta struct {
|
||||
URL string `json:"url"`
|
||||
Method string `json:"method"`
|
||||
Body string `json:"body"`
|
||||
APIKey string `json:"api_key"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Source map[string]interface{} `json:"source"`
|
||||
}
|
||||
|
||||
func Now() int64 { return time.Now().Unix() }
|
||||
Reference in New Issue
Block a user