146 lines
4.8 KiB
Go
146 lines
4.8 KiB
Go
package synthesize
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type LLMClient struct {
|
|
apiKey string
|
|
model string
|
|
baseURL string
|
|
client *http.Client
|
|
}
|
|
|
|
type Message struct {
|
|
Role string `json:"role"`
|
|
Content string `json:"content"`
|
|
}
|
|
|
|
type LLMRequest struct {
|
|
Model string `json:"model"`
|
|
Messages []Message `json:"messages"`
|
|
Temperature float32 `json:"temperature,omitempty"`
|
|
MaxTokens int `json:"max_tokens,omitempty"`
|
|
}
|
|
|
|
type LLMResponse struct {
|
|
Choices []struct {
|
|
Message Message `json:"message"`
|
|
} `json:"choices"`
|
|
Error *struct {
|
|
Message string `json:"message"`
|
|
Code int `json:"code"`
|
|
} `json:"error,omitempty"`
|
|
}
|
|
|
|
func NewLLMClient(baseURL, apiKey, model string) *LLMClient {
|
|
return &LLMClient{
|
|
apiKey: apiKey,
|
|
model: model,
|
|
baseURL: baseURL,
|
|
client: &http.Client{Timeout: 120 * time.Second},
|
|
}
|
|
}
|
|
|
|
func (c *LLMClient) Chat(ctx context.Context, messages []Message) (string, error) {
|
|
reqBody := LLMRequest{
|
|
Model: c.model,
|
|
Messages: messages,
|
|
Temperature: 0.7,
|
|
MaxTokens: 3000,
|
|
}
|
|
payload, err := json.Marshal(reqBody)
|
|
if err != nil {
|
|
return "", fmt.Errorf("marshal llm request: %w", err)
|
|
}
|
|
|
|
req, err := http.NewRequestWithContext(ctx, "POST", c.baseURL+"/v1/chat/completions", bytes.NewReader(payload))
|
|
if err != nil {
|
|
return "", fmt.Errorf("llm request: %w", err)
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("Authorization", "Bearer "+c.apiKey)
|
|
|
|
resp, err := c.client.Do(req)
|
|
if err != nil {
|
|
return "", fmt.Errorf("llm call: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
respBody, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return "", fmt.Errorf("read llm response: %w", err)
|
|
}
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return "", fmt.Errorf("llm returned %d: %s", resp.StatusCode, string(respBody))
|
|
}
|
|
|
|
var lr LLMResponse
|
|
if err := json.Unmarshal(respBody, &lr); err != nil {
|
|
return "", fmt.Errorf("parse llm response: %w", err)
|
|
}
|
|
if lr.Error != nil {
|
|
return "", fmt.Errorf("llm error %d: %s", lr.Error.Code, lr.Error.Message)
|
|
}
|
|
if len(lr.Choices) == 0 {
|
|
return "", fmt.Errorf("empty llm response: %s", string(respBody))
|
|
}
|
|
return lr.Choices[0].Message.Content, nil
|
|
}
|
|
|
|
// ---- Промпты ----
|
|
|
|
func BuildFastResearchPrompt(query string, sources []string) []Message {
|
|
srcText := strings.Join(sources, "\n\n---\n\n")
|
|
return []Message{
|
|
{
|
|
Role: "system",
|
|
Content: "Ты — DeepRes, AI-исследователь. Твоя задача: на основе предоставленных источников написать структурированную выжимку по теме. Выдели ключевые факты, противоположные точки зрения, практические выводы. Если источники противоречат друг другу — укажи это. Пиши на русском, если тема на русском. Формат: кратко, ёмко, по делу.",
|
|
},
|
|
{
|
|
Role: "user",
|
|
Content: fmt.Sprintf("Тема: %s\n\nИсточники:\n%s\n\nНапиши выжимку на 3-5 абзацев с ключевыми выводами и ссылками на источники.", query, srcText),
|
|
},
|
|
}
|
|
}
|
|
|
|
func BuildDeepResearchPrompt(query string, sources []string, followUpQ string) []Message {
|
|
srcText := strings.Join(sources, "\n\n---\n\n")
|
|
messages := []Message{
|
|
{
|
|
Role: "system",
|
|
Content: "Ты — DeepRes, AI-исследователь. Напиши ПОЛНЫЙ отчёт по теме на основе источников. Структура: введение, ключевые находки, анализ, противоположные точки зрения, выводы и рекомендации. Отметь области, по которым недостаточно информации.",
|
|
},
|
|
{
|
|
Role: "user",
|
|
Content: fmt.Sprintf("Тема: %s\n\nИсточники:\n%s", query, srcText),
|
|
},
|
|
}
|
|
if followUpQ != "" {
|
|
messages = append(messages, Message{Role: "user", Content: followUpQ})
|
|
}
|
|
return messages
|
|
}
|
|
|
|
func BuildGapPrompt(query string, sources []string) []Message {
|
|
srcText := strings.Join(sources, "\n\n---\n\n")
|
|
return []Message{
|
|
{
|
|
Role: "system",
|
|
Content: "Ты — аналитик, который ищет неизведанные области. Проанализируй предоставленные источники и найди:\n1) Какие аспекты темы НЕ освещены?\n2) Где источники противоречат друг другу?\n3) Какие вопросы остаются открытыми?\n4) Что было бы важно изучить дальше?",
|
|
},
|
|
{
|
|
Role: "user",
|
|
Content: fmt.Sprintf("Тема: %s\n\nИсточники:\n%s", query, srcText),
|
|
},
|
|
}
|
|
}
|