dealroom/internal/ai/k25.go

211 lines
5.7 KiB
Go

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)
}