Nik Afiq ad50d641bd
All checks were successful
CI / test (push) Successful in 5s
CI / build-ai-gateway (push) Successful in 43s
CI / build-ha-gateway (push) Successful in 47s
CI / build-discord-bot (push) Successful in 41s
feat: enhance AI model management in Discord bot
- Updated LLMClient interface to support model-specific generation and model listing.
- Integrated model store and validator into the command application for managing AI models.
- Implemented commands for setting, getting, and listing active AI models in Discord.
- Enhanced AI query handling to utilize the selected model and return model information in responses.
- Added caching mechanism for model validation to improve performance.
- Introduced gRPC methods for listing available AI models in the ai-gateway.
- Updated protobuf definitions to include model-related fields and messages.
- Added tests for model store and validator functionalities.
2026-04-21 22:52:00 +09:00

122 lines
3.0 KiB
Go

package ollama
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"strings"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)
type generateRequest struct {
Model string `json:"model"`
Prompt string `json:"prompt"`
Stream bool `json:"stream"`
Think *bool `json:"think,omitempty"`
}
type generateResponse struct {
Response string `json:"response"`
}
type listModelsResponse struct {
Models []struct {
Name string `json:"name"`
} `json:"models"`
}
// Client implements the LLM driven port with the Ollama generate API.
type Client struct {
baseURL string
http *http.Client
}
// New constructs an Ollama client with OTel-instrumented transport.
func New(baseURL string, httpClient *http.Client) *Client {
if httpClient == nil {
httpClient = &http.Client{Transport: otelhttp.NewTransport(http.DefaultTransport)}
}
if httpClient.Transport == nil {
httpClient.Transport = otelhttp.NewTransport(http.DefaultTransport)
}
return &Client{baseURL: baseURL, http: httpClient}
}
// Generate sends one non-streaming prompt to Ollama.
func (c *Client) Generate(ctx context.Context, model, prompt string) (string, error) {
reqBody := generateRequest{
Model: model,
Prompt: prompt,
Stream: false,
}
if isThinkingModel(model) {
disabled := false
reqBody.Think = &disabled
}
body, err := json.Marshal(reqBody)
if err != nil {
return "", fmt.Errorf("marshal ollama request: %w", err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+"/api/generate", bytes.NewReader(body))
if err != nil {
return "", fmt.Errorf("build ollama request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
resp, err := c.http.Do(req)
if err != nil {
return "", fmt.Errorf("call ollama: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return "", fmt.Errorf("ollama returned status %s", resp.Status)
}
var out generateResponse
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
return "", fmt.Errorf("decode ollama response: %w", err)
}
return out.Response, nil
}
// ListModels returns the installed model names from Ollama.
func (c *Client) ListModels(ctx context.Context) ([]string, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+"/api/tags", nil)
if err != nil {
return nil, fmt.Errorf("build ollama list models request: %w", err)
}
resp, err := c.http.Do(req)
if err != nil {
return nil, fmt.Errorf("list ollama models: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("ollama returned status %s", resp.Status)
}
var out listModelsResponse
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
return nil, fmt.Errorf("decode ollama list models response: %w", err)
}
names := make([]string, 0, len(out.Models))
for _, model := range out.Models {
if model.Name != "" {
names = append(names, model.Name)
}
}
return names, nil
}
func isThinkingModel(name string) bool {
return strings.HasPrefix(name, "qwen3")
}