package ai import ( "bytes" "encoding/json" "fmt" "io" "net/http" "time" "dealroom/internal/model" ) // K25Client handles communication with K2.5 AI service type K25Client struct { baseURL string apiKey string client *http.Client } // NewK25Client creates a new K2.5 client func NewK25Client(baseURL, apiKey string) *K25Client { return &K25Client{ baseURL: baseURL, apiKey: apiKey, client: &http.Client{ Timeout: 30 * time.Second, }, } } // AnalysisRequest represents a request for document analysis type AnalysisRequest struct { Text string `json:"text"` Category string `json:"category,omitempty"` // nda, cim, financial_model, etc. Language string `json:"language,omitempty"` } // AnalysisResponse represents the response from K2.5 analysis type AnalysisResponse struct { Summary string `json:"summary"` KeyMetrics []string `json:"key_metrics"` RiskFactors []string `json:"risk_factors"` Category string `json:"category"` Confidence float64 `json:"confidence"` ProcessingMs int64 `json:"processing_ms"` } // EmbeddingRequest represents a request for text embeddings type EmbeddingRequest struct { Text string `json:"text"` Model string `json:"model,omitempty"` } // EmbeddingResponse represents the response from K2.5 embedding type EmbeddingResponse struct { Embedding []float64 `json:"embedding"` Dimension int `json:"dimension"` Model string `json:"model"` } // AnalyzeDocument sends text to K2.5 for analysis func (c *K25Client) AnalyzeDocument(text, category string) (*model.DocumentAnalysis, error) { if c.baseURL == "" || c.apiKey == "" { return nil, fmt.Errorf("K2.5 client not configured") } req := AnalysisRequest{ Text: text, Category: category, Language: "en", } jsonData, err := json.Marshal(req) if err != nil { return nil, fmt.Errorf("failed to marshal request: %w", err) } httpReq, err := http.NewRequest("POST", c.baseURL+"/analyze", bytes.NewBuffer(jsonData)) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } httpReq.Header.Set("Content-Type", "application/json") httpReq.Header.Set("Authorization", "Bearer "+c.apiKey) resp, err := c.client.Do(httpReq) if err != nil { return nil, fmt.Errorf("failed to send request: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("K2.5 API error %d: %s", resp.StatusCode, string(body)) } var analysisResp AnalysisResponse if err := json.NewDecoder(resp.Body).Decode(&analysisResp); err != nil { return nil, fmt.Errorf("failed to decode response: %w", err) } return &model.DocumentAnalysis{ Summary: analysisResp.Summary, KeyMetrics: analysisResp.KeyMetrics, RiskFactors: analysisResp.RiskFactors, AIConfidence: analysisResp.Confidence, }, nil } // GenerateEmbedding creates vector embeddings for text func (c *K25Client) GenerateEmbedding(text string) ([]float64, error) { if c.baseURL == "" || c.apiKey == "" { return nil, fmt.Errorf("K2.5 client not configured") } req := EmbeddingRequest{ Text: text, Model: "default", } jsonData, err := json.Marshal(req) if err != nil { return nil, fmt.Errorf("failed to marshal request: %w", err) } httpReq, err := http.NewRequest("POST", c.baseURL+"/embed", bytes.NewBuffer(jsonData)) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } httpReq.Header.Set("Content-Type", "application/json") httpReq.Header.Set("Authorization", "Bearer "+c.apiKey) resp, err := c.client.Do(httpReq) if err != nil { return nil, fmt.Errorf("failed to send request: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("K2.5 API error %d: %s", resp.StatusCode, string(body)) } var embeddingResp EmbeddingResponse if err := json.NewDecoder(resp.Body).Decode(&embeddingResp); err != nil { return nil, fmt.Errorf("failed to decode response: %w", err) } return embeddingResp.Embedding, nil } // SearchSimilar finds documents similar to the given text func (c *K25Client) SearchSimilar(text string, limit int) ([]SimilarDocument, error) { // This would typically involve: // 1. Generate embedding for the search text // 2. Query the database for similar embeddings using cosine similarity // 3. Return ranked results // For now, return a placeholder implementation return []SimilarDocument{}, fmt.Errorf("not implemented") } // SimilarDocument represents a document similar to the search query type SimilarDocument struct { EntryID string `json:"entry_id"` Title string `json:"title"` Similarity float64 `json:"similarity"` Snippet string `json:"snippet"` } // Health checks if the K2.5 service is available func (c *K25Client) Health() error { if c.baseURL == "" { return fmt.Errorf("K2.5 client not configured") } req, err := http.NewRequest("GET", c.baseURL+"/health", nil) if err != nil { return fmt.Errorf("failed to create health check request: %w", err) } if c.apiKey != "" { req.Header.Set("Authorization", "Bearer "+c.apiKey) } resp, err := c.client.Do(req) if err != nil { return fmt.Errorf("health check failed: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("K2.5 service unhealthy: status %d", resp.StatusCode) } return nil } // ExtractTextFromFile extracts text content from various file types func ExtractTextFromFile(filePath, mimeType string) (string, error) { // This would implement text extraction from: // - PDF files // - Microsoft Office documents (DOCX, XLSX, PPTX) // - Plain text files // - Images with OCR // For now, return a placeholder return "", fmt.Errorf("text extraction not implemented for %s", mimeType) }