Nik Afiq 520f5d1ffb
Some checks failed
CI / build-ai-gateway (push) Has been cancelled
CI / build-ha-gateway (push) Has been cancelled
CI / build-discord-bot (push) Has been cancelled
CI / test (push) Has been cancelled
feat: add ai-gateway microservice with gRPC API for AI logic
- 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.
2026-04-21 21:52:28 +09:00

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
}