- Implemented new gRPC service `AIService` in `proto/ai/v1/ai.proto` for handling natural language queries. - Generated Go code for the gRPC service and messages in `gen/ai/v1/`. - Created `services/ai-gateway/` directory structure with necessary files for the service. - Added configuration loading and structured logging. - Implemented domain logic for intent parsing and interaction with Home Assistant. - Established outbound adapters for Ollama and Home Assistant with mTLS support. - Updated `go.work` to include the new service and maintain existing dependencies. - Modified `discord-bot` to use the new `ai-gateway` for AI interactions. - Added deployment manifest for Kubernetes and CI/CD configuration for building and deploying the service.
74 lines
1.9 KiB
Go
74 lines
1.9 KiB
Go
package ollama
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
|
|
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
|
)
|
|
|
|
type generateRequest struct {
|
|
Model string `json:"model"`
|
|
Prompt string `json:"prompt"`
|
|
Stream bool `json:"stream"`
|
|
}
|
|
|
|
type generateResponse struct {
|
|
Response string `json:"response"`
|
|
}
|
|
|
|
// Client implements the LLM driven port with the Ollama generate API.
|
|
type Client struct {
|
|
baseURL string
|
|
model string
|
|
http *http.Client
|
|
}
|
|
|
|
// New constructs an Ollama client with OTel-instrumented transport.
|
|
func New(baseURL, model 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, model: model, http: httpClient}
|
|
}
|
|
|
|
// Generate sends one non-streaming prompt to Ollama.
|
|
func (c *Client) Generate(ctx context.Context, prompt string) (string, error) {
|
|
body, err := json.Marshal(generateRequest{
|
|
Model: c.model,
|
|
Prompt: prompt,
|
|
Stream: false,
|
|
})
|
|
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
|
|
}
|