1
0

Реализована операции Milvus для управления документами и встраиванием, включая функции вставки, запроса и удаления. Внедрите архитектуру RAG с LLM и сервисами встраивания. Добавьте обработку текста для фрагментации и конкатенации. Создайте автономный скрипт для настройки и управления Milvus. Разработайте комплексные тесты API для обработки документов и взаимодействия с LLM, включая имитации для сервисов. Расширьте возможности конфигурации пользователя с помощью дополнительных настроек YAML.

This commit is contained in:
Dmitriy Fofanov
2025-09-19 11:38:31 +03:00
parent 8e7aab5181
commit 636096fd34
38 changed files with 3420 additions and 28 deletions

8
internal/llm/llm.go Normal file
View File

@@ -0,0 +1,8 @@
package llm
// implement llm interface
type LLMService interface {
// generate text from prompt
Generate(prompt string) (string, error)
GetModel() string
}

View File

@@ -0,0 +1,82 @@
package llm
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
type Ollama struct {
Endpoint string
Model string
}
func NewOllama(endpoint string, model string) *Ollama {
return &Ollama{
Endpoint: endpoint,
Model: model,
}
}
// Response represents the structure of the expected response from the API.
type Response struct {
Model string `json:"model"`
CreatedAt string `json:"created_at"`
Message struct {
Role string `json:"role"`
Content string `json:"content"`
} `json:"message"`
}
// Generate sends a prompt to the Ollama endpoint and returns the response
func (o *Ollama) Generate(prompt string) (string, error) {
// Create the request payload
payload := map[string]interface{}{
"model": o.Model,
"messages": []map[string]string{
{
"role": "user",
"content": prompt,
},
},
"stream": false,
}
// Marshal the payload into JSON
data, err := json.Marshal(payload)
if err != nil {
return "", fmt.Errorf("failed to marshal payload: %w", err)
}
// Make the POST request
resp, err := http.Post(o.Endpoint, "application/json", bytes.NewBuffer(data))
if err != nil {
return "", fmt.Errorf("failed to make request: %w", err)
}
defer resp.Body.Close()
// Read and parse the response
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("failed to read response: %w", err)
}
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("API returned error: %s", string(body))
}
// Unmarshal the response into a predefined structure
var response Response
if err := json.Unmarshal(body, &response); err != nil {
return "", fmt.Errorf("failed to unmarshal response: %w", err)
}
// Extract and return the content from the nested structure
return response.Message.Content, nil
}
func (o *Ollama) GetModel() string {
return o.Model
}

View File

@@ -0,0 +1,24 @@
package llm
type OpenAI struct {
APIKey string
Endpoint string
Model string
}
func NewOpenAI(apiKey string, endpoint string, model string) *OpenAI {
return &OpenAI{
APIKey: apiKey,
Endpoint: endpoint,
Model: model,
}
}
func (o *OpenAI) Generate(prompt string) (string, error) {
return "", nil
// TODO: implement
}
func (o *OpenAI) GetModel() string {
return o.Model
}

View File

