diff options
Diffstat (limited to 'models')
| -rw-r--r-- | models/card.go | 58 | ||||
| -rw-r--r-- | models/db.go | 17 | ||||
| -rw-r--r-- | models/deepseek.go | 144 | ||||
| -rw-r--r-- | models/embed.go | 15 | ||||
| -rw-r--r-- | models/extra.go | 8 | ||||
| -rw-r--r-- | models/models.go | 528 | ||||
| -rw-r--r-- | models/openrouter.go | 157 |
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 +} |
