Initial commit: DeepRes Go backend — bot + worker + embed service
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
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),
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user