summaryrefslogtreecommitdiff
path: root/models
diff options
context:
space:
mode:
Diffstat (limited to 'models')
-rw-r--r--models/card.go58
-rw-r--r--models/db.go17
-rw-r--r--models/deepseek.go144
-rw-r--r--models/embed.go15
-rw-r--r--models/extra.go8
-rw-r--r--models/models.go528
-rw-r--r--models/openrouter.go157
7 files changed, 870 insertions, 57 deletions
diff --git a/models/card.go b/models/card.go
new file mode 100644
index 0000000..adfb030
--- /dev/null
+++ b/models/card.go
@@ -0,0 +1,58 @@
+package models
+
+import "strings"
+
+// https://github.com/malfoyslastname/character-card-spec-v2/blob/main/spec_v2.md
+// what a bloat; trim to Role->Msg pair and first msg
+type CharCardSpec struct {
+ Name string `json:"name"`
+ Description string `json:"description"`
+ Personality string `json:"personality"`
+ FirstMes string `json:"first_mes"`
+ Avatar string `json:"avatar"`
+ Chat string `json:"chat"`
+ MesExample string `json:"mes_example"`
+ Scenario string `json:"scenario"`
+ CreateDate string `json:"create_date"`
+ Talkativeness string `json:"talkativeness"`
+ Fav bool `json:"fav"`
+ Creatorcomment string `json:"creatorcomment"`
+ Spec string `json:"spec"`
+ SpecVersion string `json:"spec_version"`
+ Tags []any `json:"tags"`
+ Extentions []byte `json:"extentions"`
+}
+
+type Spec2Wrapper struct {
+ Data CharCardSpec `json:"data"`
+}
+
+func (c *CharCardSpec) Simplify(userName, fpath string) *CharCard {
+ fm := strings.ReplaceAll(strings.ReplaceAll(c.FirstMes, "{{char}}", c.Name), "{{user}}", userName)
+ sysPr := strings.ReplaceAll(strings.ReplaceAll(c.Description, "{{char}}", c.Name), "{{user}}", userName)
+ return &CharCard{
+ SysPrompt: sysPr,
+ FirstMsg: fm,
+ Role: c.Name,
+ FilePath: fpath,
+ }
+}
+
+type CharCard struct {
+ SysPrompt string `json:"sys_prompt"`
+ FirstMsg string `json:"first_msg"`
+ Role string `json:"role"`
+ FilePath string `json:"filepath"`
+}
+
+func (cc *CharCard) ToSpec(userName string) *CharCardSpec {
+ descr := strings.ReplaceAll(strings.ReplaceAll(cc.SysPrompt, cc.Role, "{{char}}"), userName, "{{user}}")
+ return &CharCardSpec{
+ Name: cc.Role,
+ Description: descr,
+ FirstMes: cc.FirstMsg,
+ Spec: "chara_card_v2",
+ SpecVersion: "2.0",
+ Extentions: []byte("{}"),
+ }
+}
diff --git a/models/db.go b/models/db.go
index 5f49003..090f46d 100644
--- a/models/db.go
+++ b/models/db.go
@@ -8,13 +8,14 @@ import (
type Chat struct {
ID uint32 `db:"id" json:"id"`
Name string `db:"name" json:"name"`
- Msgs string `db:"msgs" json:"msgs"` // []MessagesStory to string json
+ Msgs string `db:"msgs" json:"msgs"` // []RoleMsg to string json
+ Agent string `db:"agent" json:"agent"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
}
-func (c Chat) ToHistory() ([]MessagesStory, error) {
- resp := []MessagesStory{}
+func (c Chat) ToHistory() ([]RoleMsg, error) {
+ resp := []RoleMsg{}
if err := json.Unmarshal([]byte(c.Msgs), &resp); err != nil {
return nil, err
}
@@ -34,3 +35,13 @@ type Memory struct {
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
}
+
+// vector models
+
+type VectorRow struct {
+ Embeddings []float32 `db:"embeddings" json:"embeddings"`
+ Slug string `db:"slug" json:"slug"`
+ RawText string `db:"raw_text" json:"raw_text"`
+ Distance float32 `db:"distance" json:"distance"`
+ FileName string `db:"filename" json:"filename"`
+}
diff --git a/models/deepseek.go b/models/deepseek.go
new file mode 100644
index 0000000..8f9868d
--- /dev/null
+++ b/models/deepseek.go
@@ -0,0 +1,144 @@
+package models
+
+type DSChatReq struct {
+ Messages []RoleMsg `json:"messages"`
+ Model string `json:"model"`
+ Stream bool `json:"stream"`
+ FrequencyPenalty int `json:"frequency_penalty"`
+ MaxTokens int `json:"max_tokens"`
+ PresencePenalty int `json:"presence_penalty"`
+ Temperature float32 `json:"temperature"`
+ TopP float32 `json:"top_p"`
+ // ResponseFormat struct {
+ // Type string `json:"type"`
+ // } `json:"response_format"`
+ // Stop any `json:"stop"`
+ // StreamOptions any `json:"stream_options"`
+ // Tools any `json:"tools"`
+ // ToolChoice string `json:"tool_choice"`
+ // Logprobs bool `json:"logprobs"`
+ // TopLogprobs any `json:"top_logprobs"`
+}
+
+func NewDSChatReq(cb ChatBody) DSChatReq {
+ return DSChatReq{
+ Messages: cb.Messages,
+ Model: cb.Model,
+ Stream: cb.Stream,
+ MaxTokens: 2048,
+ PresencePenalty: 0,
+ FrequencyPenalty: 0,
+ Temperature: 1.0,
+ TopP: 1.0,
+ }
+}
+
+type DSCompletionReq struct {
+ Model string `json:"model"`
+ Prompt string `json:"prompt"`
+ Echo bool `json:"echo"`
+ FrequencyPenalty int `json:"frequency_penalty"`
+ // Logprobs int `json:"logprobs"`
+ MaxTokens int `json:"max_tokens"`
+ PresencePenalty int `json:"presence_penalty"`
+ Stop any `json:"stop"`
+ Stream bool `json:"stream"`
+ StreamOptions any `json:"stream_options"`
+ Suffix any `json:"suffix"`
+ Temperature float32 `json:"temperature"`
+ TopP float32 `json:"top_p"`
+}
+
+func NewDSCompletionReq(prompt, model string, temp float32, stopSlice []string) DSCompletionReq {
+ return DSCompletionReq{
+ Model: model,
+ Prompt: prompt,
+ Temperature: temp,
+ Stream: true,
+ Echo: false,
+ MaxTokens: 2048,
+ PresencePenalty: 0,
+ FrequencyPenalty: 0,
+ TopP: 1.0,
+ Stop: stopSlice,
+ }
+}
+
+type DSCompletionResp struct {
+ ID string `json:"id"`
+ Choices []struct {
+ FinishReason string `json:"finish_reason"`
+ Index int `json:"index"`
+ Logprobs struct {
+ TextOffset []int `json:"text_offset"`
+ TokenLogprobs []int `json:"token_logprobs"`
+ Tokens []string `json:"tokens"`
+ TopLogprobs []struct {
+ } `json:"top_logprobs"`
+ } `json:"logprobs"`
+ Text string `json:"text"`
+ } `json:"choices"`
+ Created int `json:"created"`
+ Model string `json:"model"`
+ SystemFingerprint string `json:"system_fingerprint"`
+ Object string `json:"object"`
+ Usage struct {
+ CompletionTokens int `json:"completion_tokens"`
+ PromptTokens int `json:"prompt_tokens"`
+ PromptCacheHitTokens int `json:"prompt_cache_hit_tokens"`
+ PromptCacheMissTokens int `json:"prompt_cache_miss_tokens"`
+ TotalTokens int `json:"total_tokens"`
+ CompletionTokensDetails struct {
+ ReasoningTokens int `json:"reasoning_tokens"`
+ } `json:"completion_tokens_details"`
+ } `json:"usage"`
+}
+
+type DSChatResp struct {
+ Choices []struct {
+ Delta struct {
+ Content string `json:"content"`
+ Role any `json:"role"`
+ } `json:"delta"`
+ FinishReason string `json:"finish_reason"`
+ Index int `json:"index"`
+ Logprobs any `json:"logprobs"`
+ } `json:"choices"`
+ Created int `json:"created"`
+ ID string `json:"id"`
+ Model string `json:"model"`
+ Object string `json:"object"`
+ SystemFingerprint string `json:"system_fingerprint"`
+ Usage struct {
+ CompletionTokens int `json:"completion_tokens"`
+ PromptTokens int `json:"prompt_tokens"`
+ TotalTokens int `json:"total_tokens"`
+ } `json:"usage"`
+}
+
+type DSChatStreamResp struct {
+ ID string `json:"id"`
+ Object string `json:"object"`
+ Created int `json:"created"`
+ Model string `json:"model"`
+ SystemFingerprint string `json:"system_fingerprint"`
+ Choices []struct {
+ Index int `json:"index"`
+ Delta struct {
+ Content string `json:"content"`
+ ReasoningContent string `json:"reasoning_content"`
+ } `json:"delta"`
+ Logprobs any `json:"logprobs"`
+ FinishReason string `json:"finish_reason"`
+ } `json:"choices"`
+}
+
+type DSBalance struct {
+ IsAvailable bool `json:"is_available"`
+ BalanceInfos []struct {
+ Currency string `json:"currency"`
+ TotalBalance string `json:"total_balance"`
+ GrantedBalance string `json:"granted_balance"`
+ ToppedUpBalance string `json:"topped_up_balance"`
+ } `json:"balance_infos"`
+}
diff --git a/models/embed.go b/models/embed.go
new file mode 100644
index 0000000..078312c
--- /dev/null
+++ b/models/embed.go
@@ -0,0 +1,15 @@
+package models
+
+type LCPEmbedResp struct {
+ Model string `json:"model"`
+ Object string `json:"object"`
+ Usage struct {
+ PromptTokens int `json:"prompt_tokens"`
+ TotalTokens int `json:"total_tokens"`
+ } `json:"usage"`
+ Data []struct {
+ Embedding []float32 `json:"embedding"`
+ Index int `json:"index"`
+ Object string `json:"object"`
+ } `json:"data"`
+}
diff --git a/models/extra.go b/models/extra.go
new file mode 100644
index 0000000..e1ca80f
--- /dev/null
+++ b/models/extra.go
@@ -0,0 +1,8 @@
+package models
+
+type AudioFormat string
+
+const (
+ AFWav AudioFormat = "wav"
+ AFMP3 AudioFormat = "mp3"
+)
diff --git a/models/models.go b/models/models.go
index 880779f..912f72b 100644
--- a/models/models.go
+++ b/models/models.go
@@ -1,19 +1,17 @@
package models
import (
+ "encoding/base64"
+ "encoding/json"
"fmt"
+ "os"
"strings"
)
-// type FuncCall struct {
-// XMLName xml.Name `xml:"tool_call"`
-// Name string `xml:"name"`
-// Args []string `xml:"args"`
-// }
-
type FuncCall struct {
- Name string `json:"name"`
- Args string `json:"args"`
+ ID string `json:"id,omitempty"`
+ Name string `json:"name"`
+ Args map[string]string `json:"args"`
}
type LLMResp struct {
@@ -36,13 +34,25 @@ type LLMResp struct {
ID string `json:"id"`
}
+type ToolDeltaFunc struct {
+ Name string `json:"name"`
+ Arguments string `json:"arguments"`
+}
+
+type ToolDeltaResp struct {
+ ID string `json:"id,omitempty"`
+ Index int `json:"index"`
+ Function ToolDeltaFunc `json:"function"`
+}
+
// for streaming
type LLMRespChunk struct {
Choices []struct {
FinishReason string `json:"finish_reason"`
Index int `json:"index"`
Delta struct {
- Content string `json:"content"`
+ Content string `json:"content"`
+ ToolCalls []ToolDeltaResp `json:"tool_calls"`
} `json:"delta"`
} `json:"choices"`
Created int `json:"created"`
@@ -56,56 +66,466 @@ type LLMRespChunk struct {
} `json:"usage"`
}
-type MessagesStory struct {
- Role string `json:"role"`
- Content string `json:"content"`
+type TextChunk struct {
+ Chunk string
+ ToolChunk string
+ Finished bool
+ ToolResp bool
+ FuncName string
+ ToolID string
+}
+
+type TextContentPart struct {
+ Type string `json:"type"`
+ Text string `json:"text"`
+}
+
+type ImageContentPart struct {
+ Type string `json:"type"`
+ ImageURL struct {
+ URL string `json:"url"`
+ } `json:"image_url"`
+}
+
+// RoleMsg represents a message with content that can be either a simple string or structured content parts
+type RoleMsg struct {
+ Role string `json:"role"`
+ Content string `json:"-"`
+ ContentParts []interface{} `json:"-"`
+ ToolCallID string `json:"tool_call_id,omitempty"` // For tool response messages
+ hasContentParts bool // Flag to indicate which content type to marshal
}
-func (m MessagesStory) ToText(i int) string {
- icon := ""
- switch m.Role {
- case "assistant":
- icon = fmt.Sprintf("(%d) <🤖>: ", i)
- case "user":
- icon = fmt.Sprintf("(%d) <user>: ", i)
- case "system":
- icon = fmt.Sprintf("(%d) <system>: ", i)
- case "tool":
- icon = fmt.Sprintf("(%d) <tool>: ", i)
+// MarshalJSON implements custom JSON marshaling for RoleMsg
+func (m RoleMsg) MarshalJSON() ([]byte, error) {
+ if m.hasContentParts {
+ // Use structured content format
+ aux := struct {
+ Role string `json:"role"`
+ Content []interface{} `json:"content"`
+ ToolCallID string `json:"tool_call_id,omitempty"`
+ }{
+ Role: m.Role,
+ Content: m.ContentParts,
+ ToolCallID: m.ToolCallID,
+ }
+ return json.Marshal(aux)
+ } else {
+ // Use simple content format
+ aux := struct {
+ Role string `json:"role"`
+ Content string `json:"content"`
+ ToolCallID string `json:"tool_call_id,omitempty"`
+ }{
+ Role: m.Role,
+ Content: m.Content,
+ ToolCallID: m.ToolCallID,
+ }
+ return json.Marshal(aux)
+ }
+}
+
+// UnmarshalJSON implements custom JSON unmarshaling for RoleMsg
+func (m *RoleMsg) UnmarshalJSON(data []byte) error {
+ // First, try to unmarshal as structured content format
+ var structured struct {
+ Role string `json:"role"`
+ Content []interface{} `json:"content"`
+ ToolCallID string `json:"tool_call_id,omitempty"`
+ }
+ if err := json.Unmarshal(data, &structured); err == nil && len(structured.Content) > 0 {
+ m.Role = structured.Role
+ m.ContentParts = structured.Content
+ m.ToolCallID = structured.ToolCallID
+ m.hasContentParts = true
+ return nil
+ }
+
+ // Otherwise, unmarshal as simple content format
+ var simple struct {
+ Role string `json:"role"`
+ Content string `json:"content"`
+ ToolCallID string `json:"tool_call_id,omitempty"`
+ }
+ if err := json.Unmarshal(data, &simple); err != nil {
+ return err
+ }
+ m.Role = simple.Role
+ m.Content = simple.Content
+ m.ToolCallID = simple.ToolCallID
+ m.hasContentParts = false
+ return nil
+}
+
+func (m RoleMsg) ToText(i int) string {
+ icon := fmt.Sprintf("(%d)", i)
+
+ // Convert content to string representation
+ contentStr := ""
+ if !m.hasContentParts {
+ contentStr = m.Content
+ } else {
+ // For structured content, just take the text parts
+ var textParts []string
+ for _, part := range m.ContentParts {
+ if partMap, ok := part.(map[string]interface{}); ok {
+ if partType, exists := partMap["type"]; exists && partType == "text" {
+ if textVal, textExists := partMap["text"]; textExists {
+ if textStr, isStr := textVal.(string); isStr {
+ textParts = append(textParts, textStr)
+ }
+ }
+ }
+ }
+ }
+ contentStr = strings.Join(textParts, " ") + " "
+ }
+
+ // check if already has role annotation (/completion makes them)
+ if !strings.HasPrefix(contentStr, m.Role+":") {
+ icon = fmt.Sprintf("(%d) <%s>: ", i, m.Role)
}
- textMsg := fmt.Sprintf("%s%s\n", icon, m.Content)
+ textMsg := fmt.Sprintf("[-:-:b]%s[-:-:-]\n%s\n", icon, contentStr)
return strings.ReplaceAll(textMsg, "\n\n", "\n")
}
+func (m RoleMsg) ToPrompt() string {
+ contentStr := ""
+ if !m.hasContentParts {
+ contentStr = m.Content
+ } else {
+ // For structured content, just take the text parts
+ var textParts []string
+ for _, part := range m.ContentParts {
+ if partMap, ok := part.(map[string]interface{}); ok {
+ if partType, exists := partMap["type"]; exists && partType == "text" {
+ if textVal, textExists := partMap["text"]; textExists {
+ if textStr, isStr := textVal.(string); isStr {
+ textParts = append(textParts, textStr)
+ }
+ }
+ }
+ }
+ }
+ contentStr = strings.Join(textParts, " ") + " "
+ }
+ return strings.ReplaceAll(fmt.Sprintf("%s:\n%s", m.Role, contentStr), "\n\n", "\n")
+}
+
+// NewRoleMsg creates a simple RoleMsg with string content
+func NewRoleMsg(role, content string) RoleMsg {
+ return RoleMsg{
+ Role: role,
+ Content: content,
+ hasContentParts: false,
+ }
+}
+
+// NewMultimodalMsg creates a RoleMsg with structured content parts (text and images)
+func NewMultimodalMsg(role string, contentParts []interface{}) RoleMsg {
+ return RoleMsg{
+ Role: role,
+ ContentParts: contentParts,
+ hasContentParts: true,
+ }
+}
+
+// HasContent returns true if the message has either string content or structured content parts
+func (m RoleMsg) HasContent() bool {
+ if m.Content != "" {
+ return true
+ }
+ if m.hasContentParts && len(m.ContentParts) > 0 {
+ return true
+ }
+ return false
+}
+
+// IsContentParts returns true if the message uses structured content parts
+func (m RoleMsg) IsContentParts() bool {
+ return m.hasContentParts
+}
+
+// GetContentParts returns the content parts of the message
+func (m RoleMsg) GetContentParts() []interface{} {
+ return m.ContentParts
+}
+
+// Copy creates a copy of the RoleMsg with all fields
+func (m RoleMsg) Copy() RoleMsg {
+ return RoleMsg{
+ Role: m.Role,
+ Content: m.Content,
+ ContentParts: m.ContentParts,
+ ToolCallID: m.ToolCallID,
+ hasContentParts: m.hasContentParts,
+ }
+}
+
+// AddTextPart adds a text content part to the message
+func (m *RoleMsg) AddTextPart(text string) {
+ if !m.hasContentParts {
+ // Convert to content parts format
+ if m.Content != "" {
+ m.ContentParts = []interface{}{TextContentPart{Type: "text", Text: m.Content}}
+ } else {
+ m.ContentParts = []interface{}{}
+ }
+ m.hasContentParts = true
+ }
+
+ textPart := TextContentPart{Type: "text", Text: text}
+ m.ContentParts = append(m.ContentParts, textPart)
+}
+
+// AddImagePart adds an image content part to the message
+func (m *RoleMsg) AddImagePart(imageURL string) {
+ if !m.hasContentParts {
+ // Convert to content parts format
+ if m.Content != "" {
+ m.ContentParts = []interface{}{TextContentPart{Type: "text", Text: m.Content}}
+ } else {
+ m.ContentParts = []interface{}{}
+ }
+ m.hasContentParts = true
+ }
+
+ imagePart := ImageContentPart{
+ Type: "image_url",
+ ImageURL: struct {
+ URL string `json:"url"`
+ }{URL: imageURL},
+ }
+ m.ContentParts = append(m.ContentParts, imagePart)
+}
+
+// CreateImageURLFromPath creates a data URL from an image file path
+func CreateImageURLFromPath(imagePath string) (string, error) {
+ // Read the image file
+ data, err := os.ReadFile(imagePath)
+ if err != nil {
+ return "", err
+ }
+
+ // Determine the image format based on file extension
+ var mimeType string
+ switch {
+ case strings.HasSuffix(strings.ToLower(imagePath), ".png"):
+ mimeType = "image/png"
+ case strings.HasSuffix(strings.ToLower(imagePath), ".jpg"):
+ fallthrough
+ case strings.HasSuffix(strings.ToLower(imagePath), ".jpeg"):
+ mimeType = "image/jpeg"
+ case strings.HasSuffix(strings.ToLower(imagePath), ".gif"):
+ mimeType = "image/gif"
+ case strings.HasSuffix(strings.ToLower(imagePath), ".webp"):
+ mimeType = "image/webp"
+ default:
+ mimeType = "image/jpeg" // default
+ }
+
+ // Encode to base64
+ encoded := base64.StdEncoding.EncodeToString(data)
+
+ // Create data URL
+ return fmt.Sprintf("data:%s;base64,%s", mimeType, encoded), nil
+}
+
type ChatBody struct {
- Model string `json:"model"`
- Stream bool `json:"stream"`
- Messages []MessagesStory `json:"messages"`
-}
-
-type ChatToolsBody struct {
- Model string `json:"model"`
- Messages []MessagesStory `json:"messages"`
- Tools []struct {
- Type string `json:"type"`
- Function struct {
- Name string `json:"name"`
- Description string `json:"description"`
- Parameters struct {
- Type string `json:"type"`
- Properties struct {
- Location struct {
- Type string `json:"type"`
- Description string `json:"description"`
- } `json:"location"`
- Unit struct {
- Type string `json:"type"`
- Enum []string `json:"enum"`
- } `json:"unit"`
- } `json:"properties"`
- Required []string `json:"required"`
- } `json:"parameters"`
- } `json:"function"`
- } `json:"tools"`
- ToolChoice string `json:"tool_choice"`
+ Model string `json:"model"`
+ Stream bool `json:"stream"`
+ Messages []RoleMsg `json:"messages"`
+}
+
+func (cb *ChatBody) Rename(oldname, newname string) {
+ for i, m := range cb.Messages {
+ cb.Messages[i].Content = strings.ReplaceAll(m.Content, oldname, newname)
+ cb.Messages[i].Role = strings.ReplaceAll(m.Role, oldname, newname)
+ }
+}
+
+func (cb *ChatBody) ListRoles() []string {
+ namesMap := make(map[string]struct{})
+ for _, m := range cb.Messages {
+ namesMap[m.Role] = struct{}{}
+ }
+ resp := make([]string, len(namesMap))
+ i := 0
+ for k := range namesMap {
+ resp[i] = k
+ i++
+ }
+ return resp
+}
+
+func (cb *ChatBody) MakeStopSlice() []string {
+ namesMap := make(map[string]struct{})
+ for _, m := range cb.Messages {
+ namesMap[m.Role] = struct{}{}
+ }
+ ss := []string{"<|im_end|>"}
+ for k := range namesMap {
+ ss = append(ss, k+":\n")
+ }
+ return ss
+}
+
+type EmbeddingResp struct {
+ Embedding []float32 `json:"embedding"`
+ Index uint32 `json:"index"`
+}
+
+// type EmbeddingsResp struct {
+// Model string `json:"model"`
+// Object string `json:"object"`
+// Usage struct {
+// PromptTokens int `json:"prompt_tokens"`
+// TotalTokens int `json:"total_tokens"`
+// } `json:"usage"`
+// Data []struct {
+// Embedding []float32 `json:"embedding"`
+// Index int `json:"index"`
+// Object string `json:"object"`
+// } `json:"data"`
+// }
+
+// === tools models
+
+type ToolArgProps struct {
+ Type string `json:"type"`
+ Description string `json:"description"`
+}
+
+type ToolFuncParams struct {
+ Type string `json:"type"`
+ Properties map[string]ToolArgProps `json:"properties"`
+ Required []string `json:"required"`
+}
+
+type ToolFunc struct {
+ Name string `json:"name"`
+ Description string `json:"description"`
+ Parameters ToolFuncParams `json:"parameters"`
+}
+
+type Tool struct {
+ Type string `json:"type"`
+ Function ToolFunc `json:"function"`
+}
+
+type OpenAIReq struct {
+ *ChatBody
+ Tools []Tool `json:"tools"`
+}
+
+// ===
+
+// type LLMModels struct {
+// Object string `json:"object"`
+// Data []struct {
+// ID string `json:"id"`
+// Object string `json:"object"`
+// Created int `json:"created"`
+// OwnedBy string `json:"owned_by"`
+// Meta struct {
+// VocabType int `json:"vocab_type"`
+// NVocab int `json:"n_vocab"`
+// NCtxTrain int `json:"n_ctx_train"`
+// NEmbd int `json:"n_embd"`
+// NParams int64 `json:"n_params"`
+// Size int64 `json:"size"`
+// } `json:"meta"`
+// } `json:"data"`
+// }
+
+type LlamaCPPReq struct {
+ Model string `json:"model"`
+ Stream bool `json:"stream"`
+ // For multimodal requests, prompt should be an object with prompt_string and multimodal_data
+ // For regular requests, prompt is a string
+ Prompt interface{} `json:"prompt"` // Can be string or object with prompt_string and multimodal_data
+ Temperature float32 `json:"temperature"`
+ DryMultiplier float32 `json:"dry_multiplier"`
+ Stop []string `json:"stop"`
+ MinP float32 `json:"min_p"`
+ NPredict int32 `json:"n_predict"`
+ // MaxTokens int `json:"max_tokens"`
+ // DryBase float64 `json:"dry_base"`
+ // DryAllowedLength int `json:"dry_allowed_length"`
+ // DryPenaltyLastN int `json:"dry_penalty_last_n"`
+ // CachePrompt bool `json:"cache_prompt"`
+ // DynatempRange int `json:"dynatemp_range"`
+ // DynatempExponent int `json:"dynatemp_exponent"`
+ // TopK int `json:"top_k"`
+ // TopP float32 `json:"top_p"`
+ // TypicalP int `json:"typical_p"`
+ // XtcProbability int `json:"xtc_probability"`
+ // XtcThreshold float32 `json:"xtc_threshold"`
+ // RepeatLastN int `json:"repeat_last_n"`
+ // RepeatPenalty int `json:"repeat_penalty"`
+ // PresencePenalty int `json:"presence_penalty"`
+ // FrequencyPenalty int `json:"frequency_penalty"`
+ // Samplers string `json:"samplers"`
+}
+
+type PromptObject struct {
+ PromptString string `json:"prompt_string"`
+ MultimodalData []string `json:"multimodal_data,omitempty"`
+ // Alternative field name used by some llama.cpp implementations
+ ImageData []string `json:"image_data,omitempty"` // For compatibility
+}
+
+func NewLCPReq(prompt, model string, multimodalData []string, props map[string]float32, stopStrings []string) LlamaCPPReq {
+ var finalPrompt interface{}
+ if len(multimodalData) > 0 {
+ // When multimodal data is present, use the object format as per Python example:
+ // { "prompt": { "prompt_string": "...", "multimodal_data": [...] } }
+ finalPrompt = PromptObject{
+ PromptString: prompt,
+ MultimodalData: multimodalData,
+ ImageData: multimodalData, // Also populate for compatibility with different llama.cpp versions
+ }
+ } else {
+ // When no multimodal data, use plain string
+ finalPrompt = prompt
+ }
+ return LlamaCPPReq{
+ Model: model,
+ Stream: true,
+ Prompt: finalPrompt,
+ Temperature: props["temperature"],
+ DryMultiplier: props["dry_multiplier"],
+ Stop: stopStrings,
+ MinP: props["min_p"],
+ NPredict: int32(props["n_predict"]),
+ }
+}
+
+type LlamaCPPResp struct {
+ Content string `json:"content"`
+ Stop bool `json:"stop"`
+}
+
+type LCPModels struct {
+ Data []struct {
+ ID string `json:"id"`
+ Object string `json:"object"`
+ OwnedBy string `json:"owned_by"`
+ Created int `json:"created"`
+ InCache bool `json:"in_cache"`
+ Path string `json:"path"`
+ Status struct {
+ Value string `json:"value"`
+ Args []string `json:"args"`
+ } `json:"status"`
+ } `json:"data"`
+ Object string `json:"object"`
+}
+
+func (lcp *LCPModels) ListModels() []string {
+ resp := []string{}
+ for _, model := range lcp.Data {
+ resp = append(resp, model.ID)
+ }
+ return resp
}
diff --git a/models/openrouter.go b/models/openrouter.go
new file mode 100644
index 0000000..50f26b6
--- /dev/null
+++ b/models/openrouter.go
@@ -0,0 +1,157 @@
+package models
+
+// openrouter
+// https://openrouter.ai/docs/api-reference/completion
+type OpenRouterCompletionReq struct {
+ Model string `json:"model"`
+ Prompt string `json:"prompt"`
+ Stream bool `json:"stream"`
+ Temperature float32 `json:"temperature"`
+ Stop []string `json:"stop"` // not present in docs
+ MinP float32 `json:"min_p"`
+ NPredict int32 `json:"max_tokens"`
+}
+
+func NewOpenRouterCompletionReq(model, prompt string, props map[string]float32, stopStrings []string) OpenRouterCompletionReq {
+ return OpenRouterCompletionReq{
+ Stream: true,
+ Prompt: prompt,
+ Temperature: props["temperature"],
+ MinP: props["min_p"],
+ NPredict: int32(props["n_predict"]),
+ Stop: stopStrings,
+ Model: model,
+ }
+}
+
+type OpenRouterChatReq struct {
+ Messages []RoleMsg `json:"messages"`
+ Model string `json:"model"`
+ Stream bool `json:"stream"`
+ Temperature float32 `json:"temperature"`
+ MinP float32 `json:"min_p"`
+ NPredict int32 `json:"max_tokens"`
+ Tools []Tool `json:"tools"`
+}
+
+func NewOpenRouterChatReq(cb ChatBody, props map[string]float32) OpenRouterChatReq {
+ return OpenRouterChatReq{
+ Messages: cb.Messages,
+ Model: cb.Model,
+ Stream: cb.Stream,
+ Temperature: props["temperature"],
+ MinP: props["min_p"],
+ NPredict: int32(props["n_predict"]),
+ }
+}
+
+type OpenRouterChatRespNonStream struct {
+ ID string `json:"id"`
+ Provider string `json:"provider"`
+ Model string `json:"model"`
+ Object string `json:"object"`
+ Created int `json:"created"`
+ Choices []struct {
+ Logprobs any `json:"logprobs"`
+ FinishReason string `json:"finish_reason"`
+ NativeFinishReason string `json:"native_finish_reason"`
+ Index int `json:"index"`
+ Message struct {
+ Role string `json:"role"`
+ Content string `json:"content"`
+ Refusal any `json:"refusal"`
+ Reasoning any `json:"reasoning"`
+ ToolCalls []ToolDeltaResp `json:"tool_calls"`
+ } `json:"message"`
+ } `json:"choices"`
+ Usage struct {
+ PromptTokens int `json:"prompt_tokens"`
+ CompletionTokens int `json:"completion_tokens"`
+ TotalTokens int `json:"total_tokens"`
+ } `json:"usage"`
+}
+
+type OpenRouterChatResp struct {
+ ID string `json:"id"`
+ Provider string `json:"provider"`
+ Model string `json:"model"`
+ Object string `json:"object"`
+ Created int `json:"created"`
+ Choices []struct {
+ Index int `json:"index"`
+ Delta struct {
+ Role string `json:"role"`
+ Content string `json:"content"`
+ ToolCalls []ToolDeltaResp `json:"tool_calls"`
+ } `json:"delta"`
+ FinishReason string `json:"finish_reason"`
+ NativeFinishReason string `json:"native_finish_reason"`
+ Logprobs any `json:"logprobs"`
+ } `json:"choices"`
+}
+
+type OpenRouterCompletionResp struct {
+ ID string `json:"id"`
+ Provider string `json:"provider"`
+ Model string `json:"model"`
+ Object string `json:"object"`
+ Created int `json:"created"`
+ Choices []struct {
+ Text string `json:"text"`
+ FinishReason string `json:"finish_reason"`
+ NativeFinishReason string `json:"native_finish_reason"`
+ Logprobs any `json:"logprobs"`
+ } `json:"choices"`
+}
+
+type ORModel struct {
+ ID string `json:"id"`
+ CanonicalSlug string `json:"canonical_slug"`
+ HuggingFaceID string `json:"hugging_face_id"`
+ Name string `json:"name"`
+ Created int `json:"created"`
+ Description string `json:"description"`
+ ContextLength int `json:"context_length"`
+ Architecture struct {
+ Modality string `json:"modality"`
+ InputModalities []string `json:"input_modalities"`
+ OutputModalities []string `json:"output_modalities"`
+ Tokenizer string `json:"tokenizer"`
+ InstructType any `json:"instruct_type"`
+ } `json:"architecture"`
+ Pricing struct {
+ Prompt string `json:"prompt"`
+ Completion string `json:"completion"`
+ Request string `json:"request"`
+ Image string `json:"image"`
+ Audio string `json:"audio"`
+ WebSearch string `json:"web_search"`
+ InternalReasoning string `json:"internal_reasoning"`
+ } `json:"pricing,omitempty"`
+ TopProvider struct {
+ ContextLength int `json:"context_length"`
+ MaxCompletionTokens int `json:"max_completion_tokens"`
+ IsModerated bool `json:"is_moderated"`
+ } `json:"top_provider"`
+ PerRequestLimits any `json:"per_request_limits"`
+ SupportedParameters []string `json:"supported_parameters"`
+}
+
+type ORModels struct {
+ Data []ORModel `json:"data"`
+}
+
+func (orm *ORModels) ListModels(free bool) []string {
+ resp := []string{}
+ for _, model := range orm.Data {
+ if free {
+ if model.Pricing.Prompt == "0" && model.Pricing.Request == "0" &&
+ model.Pricing.Completion == "0" {
+ resp = append(resp, model.ID)
+ }
+ } else {
+ resp = append(resp, model.ID)
+ }
+ }
+ return resp
+}