@@ -0,0 +1,155 @@
package openroute
// Request represents the main request structure.
type Request struct {
Messages []MessageRequest `json:"messages,omitempty"`
Prompt string `json:"prompt,omitempty"`
Model string `json:"model,omitempty"`
ResponseFormat *ResponseFormat `json:"response_format,omitempty"`
Stop []string `json:"stop,omitempty"`
Stream bool `json:"stream,omitempty"`
MaxTokens int `json:"max_tokens,omitempty"`
Temperature float64 `json:"temperature,omitempty"`
Tools []Tool `json:"tools,omitempty"`
ToolChoice ToolChoice `json:"tool_choice,omitempty"`
Seed int `json:"seed,omitempty"`
TopP float64 `json:"top_p,omitempty"`
TopK int `json:"top_k,omitempty"`
FrequencyPenalty float64 `json:"frequency_penalty,omitempty"`
PresencePenalty float64 `json:"presence_penalty,omitempty"`
RepetitionPenalty float64 `json:"repetition_penalty,omitempty"`
LogitBias map[int]float64 `json:"logit_bias,omitempty"`
TopLogprobs int `json:"top_logprobs,omitempty"`
MinP float64 `json:"min_p,omitempty"`
TopA float64 `json:"top_a,omitempty"`
Prediction *Prediction `json:"prediction,omitempty"`
Transforms []string `json:"transforms,omitempty"`
Models []string `json:"models,omitempty"`
Route string `json:"route,omitempty"`
Provider *ProviderPreferences `json:"provider,omitempty"`
IncludeReasoning bool `json:"include_reasoning,omitempty"`
}
// ResponseFormat represents the response format structure.
type ResponseFormat struct {
Type string `json:"type"`
}
// Prediction represents the prediction structure.
type Prediction struct {
Type string `json:"type"`
Content string `json:"content"`
}
// ProviderPreferences represents the provider preferences structure.
type ProviderPreferences struct {
RefererURL string `json:"referer_url,omitempty"`
SiteName string `json:"site_name,omitempty"`
}
// Message represents the message structure.
type MessageRequest struct {
Role MessageRole `json:"role"`
Content interface{} `json:"content"` // Can be string or []ContentPart
Name string `json:"name,omitempty"`
ToolCallID string `json:"tool_call_id,omitempty"`
}
type MessageRole string
const (
RoleSystem MessageRole = "system"
RoleUser MessageRole = "user"
RoleAssistant MessageRole = "assistant"
)
// ContentPart represents the content part structure.
type ContentPart struct {
Type ContnetType `json:"type"`
Text string `json:"text,omitempty"`
ImageURL *ImageURL `json:"image_url,omitempty"`
}
type ContnetType string
const (
ContentTypeText ContnetType = "text"
ContentTypeImage ContnetType = "image_url"
)
// ImageURL represents the image URL structure.
type ImageURL struct {
URL string `json:"url"`
Detail string `json:"detail,omitempty"`
}
// FunctionDescription represents the function description structure.
type FunctionDescription struct {
Description string `json:"description,omitempty"`
Name string `json:"name"`
Parameters interface{} `json:"parameters"` // JSON Schema object
}
// Tool represents the tool structure.
type Tool struct {
Type string `json:"type"`
Function FunctionDescription `json:"function"`
}
// ToolChoice represents the tool choice structure.
type ToolChoice struct {
Type string `json:"type"`
Function struct {
Name string `json:"name"`
} `json:"function"`
}
type Response struct {
ID string `json:"id"`
Choices []Choice `json:"choices"`
Created int64 `json:"created"`
Model string `json:"model"`
Object string `json:"object"`
SystemFingerprint *string `json:"system_fingerprint,omitempty"`
Usage *ResponseUsage `json:"usage,omitempty"`
}
type ResponseUsage struct {
PromptTokens int `json:"prompt_tokens"`
CompletionTokens int `json:"completion_tokens"`
TotalTokens int `json:"total_tokens"`
}
type Choice struct {
FinishReason string `json:"finish_reason"`
Text string `json:"text,omitempty"`
Message *MessageResponse `json:"message,omitempty"`
Delta *Delta `json:"delta,omitempty"`
Error *ErrorResponse `json:"error,omitempty"`
}
type MessageResponse struct {
Content string `json:"content"`
Role string `json:"role"`
Reasoning string `json:"reasoning,omitempty"`
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
}
type Delta struct {
Content string `json:"content"`
Role string `json:"role,omitempty"`
Reasoning string `json:"reasoning,omitempty"`
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
}
type ErrorResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
type ToolCall struct {
ID string `json:"id"`
Type string `json:"type"`
Function interface{} `json:"function"`
}

View File

