Files
boc/backend/handlers/agent.go
T

378 lines
11 KiB
Go
Raw Normal View History

2026-08-12 07:50:12 +00:00
package handlers
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"time"
"github.com/rs/zerolog/log"
)
// AgentChatRequest represents a chat message to an agent
type AgentChatRequest struct {
Rum string `json:"rum"`
SystemPrompt string `json:"systemPrompt"`
Meddelanden []AgentMeddelande `json:"meddelanden"`
}
// AgentMeddelande represents a single message
type AgentMeddelande struct {
Roll string `json:"roll"`
Innehall string `json:"innehall"`
}
// AgentChatResponse represents the agent's response
type AgentChatResponse struct {
Svar string `json:"svar"`
Rum string `json:"rum"`
Timestamp string `json:"timestamp"`
}
// AgentOrchestrator handles agent routing and coordination
type AgentOrchestrator struct {
anthropicKey string
apiEndpoint string
}
// NewAgentOrchestrator creates a new agent orchestrator
func NewAgentOrchestrator() *AgentOrchestrator {
key := os.Getenv("ANTHROPIC_API_KEY")
if key == "" {
log.Warn().Msg("ANTHROPIC_API_KEY not set, agent will use mock responses")
}
return &AgentOrchestrator{
anthropicKey: key,
apiEndpoint: "https://api.anthropic.com/v1/messages",
}
}
// HandleAgentChat handles chat requests to agents
func (o *AgentOrchestrator) HandleAgentChat(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, `{"error":"method not allowed"}`, http.StatusMethodNotAllowed)
return
}
var req AgentChatRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, `{"error":"invalid request"}`, http.StatusBadRequest)
return
}
// Validate request
if req.Rum == "" || req.SystemPrompt == "" {
http.Error(w, `{"error":"rum and systemPrompt required"}`, http.StatusBadRequest)
return
}
// If no Anthropic key, return mock response
if o.anthropicKey == "" {
mockSvar := generateMockResponse(req.Rum, req.Meddelanden)
respondWithJSON(w, AgentChatResponse{
Svar: mockSvar,
Rum: req.Rum,
Timestamp: time.Now().Format(time.RFC3339),
})
return
}
// Call Anthropic API
svar, err := o.callAnthropic(req.SystemPrompt, req.Meddelanden)
if err != nil {
log.Error().Err(err).Str("rum", req.Rum).Msg("Agent chat failed")
// Fallback to mock
mockSvar := generateMockResponse(req.Rum, req.Meddelanden)
respondWithJSON(w, AgentChatResponse{
Svar: mockSvar,
Rum: req.Rum,
Timestamp: time.Now().Format(time.RFC3339),
})
return
}
respondWithJSON(w, AgentChatResponse{
Svar: svar,
Rum: req.Rum,
Timestamp: time.Now().Format(time.RFC3339),
})
}
// callAnthropic calls the Anthropic Claude API
func (o *AgentOrchestrator) callAnthropic(systemPrompt string, meddelanden []AgentMeddelande) (string, error) {
// Build messages for Anthropic
messages := make([]map[string]string, 0, len(meddelanden))
for _, m := range meddelanden {
role := m.Roll
if role == "assistant" {
role = "assistant"
} else {
role = "user"
}
messages = append(messages, map[string]string{
"role": role,
"content": m.Innehall,
})
}
payload := map[string]interface{}{
"model": "claude-3-sonnet-20240229",
"max_tokens": 1024,
"system": systemPrompt,
"messages": messages,
}
jsonPayload, err := json.Marshal(payload)
if err != nil {
return "", err
}
req, err := http.NewRequest("POST", o.apiEndpoint, bytes.NewBuffer(jsonPayload))
if err != nil {
return "", err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-API-Key", o.anthropicKey)
req.Header.Set("Anthropic-Version", "2023-06-01")
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return "", fmt.Errorf("anthropic API error: %d - %s", resp.StatusCode, string(body))
}
var result struct {
Content []struct {
Text string `json:"text"`
} `json:"content"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return "", err
}
if len(result.Content) > 0 {
return result.Content[0].Text, nil
}
return "", fmt.Errorf("no content in response")
}
// generateMockResponse generates a contextual mock response
func generateMockResponse(rum string, meddelanden []AgentMeddelande) string {
// Get last user message
var lastMessage string
for i := len(meddelanden) - 1; i >= 0; i-- {
if meddelanden[i].Roll == "user" {
lastMessage = meddelanden[i].Innehall
break
}
}
inputLower := ""
if lastMessage != "" {
inputLower = lastMessage
}
_ = inputLower
switch rum {
case "finance":
return `Jag kan hjälpa dig med finansiell analys, MOMS-rapportering, fakturahantering och kassaflödesprognoser.
Just nu har systemet tillgång till:
• Balansräkning i realtid
• Resultaträkning per period
• MOMS-rapport (månadsvis/kvartalsvis)
• Fakturor och betalningsstatus
• Kassaflödesanalys
Vad vill du veta mer om?`
case "sales":
return `Jag kan hjälpa dig med försäljningsanalys, lead-hantering och pipeline-översikt.
Aktuell status:
• 12 aktiva leads i pipelinen
• 3 deals i förhandlingsfas
• MRR: 847 500 kr
• ARR: 10 170 000 kr
Vill du se detaljerad pipeline eller analysera specifika deals?`
case "hr":
return `Jag kan hjälpa dig med HR-frågor, personaldata och arbetsflöden.
Systemet har tillgång till:
• Anställda och organisation
• Semester och frånvaro
• Tidrapporter
• Kompetenser och utbildning
• Prestanda och utveckling
Vad behöver du hjälp med?`
case "crm":
return `Jag kan hjälpa dig med kundanalys, kundresor och supportärenden.
Aktuell översikt:
• 156 aktiva kunder
• 23 leads att följa upp
• 5 supportärenden öppna
• NPS: 72 (utmärkt)
Vill du djupdyka i något specifikt?`
case "legal":
return `Jag kan hjälpa dig med avtalsgranskning, GDPR-frågor och compliance.
**Viktigt:** Jag ersätter inte en jurist. Vid komplexa juridiska frågor, hänvisa alltid till jurist.
Systemet har tillgång till:
• Avtal och mallar
• Produktlänkar
• Regelverk och policyer
Vad vill du granska?`
case "marketing":
return `Jag kan hjälpa dig med kampanjanalys, content-planering och marknadsstrategi.
Aktuella kampanjer:
• Q3 Product Launch (pågående)
• Summer Retention Campaign (avslutad)
• Enterprise Outreach (planerad)
Vill du analysera resultat eller planera nya kampanjer?`
case "accounting":
return `Jag kan hjälpa dig med bokföring, transaktioner och avstämning.
Systemet har tillgång till:
• Totaljournal och verifikationer
• Kontoplan och saldon
• Periodisering och bokslut
• Reconciliation-rapporter
Vad behöver du hjälp med?`
case "compliance":
return `Jag kan hjälpa dig med compliance-kontroller, policyer och audit-förberedelser.
Systemet övervakar:
• Kontrollstatus per område
• Avvikelser och risker
• Regulatoriska deadlines
• Bevis och dokumentation
Vill du se aktuell status eller granska specifika kontroller?`
case "support":
return `Jag kan hjälpa dig med supportärenden, triage och eskalering.
Aktuell kö:
• 5 öppna ärenden
• 2 väntar på svar
• 1 eskalerat till L2
• Genomsnittlig svarstid: 4.2h
Vill du se ärendelista eller analysera trender?`
case "projects":
return `Jag kan hjälpa dig med projektöversikt, milstolpar och resurser.
Aktiva projekt:
• BOC v2.1 (pågående, 78% klart)
• quiXzoom Integration (planerad)
• AMOS Vision Launch (pågående)
Vill du se detaljer eller analysera risker?`
case "automation":
return `Jag kan hjälpa dig med automationsflöden, triggers och integrationer.
Systemet hanterar:
• 12 aktiva workflows
• 5 schemalagda jobb
• 3 integrationer (Slack, Email, SMS)
Vill du skapa ny automation eller övervaka befintliga?`
case "social":
return `Jag kan hjälpa dig med sociala kanaler, content-kalender och engagement.
Aktuell status:
• LinkedIn: 3 inlägg denna vecka
• Twitter: 5 tweets, 2.3k impressions
• YouTube: 1 video publicerad
Vill du planera content eller analysera performance?`
default:
return `Jag är AMOS Assistant. Jag kan hjälpa dig med frågor om hela plattformen.
Tillgängliga områden:
• CRM, Sales, Marketing
• Finance, Accounting
• HR, Legal, Compliance
• Support, Projects, Automation
• Social Media
Vad vill du veta mer om?`
}
}
// respondWithJSON sends a JSON response
func respondWithJSON(w http.ResponseWriter, data interface{}) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(data)
}
// AgentStatus represents the status of an agent
type AgentStatus struct {
Rum string `json:"rum"`
Titel string `json:"titel"`
Status string `json:"status"` // operational, degraded, error, disabled
LastPing string `json:"lastPing"`
Capabilities []string `json:"capabilities"`
}
// HandleAgentStatus returns the status of all agents
func (o *AgentOrchestrator) HandleAgentStatus(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, `{"error":"method not allowed"}`, http.StatusMethodNotAllowed)
return
}
agents := []AgentStatus{
{Rum: "crm", Titel: "CRM AI", Status: "operational", LastPing: time.Now().Format(time.RFC3339), Capabilities: []string{"read", "analyze", "propose"}},
{Rum: "sales", Titel: "Sales AI", Status: "operational", LastPing: time.Now().Format(time.RFC3339), Capabilities: []string{"read", "analyze", "propose", "execute"}},
{Rum: "marketing", Titel: "Marketing AI", Status: "operational", LastPing: time.Now().Format(time.RFC3339), Capabilities: []string{"read", "analyze", "propose"}},
{Rum: "finance", Titel: "Finance AI", Status: "operational", LastPing: time.Now().Format(time.RFC3339), Capabilities: []string{"read", "analyze", "propose", "execute"}},
{Rum: "accounting", Titel: "Accounting AI", Status: "operational", LastPing: time.Now().Format(time.RFC3339), Capabilities: []string{"read", "analyze", "propose"}},
{Rum: "hr", Titel: "HR AI", Status: "operational", LastPing: time.Now().Format(time.RFC3339), Capabilities: []string{"read", "analyze", "propose"}},
{Rum: "legal", Titel: "Legal AI", Status: "operational", LastPing: time.Now().Format(time.RFC3339), Capabilities: []string{"read", "analyze", "propose", "escalate"}},
{Rum: "compliance", Titel: "Compliance AI", Status: "operational", LastPing: time.Now().Format(time.RFC3339), Capabilities: []string{"read", "analyze", "propose", "escalate"}},
{Rum: "support", Titel: "Support AI", Status: "operational", LastPing: time.Now().Format(time.RFC3339), Capabilities: []string{"read", "analyze", "propose", "execute"}},
{Rum: "projects", Titel: "Projects AI", Status: "operational", LastPing: time.Now().Format(time.RFC3339), Capabilities: []string{"read", "analyze", "propose"}},
{Rum: "automation", Titel: "Automation AI", Status: "operational", LastPing: time.Now().Format(time.RFC3339), Capabilities: []string{"read", "analyze", "propose", "execute"}},
{Rum: "social", Titel: "Social AI", Status: "operational", LastPing: time.Now().Format(time.RFC3339), Capabilities: []string{"read", "analyze", "propose"}},
}
respondWithJSON(w, map[string]interface{}{
"agents": agents,
"timestamp": time.Now().Format(time.RFC3339),
"orchestrator": "operational",
})
}