158 lines
3.6 KiB
Go
158 lines
3.6 KiB
Go
package gollm
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
type ollamaChatRequest struct {
|
|
Model string `json:"model"`
|
|
Messages []openaiMessage `json:"messages"`
|
|
Stream bool `json:"stream"`
|
|
Options map[string]any `json:"options,omitempty"`
|
|
}
|
|
|
|
func ollamaOptions(opt Options) map[string]any {
|
|
if opt.Temperature == nil && opt.MaxTokens == nil {
|
|
return nil
|
|
}
|
|
m := map[string]any{}
|
|
if opt.Temperature != nil {
|
|
m["temperature"] = *opt.Temperature
|
|
}
|
|
if opt.MaxTokens != nil {
|
|
m["num_predict"] = *opt.MaxTokens
|
|
}
|
|
return m
|
|
}
|
|
|
|
func (c *Client) ollamaChat(ctx context.Context, messages []Message, opt Options) (string, error) {
|
|
body, err := json.Marshal(ollamaChatRequest{
|
|
Model: opt.Model,
|
|
Messages: openaiMessages(messages),
|
|
Stream: false,
|
|
Options: ollamaOptions(opt),
|
|
})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+"/api/chat", bytes.NewReader(body))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
c.auth(req)
|
|
res, err := c.http.Do(req)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer res.Body.Close()
|
|
raw, err := io.ReadAll(res.Body)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if res.StatusCode < 200 || res.StatusCode >= 300 {
|
|
return "", fmt.Errorf("ollama chat: %s: %s", res.Status, truncate(raw))
|
|
}
|
|
var parsed struct {
|
|
Message struct {
|
|
Content string `json:"content"`
|
|
} `json:"message"`
|
|
}
|
|
if err := json.Unmarshal(raw, &parsed); err != nil {
|
|
return "", err
|
|
}
|
|
return parsed.Message.Content, nil
|
|
}
|
|
|
|
func (c *Client) ollamaStream(ctx context.Context, messages []Message, opt Options, yield func(string) error) error {
|
|
body, err := json.Marshal(ollamaChatRequest{
|
|
Model: opt.Model,
|
|
Messages: openaiMessages(messages),
|
|
Stream: true,
|
|
Options: ollamaOptions(opt),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+"/api/chat", bytes.NewReader(body))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
c.auth(req)
|
|
res, err := c.http.Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer res.Body.Close()
|
|
if res.StatusCode < 200 || res.StatusCode >= 300 {
|
|
raw, _ := io.ReadAll(res.Body)
|
|
return fmt.Errorf("ollama stream: %s: %s", res.Status, truncate(raw))
|
|
}
|
|
sc := bufio.NewScanner(res.Body)
|
|
sc.Buffer(nil, 1024*1024)
|
|
for sc.Scan() {
|
|
line := strings.TrimSpace(sc.Text())
|
|
if line == "" {
|
|
continue
|
|
}
|
|
var parsed struct {
|
|
Message struct {
|
|
Content string `json:"content"`
|
|
} `json:"message"`
|
|
}
|
|
if err := json.Unmarshal([]byte(line), &parsed); err != nil {
|
|
return err
|
|
}
|
|
if parsed.Message.Content == "" {
|
|
continue
|
|
}
|
|
if err := yield(parsed.Message.Content); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return sc.Err()
|
|
}
|
|
|
|
func (c *Client) ollamaListModels(ctx context.Context) ([]string, error) {
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+"/api/tags", nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
c.auth(req)
|
|
res, err := c.http.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer res.Body.Close()
|
|
raw, err := io.ReadAll(res.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if res.StatusCode < 200 || res.StatusCode >= 300 {
|
|
return nil, fmt.Errorf("ollama tags: %s: %s", res.Status, truncate(raw))
|
|
}
|
|
var parsed struct {
|
|
Models []struct {
|
|
Name string `json:"name"`
|
|
} `json:"models"`
|
|
}
|
|
if err := json.Unmarshal(raw, &parsed); err != nil {
|
|
return nil, err
|
|
}
|
|
names := make([]string, 0, len(parsed.Models))
|
|
for _, m := range parsed.Models {
|
|
if m.Name != "" {
|
|
names = append(names, m.Name)
|
|
}
|
|
}
|
|
return names, nil
|
|
}
|