@@ -0,0 +1,198 @@
package openroute
import "context"
type RouterAgentConfig struct {
ResponseFormat *ResponseFormat `json:"response_format,omitempty"`
Stop []string `json:"stop,omitempty"`
MaxTokens int `json:"max_tokens,omitempty"`
Temperature float64 `json:"temperature,omitempty"`
Tools []Tool `json:"tools,omitempty"`
ToolChoice ToolChoice `json:"tool_choice,omitempty"`
Seed int `json:"seed,omitempty"`
TopP float64 `json:"top_p,omitempty"`
TopK int `json:"top_k,omitempty"`
FrequencyPenalty float64 `json:"frequency_penalty,omitempty"`
PresencePenalty float64 `json:"presence_penalty,omitempty"`
RepetitionPenalty float64 `json:"repetition_penalty,omitempty"`
LogitBias map[int]float64 `json:"logit_bias,omitempty"`
TopLogprobs int `json:"top_logprobs,omitempty"`
MinP float64 `json:"min_p,omitempty"`
TopA float64 `json:"top_a,omitempty"`
}
type RouterAgent struct {
client *OpenRouterClient
model string
config RouterAgentConfig
}
func NewRouterAgent(client *OpenRouterClient, model string, config RouterAgentConfig) *RouterAgent {
return &RouterAgent{
client: client,
model: model,
config: config,
}
}
func (agent RouterAgent) Completion(prompt string) (*Response, error) {
request := Request{
Prompt: prompt,
Model: agent.model,
ResponseFormat: agent.config.ResponseFormat,
Stop: agent.config.Stop,
MaxTokens: agent.config.MaxTokens,
Temperature: agent.config.Temperature,
Tools: agent.config.Tools,
ToolChoice: agent.config.ToolChoice,
Seed: agent.config.Seed,
TopP: agent.config.TopP,
TopK: agent.config.TopK,
FrequencyPenalty: agent.config.FrequencyPenalty,
PresencePenalty: agent.config.PresencePenalty,
RepetitionPenalty: agent.config.RepetitionPenalty,
LogitBias: agent.config.LogitBias,
TopLogprobs: agent.config.TopLogprobs,
MinP: agent.config.MinP,
TopA: agent.config.TopA,
Stream: false,
}
return agent.client.FetchChatCompletions(request)
}
func (agent RouterAgent) CompletionStream(prompt string, outputChan chan Response, processingChan chan interface{}, errChan chan error, ctx context.Context) {
request := Request{
Prompt: prompt,
Model: agent.model,
ResponseFormat: agent.config.ResponseFormat,
Stop: agent.config.Stop,
MaxTokens: agent.config.MaxTokens,
Temperature: agent.config.Temperature,
Tools: agent.config.Tools,
ToolChoice: agent.config.ToolChoice,
Seed: agent.config.Seed,
TopP: agent.config.TopP,
TopK: agent.config.TopK,
FrequencyPenalty: agent.config.FrequencyPenalty,
PresencePenalty: agent.config.PresencePenalty,
RepetitionPenalty: agent.config.RepetitionPenalty,
LogitBias: agent.config.LogitBias,
TopLogprobs: agent.config.TopLogprobs,
MinP: agent.config.MinP,
TopA: agent.config.TopA,
Stream: true,
}
agent.client.FetchChatCompletionsStream(request, outputChan, processingChan, errChan, ctx)
}
func (agent RouterAgent) Chat(messages []MessageRequest) (*Response, error) {
request := Request{
Messages: messages,
Model: agent.model,
ResponseFormat: agent.config.ResponseFormat,
Stop: agent.config.Stop,
MaxTokens: agent.config.MaxTokens,
Temperature: agent.config.Temperature,
Tools: agent.config.Tools,
ToolChoice: agent.config.ToolChoice,
Seed: agent.config.Seed,
TopP: agent.config.TopP,
TopK: agent.config.TopK,
FrequencyPenalty: agent.config.FrequencyPenalty,
PresencePenalty: agent.config.PresencePenalty,
RepetitionPenalty: agent.config.RepetitionPenalty,
LogitBias: agent.config.LogitBias,
TopLogprobs: agent.config.TopLogprobs,
MinP: agent.config.MinP,
TopA: agent.config.TopA,
Stream: false,
}
return agent.client.FetchChatCompletions(request)
}
func (agent RouterAgent) ChatStream(messages []MessageRequest, outputChan chan Response, processingChan chan interface{}, errChan chan error, ctx context.Context) {
request := Request{
Messages: messages,
Model: agent.model,
ResponseFormat: agent.config.ResponseFormat,
Stop: agent.config.Stop,
MaxTokens: agent.config.MaxTokens,
Temperature: agent.config.Temperature,
Tools: agent.config.Tools,
ToolChoice: agent.config.ToolChoice,
Seed: agent.config.Seed,
TopP: agent.config.TopP,
TopK: agent.config.TopK,
FrequencyPenalty: agent.config.FrequencyPenalty,
PresencePenalty: agent.config.PresencePenalty,
RepetitionPenalty: agent.config.RepetitionPenalty,
LogitBias: agent.config.LogitBias,
TopLogprobs: agent.config.TopLogprobs,
MinP: agent.config.MinP,
TopA: agent.config.TopA,
Stream: true,
}
agent.client.FetchChatCompletionsStream(request, outputChan, processingChan, errChan, ctx)
}
type RouterAgentChat struct {
RouterAgent
Messages []MessageRequest
}
func NewRouterAgentChat(client *OpenRouterClient, model string, config RouterAgentConfig, system_prompt string) RouterAgentChat {
return RouterAgentChat{
RouterAgent: RouterAgent{
client: client,
model: model,
config: config,
},
Messages: []MessageRequest{
{
Role: RoleSystem,
Content: system_prompt,
},
},
}
}
func (agent *RouterAgentChat) Chat(message string) error {
agent.Messages = append(agent.Messages, MessageRequest{
Role: RoleUser,
Content: message,
})
request := Request{
Messages: agent.Messages,
Model: agent.model,
ResponseFormat: agent.config.ResponseFormat,
Stop: agent.config.Stop,
MaxTokens: agent.config.MaxTokens,
Temperature: agent.config.Temperature,
Tools: agent.config.Tools,
ToolChoice: agent.config.ToolChoice,
Seed: agent.config.Seed,
TopP: agent.config.TopP,
TopK: agent.config.TopK,
FrequencyPenalty: agent.config.FrequencyPenalty,
PresencePenalty: agent.config.PresencePenalty,
RepetitionPenalty: agent.config.RepetitionPenalty,
LogitBias: agent.config.LogitBias,
TopLogprobs: agent.config.TopLogprobs,
MinP: agent.config.MinP,
TopA: agent.config.TopA,
Stream: false,
}
response, err := agent.client.FetchChatCompletions(request)
agent.Messages = append(agent.Messages, MessageRequest{
Role: RoleAssistant,
Content: response.Choices[0].Message.Content,
})
return err
}

