149 lines
3.9 KiB
Go
149 lines
3.9 KiB
Go
|
|
package ollama
|
||
|
|
|
||
|
|
import (
|
||
|
|
"bytes"
|
||
|
|
"encoding/json"
|
||
|
|
"fmt"
|
||
|
|
"io"
|
||
|
|
"net/http"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
const defaultTimeout = 10 * time.Minute
|
||
|
|
|
||
|
|
// Client calls Ollama /api/chat.
|
||
|
|
type Client struct {
|
||
|
|
BaseURL string
|
||
|
|
HTTPClient *http.Client
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewClient creates an Ollama client. baseURL is e.g. "http://localhost:11434".
|
||
|
|
func NewClient(baseURL string) *Client {
|
||
|
|
return &Client{
|
||
|
|
BaseURL: baseURL,
|
||
|
|
HTTPClient: &http.Client{
|
||
|
|
Timeout: defaultTimeout,
|
||
|
|
},
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ChatRequest matches Ollama POST /api/chat body.
|
||
|
|
type ChatRequest struct {
|
||
|
|
Model string `json:"model"`
|
||
|
|
Messages []ChatMessage `json:"messages"`
|
||
|
|
Stream bool `json:"stream"`
|
||
|
|
Format interface{} `json:"format,omitempty"` // "json" or JSON schema object
|
||
|
|
Tools []Tool `json:"tools,omitempty"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// ChatMessage is one message in the conversation.
|
||
|
|
type ChatMessage struct {
|
||
|
|
Role string `json:"role"` // "user", "assistant", "system", "tool"
|
||
|
|
Content string `json:"content,omitempty"`
|
||
|
|
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
|
||
|
|
ToolName string `json:"tool_name,omitempty"` // for role "tool"
|
||
|
|
}
|
||
|
|
|
||
|
|
// Tool defines a function the model may call.
|
||
|
|
type Tool struct {
|
||
|
|
Type string `json:"type"`
|
||
|
|
Function ToolFunc `json:"function"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// ToolFunc describes the function.
|
||
|
|
type ToolFunc struct {
|
||
|
|
Name string `json:"name"`
|
||
|
|
Description string `json:"description"`
|
||
|
|
Parameters interface{} `json:"parameters"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// ToolCall is a model request to call a tool.
|
||
|
|
type ToolCall struct {
|
||
|
|
Type string `json:"type"`
|
||
|
|
Function ToolCallFn `json:"function"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// ToolCallFn holds name and arguments.
|
||
|
|
// Arguments may come from Ollama as a JSON object or as a JSON string.
|
||
|
|
type ToolCallFn struct {
|
||
|
|
Name string `json:"name"`
|
||
|
|
Arguments interface{} `json:"arguments"` // object or string
|
||
|
|
}
|
||
|
|
|
||
|
|
// QueryFromToolCall returns the "query" argument from a web_search tool call.
|
||
|
|
// Ollama may send arguments as a map or as a JSON string.
|
||
|
|
func QueryFromToolCall(tc ToolCall) string {
|
||
|
|
switch v := tc.Function.Arguments.(type) {
|
||
|
|
case map[string]interface{}:
|
||
|
|
if q, _ := v["query"].(string); q != "" {
|
||
|
|
return q
|
||
|
|
}
|
||
|
|
case string:
|
||
|
|
var m map[string]interface{}
|
||
|
|
if json.Unmarshal([]byte(v), &m) == nil {
|
||
|
|
if q, _ := m["query"].(string); q != "" {
|
||
|
|
return q
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return ""
|
||
|
|
}
|
||
|
|
|
||
|
|
// ChatResponse is the Ollama /api/chat response.
|
||
|
|
type ChatResponse struct {
|
||
|
|
Message ChatMessage `json:"message"`
|
||
|
|
Done bool `json:"done"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// Chat sends a chat request and returns the response.
|
||
|
|
func (c *Client) Chat(req *ChatRequest) (*ChatResponse, error) {
|
||
|
|
body, err := json.Marshal(req)
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("marshal request: %w", err)
|
||
|
|
}
|
||
|
|
url := c.BaseURL + "/api/chat"
|
||
|
|
httpReq, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body))
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("new request: %w", err)
|
||
|
|
}
|
||
|
|
httpReq.Header.Set("Content-Type", "application/json")
|
||
|
|
|
||
|
|
resp, err := c.HTTPClient.Do(httpReq)
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("do request: %w", err)
|
||
|
|
}
|
||
|
|
defer resp.Body.Close()
|
||
|
|
|
||
|
|
if resp.StatusCode != http.StatusOK {
|
||
|
|
b, _ := io.ReadAll(resp.Body)
|
||
|
|
return nil, fmt.Errorf("ollama returned %d: %s", resp.StatusCode, string(b))
|
||
|
|
}
|
||
|
|
|
||
|
|
var out ChatResponse
|
||
|
|
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
|
||
|
|
return nil, fmt.Errorf("decode response: %w", err)
|
||
|
|
}
|
||
|
|
return &out, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// WebSearchTool returns the tool definition for web_search (Tavily).
|
||
|
|
func WebSearchTool() Tool {
|
||
|
|
return Tool{
|
||
|
|
Type: "function",
|
||
|
|
Function: ToolFunc{
|
||
|
|
Name: "web_search",
|
||
|
|
Description: "Search the web for current information. Use when you need up-to-date or factual information from the internet.",
|
||
|
|
Parameters: map[string]interface{}{
|
||
|
|
"type": "object",
|
||
|
|
"properties": map[string]interface{}{
|
||
|
|
"query": map[string]interface{}{
|
||
|
|
"type": "string",
|
||
|
|
"description": "Search query",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
"required": []string{"query"},
|
||
|
|
},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
}
|