mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:21:09 +02:00
Replace 21 separate NestJS Matrix bot processes (~2.1 GB RAM, ~4.2 GB Docker images) with a single Go binary using plugin architecture (8.6 MB binary, ~30 MB RAM). New services: - services/mana-matrix-bot/ — Go Matrix bot with 21 plugins (mautrix-go, Redis sessions) - services/mana-api-gateway-go/ — Go API gateway (rate limiting, API keys, credit billing) Deleted: - 21 services/matrix-*-bot/ directories - packages/bot-services/ and packages/matrix-bot-common/ - Legacy deploy scripts and CI build jobs Updated: - docker-compose.macmini.yml: new Go services, legacy bots removed - CI/CD: change detection + build jobs for Go services - Root package.json: new dev:matrix, build:matrix, test:matrix scripts Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
120 lines
3.1 KiB
Go
120 lines
3.1 KiB
Go
package gateway
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"time"
|
|
)
|
|
|
|
// OllamaModel represents an available model.
|
|
type OllamaModel struct {
|
|
Name string `json:"name"`
|
|
Size int64 `json:"size"`
|
|
}
|
|
|
|
type ollamaModelsResponse struct {
|
|
Models []OllamaModel `json:"models"`
|
|
}
|
|
|
|
type ollamaChatRequest struct {
|
|
Model string `json:"model"`
|
|
Messages []ChatMessage `json:"messages"`
|
|
Stream bool `json:"stream"`
|
|
}
|
|
|
|
type ollamaChatResponse struct {
|
|
Message ChatMessage `json:"message"`
|
|
}
|
|
|
|
var httpClient = &http.Client{Timeout: 120 * time.Second}
|
|
|
|
// ollamaChat sends a chat request to the Ollama API.
|
|
func ollamaChat(ctx context.Context, baseURL, model string, messages []ChatMessage) (string, error) {
|
|
body := ollamaChatRequest{Model: model, Messages: messages, Stream: false}
|
|
data, err := json.Marshal(body)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
req, err := http.NewRequestWithContext(ctx, "POST", baseURL+"/api/chat", bytes.NewReader(data))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := httpClient.Do(req)
|
|
if err != nil {
|
|
return "", fmt.Errorf("ollama: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
b, _ := io.ReadAll(resp.Body)
|
|
return "", fmt.Errorf("ollama %d: %s", resp.StatusCode, string(b))
|
|
}
|
|
|
|
var result ollamaChatResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
|
return "", err
|
|
}
|
|
return result.Message.Content, nil
|
|
}
|
|
|
|
// ollamaListModels lists available models from the Ollama API.
|
|
func ollamaListModels(ctx context.Context, baseURL string) ([]OllamaModel, error) {
|
|
req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/api/tags", nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resp, err := httpClient.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("list models: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("list models: %d", resp.StatusCode)
|
|
}
|
|
|
|
var result ollamaModelsResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
|
return nil, err
|
|
}
|
|
return result.Models, nil
|
|
}
|
|
|
|
// buildMessages constructs the message array for an Ollama chat request.
|
|
func buildMessages(session *AISession, userMessage string) []ChatMessage {
|
|
msgs := make([]ChatMessage, 0, len(session.History)+2)
|
|
|
|
// System prompt
|
|
prompt := getSystemPrompt(session.Mode)
|
|
if prompt != "" {
|
|
msgs = append(msgs, ChatMessage{Role: "system", Content: prompt})
|
|
}
|
|
|
|
// History
|
|
msgs = append(msgs, session.History...)
|
|
|
|
// New user message
|
|
msgs = append(msgs, ChatMessage{Role: "user", Content: userMessage})
|
|
return msgs
|
|
}
|
|
|
|
func getSystemPrompt(mode string) string {
|
|
switch mode {
|
|
case "code":
|
|
return "Du bist ein erfahrener Programmierer. Antworte mit klaren Code-Beispielen."
|
|
case "translate":
|
|
return "Du bist ein Übersetzer. Übersetze Deutsch↔Englisch. Gib nur die Übersetzung zurück."
|
|
case "summarize":
|
|
return "Fasse den Text kurz und prägnant zusammen."
|
|
default:
|
|
return "Du bist ein hilfreicher KI-Assistent. Antworte auf Deutsch, außer der Nutzer schreibt auf Englisch."
|
|
}
|
|
}
|