View File

@@ -0,0 +1,188 @@
package openroute
import (
"bufio"
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
)
type OpenRouterClient struct {
apiKey string
apiURL string
httpClient *http.Client
}
func NewOpenRouterClient(apiKey string) *OpenRouterClient {
return &OpenRouterClient{
apiKey: apiKey,
apiURL: "https://openrouter.ai/api/v1",
httpClient: &http.Client{},
}
}
func NewOpenRouterClientFull(apiKey string, apiUrl string, client *http.Client) *OpenRouterClient {
return &OpenRouterClient{
apiKey: apiKey,
apiURL: apiUrl,
httpClient: client,
}
}
func (c *OpenRouterClient) FetchChatCompletions(request Request) (*Response, error) {
headers := map[string]string{
"Authorization": "Bearer " + c.apiKey,
"Content-Type": "application/json",
}
if request.Provider != nil {
headers["HTTP-Referer"] = request.Provider.RefererURL
headers["X-Title"] = request.Provider.SiteName
}
body, err := json.Marshal(request)
if err != nil {
return nil, err
}
req, err := http.NewRequest("POST", fmt.Sprintf("%s/chat/completions", c.apiURL), bytes.NewBuffer(body))
if err != nil {
return nil, err
}
for key, value := range headers {
req.Header.Set(key, value)
}
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
output, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
outputReponse := &Response{}
err = json.Unmarshal(output, outputReponse)
if err != nil {
return nil, err
}
return outputReponse, nil
}
func (c *OpenRouterClient) FetchChatCompletionsStream(request Request, outputChan chan Response, processingChan chan interface{}, errChan chan error, ctx context.Context) {
headers := map[string]string{
"Authorization": "Bearer " + c.apiKey,
"Content-Type": "application/json",
}
if request.Provider != nil {
headers["HTTP-Referer"] = request.Provider.RefererURL
headers["X-Title"] = request.Provider.SiteName
}
body, err := json.Marshal(request)
if err != nil {
errChan <- err
close(errChan)
close(outputChan)
close(processingChan)
return
}
req, err := http.NewRequest("POST", fmt.Sprintf("%s/chat/completions", c.apiURL), bytes.NewBuffer(body))
if err != nil {
errChan <- err
close(errChan)
close(outputChan)
close(processingChan)
return
}
for key, value := range headers {
req.Header.Set(key, value)
}
resp, err := c.httpClient.Do(req)
if err != nil {
errChan <- err
close(errChan)
close(outputChan)
close(processingChan)
return
}
go func() {
defer resp.Body.Close()
defer close(errChan)
defer close(outputChan)
defer close(processingChan)
if resp.StatusCode != http.StatusOK {
errChan <- fmt.Errorf("unexpected status code: %d", resp.StatusCode)
return
}
reader := bufio.NewReader(resp.Body)
for {
select {
case <-ctx.Done():
errChan <- ctx.Err()
close(errChan)
close(outputChan)
close(processingChan)
return
default:
line, err := reader.ReadString('\n')
line = strings.TrimSpace(line)
if strings.HasPrefix(line, ":") {
select {
case processingChan <- true:
case <-ctx.Done():
errChan <- ctx.Err()
return
}
continue
}
if line != "" {
if strings.Compare(line[6:], "[DONE]") == 0 {
return
}
response := Response{}
err = json.Unmarshal([]byte(line[6:]), &response)
if err != nil {
errChan <- err
return
}
select {
case outputChan <- response:
case <-ctx.Done():
errChan <- ctx.Err()
return
}
}
if err != nil {
if err == io.EOF {
return
}
errChan <- err
return
}
}
}
}()
}

View File

@@ -0,0 +1,24 @@
package llm
type OpenRoute struct {
APIKey string
Endpoint string
Model string
}
func NewOpenRoute(apiKey string, endpoint string, model string) *OpenAI {
return &OpenAI{
APIKey: apiKey,
Endpoint: endpoint,
Model: model,
}
}
func (o *OpenRoute) Generate(prompt string) (string, error) {
return "", nil
// TODO: implement
}
func (o *OpenRoute) GetModel() string {
return o.Model
}