Files
smart-search-back/internal/ai/perplexity.go
vallyenfail ad82c8244f
Some checks failed
Deploy Smart Search Backend Test / deploy (push) Failing after 3m19s
add service
2026-01-20 11:22:04 +03:00

322 lines
9.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package ai
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"regexp"
"strings"
"git.techease.ru/Smart-search/smart-search-back/internal/model"
"git.techease.ru/Smart-search/smart-search-back/pkg/errors"
)
type PerplexityClient struct {
apiKey string
client *http.Client
}
type perplexityRequest struct {
Model string `json:"model"`
Messages []perplexityMessage `json:"messages"`
}
type perplexityMessage struct {
Role string `json:"role"`
Content string `json:"content"`
}
type perplexityResponse struct {
Choices []struct {
Message perplexityMessage `json:"message"`
} `json:"choices"`
Usage *struct {
PromptTokens int `json:"prompt_tokens"`
CompletionTokens int `json:"completion_tokens"`
} `json:"usage,omitempty"`
Error *struct {
Message string `json:"message"`
} `json:"error,omitempty"`
}
type supplierData struct {
CompanyName string `json:"company_name"`
Email string `json:"email"`
Phone string `json:"phone"`
Address string `json:"address"`
URL string `json:"url"`
}
func NewPerplexityClient(apiKey string) *PerplexityClient {
return &PerplexityClient{
apiKey: apiKey,
client: &http.Client{},
}
}
func (c *PerplexityClient) isMockMode() bool {
return c.apiKey == ""
}
func (c *PerplexityClient) FindSuppliers(tzText string) ([]*model.Supplier, int, int, error) {
if c.isMockMode() {
return c.generateMockSuppliers(), 1000, 500, nil
}
prompt := fmt.Sprintf(`Ты — эксперт по поиску поставщиков на российском рынке.
ЗАДАЧА:
Найти компании-поставщики/производители для технического задания:
%s
ОПРЕДЕЛЕНИЕ ТИПА ПОИСКА:
- Если ТЗ требует производства/изготовления → ищи производителей
- Если ТЗ требует готовый товар/услугу → ищи компании, которые это продают
КРИТЕРИИ:
1. Максимальная релевантность
2. Действующие компании с полными контактами
3. Приоритет: производители > дистрибьюторы
ПРИМЕРЫ ОТВЕТОВ:
Пример 1 - ТЗ: "Поставка офисной мебели: столы 20 шт, стулья 50 шт"
[
{
"company_name": "ООО Мебельная фабрика Союз",
"email": "zakaz@mebelsoyuz.ru",
"phone": "+7 495 123-45-67",
"adress": "г. Москва, ул. Промышленная, д. 15",
"url": ""
}
]
Пример 2 - ТЗ: "Производство кованых изделий: ворота, решётки, перила"
[
{
"company_name": "ООО Кузня Премиум",
"email": "info@kuzniya.ru",
"phone": "",
"adress": "г. Москва, ул. Заводская, д. 42",
"url": "www.mebelsoyuz.ru"
}
]
ФОРМАТ ОТВЕТА - ТОЛЬКО JSON:
[
{
"company_name": "...",
"email": "...",
"phone": "...",
"adress": "...",
"url": "..."
}
]
ПРАВИЛА:
- Минимум 15 компаний, максимум 100
- Только валидный JSON массив, без текста вокруг
- email и url всегда заполнены (не null)
- Если другие поля пустые, то можно оставить пустую строку -> ""
- Сортируй по релевантности`, tzText)
reqBody := perplexityRequest{
Model: "llama-3.1-sonar-large-128k-online",
Messages: []perplexityMessage{
{
Role: "user",
Content: prompt,
},
},
}
jsonData, err := json.Marshal(reqBody)
if err != nil {
return nil, 0, 0, errors.NewInternalError(errors.AIAPIError, "failed to marshal request", err)
}
req, err := http.NewRequest("POST", "https://api.perplexity.ai/chat/completions", bytes.NewBuffer(jsonData))
if err != nil {
return nil, 0, 0, errors.NewInternalError(errors.AIAPIError, "failed to create request", 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 nil, 0, 0, errors.NewInternalError(errors.AIAPIError, "failed to send request", err)
}
defer func() { _ = resp.Body.Close() }()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, 0, 0, errors.NewInternalError(errors.AIAPIError, "failed to read response", err)
}
var aiResp perplexityResponse
if err := json.Unmarshal(body, &aiResp); err != nil {
return nil, 0, 0, errors.NewInternalError(errors.AIAPIError, "failed to unmarshal response", err)
}
if aiResp.Error != nil {
return nil, 0, 0, errors.NewInternalError(errors.AIAPIError, aiResp.Error.Message, nil)
}
if len(aiResp.Choices) == 0 {
return nil, 0, 0, errors.NewInternalError(errors.AIAPIError, "no choices in response", nil)
}
content := aiResp.Choices[0].Message.Content
content = strings.TrimPrefix(content, "```json")
content = strings.TrimPrefix(content, "```")
content = strings.TrimSuffix(content, "```")
content = strings.TrimSpace(content)
var supplierDataList []supplierData
if err := json.Unmarshal([]byte(content), &supplierDataList); err != nil {
re := regexp.MustCompile(`\[[\s\S]*\]`)
match := re.FindString(content)
if match != "" {
if err := json.Unmarshal([]byte(match), &supplierDataList); err != nil {
return nil, 0, 0, errors.NewInternalError(errors.AIAPIError, "failed to parse suppliers response", err)
}
} else {
return nil, 0, 0, errors.NewInternalError(errors.AIAPIError, "failed to parse suppliers response", err)
}
}
suppliers := make([]*model.Supplier, 0, len(supplierDataList))
for _, sd := range supplierDataList {
suppliers = append(suppliers, &model.Supplier{
Name: sd.CompanyName,
Email: sd.Email,
Phone: sd.Phone,
Address: sd.Address,
URL: sd.URL,
})
}
promptTokens := 0
responseTokens := 0
if aiResp.Usage != nil {
promptTokens = aiResp.Usage.PromptTokens
responseTokens = aiResp.Usage.CompletionTokens
}
return suppliers, promptTokens, responseTokens, nil
}
func (c *PerplexityClient) generateMockSuppliers() []*model.Supplier {
return []*model.Supplier{
{
Name: "ООО Поставщик-1 (Mock)",
Email: "supplier1@example.com",
Phone: "+7 (495) 123-45-67",
Address: "г. Москва, ул. Примерная, д. 1",
URL: "https://supplier1.example.com",
},
{
Name: "ООО Поставщик-2 (Mock)",
Email: "supplier2@example.com",
Phone: "+7 (495) 234-56-78",
Address: "г. Москва, ул. Примерная, д. 2",
URL: "https://supplier2.example.com",
},
{
Name: "ООО Поставщик-3 (Mock)",
Email: "supplier3@example.com",
Phone: "+7 (495) 345-67-89",
Address: "г. Москва, ул. Примерная, д. 3",
URL: "https://supplier3.example.com",
},
{
Name: "ООО Производитель-1 (Mock)",
Email: "producer1@example.com",
Phone: "+7 (495) 456-78-90",
Address: "г. Санкт-Петербург, ул. Тестовая, д. 10",
URL: "https://producer1.example.com",
},
{
Name: "ООО Производитель-2 (Mock)",
Email: "producer2@example.com",
Phone: "+7 (495) 567-89-01",
Address: "г. Санкт-Петербург, ул. Тестовая, д. 20",
URL: "https://producer2.example.com",
},
{
Name: "ООО Дистрибьютор-1 (Mock)",
Email: "distributor1@example.com",
Phone: "+7 (495) 678-90-12",
Address: "г. Казань, ул. Демо, д. 5",
URL: "https://distributor1.example.com",
},
{
Name: "ООО Дистрибьютор-2 (Mock)",
Email: "distributor2@example.com",
Phone: "+7 (495) 789-01-23",
Address: "г. Казань, ул. Демо, д. 15",
URL: "https://distributor2.example.com",
},
{
Name: "ООО Импортер-1 (Mock)",
Email: "importer1@example.com",
Phone: "+7 (495) 890-12-34",
Address: "г. Новосибирск, ул. Примера, д. 100",
URL: "https://importer1.example.com",
},
{
Name: "ООО Импортер-2 (Mock)",
Email: "importer2@example.com",
Phone: "+7 (495) 901-23-45",
Address: "г. Новосибирск, ул. Примера, д. 200",
URL: "https://importer2.example.com",
},
{
Name: "ООО Оптовик-1 (Mock)",
Email: "wholesale1@example.com",
Phone: "+7 (495) 012-34-56",
Address: "г. Екатеринбург, ул. Тестовая, д. 50",
URL: "https://wholesale1.example.com",
},
{
Name: "ООО Оптовик-2 (Mock)",
Email: "wholesale2@example.com",
Phone: "+7 (495) 123-45-67",
Address: "г. Екатеринбург, ул. Тестовая, д. 60",
URL: "https://wholesale2.example.com",
},
{
Name: "ООО Фабрика-1 (Mock)",
Email: "factory1@example.com",
Phone: "+7 (495) 234-56-78",
Address: "г. Нижний Новгород, Промзона, д. 1",
URL: "https://factory1.example.com",
},
{
Name: "ООО Фабрика-2 (Mock)",
Email: "factory2@example.com",
Phone: "+7 (495) 345-67-89",
Address: "г. Нижний Новгород, Промзона, д. 2",
URL: "https://factory2.example.com",
},
{
Name: "ООО Завод-1 (Mock)",
Email: "plant1@example.com",
Phone: "+7 (495) 456-78-90",
Address: "г. Челябинск, Индустриальная, д. 10",
URL: "https://plant1.example.com",
},
{
Name: "ООО Завод-2 (Mock)",
Email: "plant2@example.com",
Phone: "+7 (495) 567-89-01",
Address: "г. Челябинск, Индустриальная, д. 20",
URL: "https://plant2.example.com",
},
}
}