Commit 5e5128c5 authored by Шаронов Егор Алексеевич's avatar Шаронов Егор Алексеевич
Browse files

new route and fix llm quality

parent b556c870
No related merge requests found
Showing with 140 additions and 64 deletions
+140 -64
# Используем базовый образ с Go
FROM golang:1.20
FROM golang:1.23.2
# Устанавливаем рабочую директорию
WORKDIR /app
......@@ -10,7 +10,7 @@ RUN go mod download
COPY . .
# Сборка приложения
RUN go build -o main .
RUN go build -o main ./cmd/assistant
# Указываем порт
EXPOSE 8080
......
......@@ -12,12 +12,13 @@ import (
"model/internal/services/chatassistant"
"net/http"
"os"
"strconv"
)
type Product struct {
ID int `gorm:"primaryKey"`
Content string `gorm:"type:jsonb"`
Embedding pgvector.Vector `gorm:"type:vector(3072)"` // Размер вектора зависит от модели
Embedding pgvector.Vector `gorm:"type:vector(3072)"`
}
func upload() {
......@@ -90,24 +91,24 @@ func upload() {
}
}
func test() {
ctx := context.Background()
initConfig, err := config.InitConfig()
if err != nil {
log.Fatalf("Failed to initialize config: %v", err)
}
Assistant, err := chatassistant.NewChatAssistant(ctx, initConfig)
if err != nil {
log.Fatalf("Failed to initialize NewChatAssistant: %v", err)
}
query, err := Assistant.ProcessQuery(ctx, "Какой ноутбук выбрать? люблю lenovo, хочу чтоб заряд дольше держал")
if err != nil {
return
}
fmt.Println(query)
}
//func test() {
// ctx := context.Background()
// initConfig, err := config.InitConfig()
//
// if err != nil {
// log.Fatalf("Failed to initialize config: %v", err)
// }
//
// Assistant, err := chatassistant.NewChatAssistant(ctx, initConfig)
// if err != nil {
// log.Fatalf("Failed to initialize NewChatAssistant: %v", err)
// }
// query, err := Assistant.ProcessQuery(ctx, "Какой ноутбук выбрать? люблю lenovo, хочу чтоб заряд дольше держал")
// if err != nil {
// return
// }
// fmt.Println(query)
//}
func main() {
// Инициализация конфигурации
......@@ -127,7 +128,7 @@ func main() {
router := gin.Default()
// Роут для обработки запросов
router.POST("/query", func(c *gin.Context) {
router.POST("/llm-response", func(c *gin.Context) {
var request struct {
Query string `json:"query" binding:"required"`
}
......@@ -138,11 +139,56 @@ func main() {
response, err := assistant.ProcessQuery(ctx, request.Query)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to process query"})
return
}
var parsedResponse struct {
Ids []int `json:"Ids"`
Comment string `json:"comment"`
}
if err = json.Unmarshal([]byte(response), &parsedResponse); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "ошибка парсинга ответа"})
return
}
var products []Product
if err = assistant.Store.DB.Where("id IN ?", parsedResponse.Ids).Find(&products).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"products": products, "comment": parsedResponse.Comment})
})
router.GET("/get-product", func(c *gin.Context) {
idStr := c.Query("Id")
if idStr == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request parameter"})
return
}
id, err := strconv.Atoi(idStr)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request parameter"})
return
}
var product Product
if err = assistant.Store.DB.First(&product, id).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"response": response})
var contentMap map[string]interface{}
if err := json.Unmarshal([]byte(product.Content), &contentMap); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Ошибка парсинга JSON содержимого продукта"})
return
}
manufacturer := contentMap["Производитель"].(string)
model := contentMap["Модель"].(string)
Name := manufacturer + " " + model
c.JSON(http.StatusOK, gin.H{"Id": id, "Name": Name})
})
// Запуск сервера
......
......@@ -8,11 +8,11 @@ require (
github.com/jmoiron/sqlx v1.4.0
github.com/joho/godotenv v1.5.1
github.com/nqd/flat v0.2.0
github.com/pgvector/pgvector-go v0.1.1
github.com/pgvector/pgvector-go v0.2.2
github.com/tmc/langchaingo v0.1.12
go.nhat.io/otelsql v0.14.0
gopkg.in/yaml.v3 v3.0.1
gorm.io/driver/postgres v1.5.9
gorm.io/driver/postgres v1.5.10
gorm.io/gorm v1.25.12
)
......@@ -21,8 +21,9 @@ require (
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/dlclark/regexp2 v1.10.0 // indirect
github.com/dlclark/regexp2 v1.11.4 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gage-technologies/mistral-go v1.0.1 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
......@@ -31,7 +32,7 @@ require (
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
......@@ -44,7 +45,7 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pkoukk/tiktoken-go v0.1.6 // indirect
github.com/pkoukk/tiktoken-go v0.1.7 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
go.opentelemetry.io/otel v1.30.0 // indirect
......
......@@ -12,6 +12,8 @@ cloud.google.com/go/iam v1.1.7 h1:z4VHOhwKLF/+UYXAJDFwGtNF0b6gjsW1Pk9Ml0U/IoM=
cloud.google.com/go/iam v1.1.7/go.mod h1:J4PMPg8TtyurAUvSmPj8FF3EDgY1SPRZxcUGrn7WXGA=
cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU=
cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng=
entgo.io/ent v0.13.1 h1:uD8QwN1h6SNphdCCzmkMN3feSUzNnVvV/WIkHKMbzOE=
entgo.io/ent v0.13.1/go.mod h1:qCEmo+biw3ccBn9OyL4ZK5dfpwg++l1Gxwac5B1206A=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
......@@ -29,12 +31,14 @@ github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQ
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gage-technologies/mistral-go v1.0.1 h1:JKoDFDpsAG3YmUKmSP04ADaP1LZYsCAnEL15WM4ACwc=
github.com/gage-technologies/mistral-go v1.0.1/go.mod h1:tF++Xt7U975GcLlzhrjSQb8l/x+PrriO9QEdsgm9l28=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
......@@ -78,8 +82,8 @@ github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoF
github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc=
github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
......@@ -123,10 +127,10 @@ github.com/nqd/flat v0.2.0 h1:g6lXtMxsxrz6PZOO+rNnAJUn/GGRrK4FgVEhy/v+cHI=
github.com/nqd/flat v0.2.0/go.mod h1:FOuslZmNY082wVfVUUb7qAGWKl8z8Nor9FMg+Xj2Nss=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pgvector/pgvector-go v0.1.1 h1:kqJigGctFnlWvskUiYIvJRNwUtQl/aMSUZVs0YWQe+g=
github.com/pgvector/pgvector-go v0.1.1/go.mod h1:wLJgD/ODkdtd2LJK4l6evHXTuG+8PxymYAVomKHOWac=
github.com/pkoukk/tiktoken-go v0.1.6 h1:JF0TlJzhTbrI30wCvFuiw6FzP2+/bR+FIxUdgEAcUsw=
github.com/pkoukk/tiktoken-go v0.1.6/go.mod h1:9NiV+i9mJKGj1rYOT+njbv+ZwA/zJxYdewGl6qVatpg=
github.com/pgvector/pgvector-go v0.2.2 h1:Q/oArmzgbEcio88q0tWQksv/u9Gnb1c3F1K2TnalxR0=
github.com/pgvector/pgvector-go v0.2.2/go.mod h1:u5sg3z9bnqVEdpe1pkTij8/rFhTaMCMNyQagPDLK8gQ=
github.com/pkoukk/tiktoken-go v0.1.7 h1:qOBHXX4PHtvIvmOtyg1EeKlwFRiMKAcoMp4Q+bLQDmw=
github.com/pkoukk/tiktoken-go v0.1.7/go.mod h1:9NiV+i9mJKGj1rYOT+njbv+ZwA/zJxYdewGl6qVatpg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
......@@ -233,11 +237,10 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8=
gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
gorm.io/driver/postgres v1.5.10 h1:7Lggqempgy496c0WfHXsYWxk3Th+ZcW66/21QhVFdeE=
gorm.io/driver/postgres v1.5.10/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo=
......
......@@ -4,26 +4,30 @@ import (
"context"
"fmt"
"github.com/tmc/langchaingo/embeddings"
"github.com/tmc/langchaingo/llms"
"github.com/tmc/langchaingo/llms/mistral"
"github.com/tmc/langchaingo/llms/ollama"
"model/internal/config"
"model/internal/services/store"
)
type ChatAssistant struct {
client *ollama.LLM
client *mistral.Model
embedding *embeddings.EmbedderImpl
Store *store.Store
}
func NewChatAssistant(ctx context.Context, config *config.Config) (*ChatAssistant, error) {
client, err := ollama.New(
ollama.WithModel(config.ModelName),
client, err := mistral.New(
mistral.WithModel("mistral-large-latest"),
mistral.WithAPIKey("ZdDk5S3QxaEytpf9DWXM2nEb6GVHuF9y"),
)
embModel, err := ollama.New(ollama.WithModel(config.ModelName))
if err != nil {
return nil, err
}
embedding, err := embeddings.NewEmbedder(client)
embedding, err := embeddings.NewEmbedder(embModel)
if err != nil {
return nil, err
}
......@@ -42,8 +46,7 @@ func NewChatAssistant(ctx context.Context, config *config.Config) (*ChatAssistan
func (chat *ChatAssistant) ProcessQuery(ctx context.Context, queryString string) (string, error) {
queryEmbedding, err := chat.EmbedQuery(ctx, queryString)
products, err := chat.Store.GetProducts(ctx, queryEmbedding[:768], 20)
products, err := chat.Store.GetProductsByEmbedding(ctx, queryEmbedding[:768], 20)
if err != nil {
return "", err
}
......@@ -52,24 +55,31 @@ func (chat *ChatAssistant) ProcessQuery(ctx context.Context, queryString string)
productsContext += "\n\n" + fmt.Sprintf("%d: %s", product.ID, product.Content)
}
//messageHistory := []llms.MessageContent{
// llms.TextParts(llms.ChatMessageTypeSystem, "Ты автоматизированный ассистент по поиску товаров на основе имеющейся базы."+
// "\n\tЗадача: Анализировать запрос пользователя, сравнить с имеющимся списком продуктов в формате json"+
// " Определять наиболее подходящие товары и возвращать только список id товаров.\n\t"+
// "Инструкции: Внимательно анализировать запрос пользователя и имеющийся список продуктов"+
// "Продукты даны в формате id: {content}.\n\t"+
// "Формат выходных данных: [id1, id2, ...]"+
// "\n\t\n\tПродукты:\n\t"+productsContext),
// llms.TextParts(llms.ChatMessageTypeHuman, queryString),
//}
return chat.client.Call(ctx, "Ты автоматизированный ассистент по поиску товаров на основе имеющейся базы."+
"\n\tЗадача: Анализировать запрос пользователя, сравнить с имеющимся списком продуктов в формате json"+
" Ориентируясь исключительно на приведённый список товаров, определять наиболее подходящие товары "+
"и возвращать только список id товаров.\n\t"+
"Инструкции: Внимательно анализировать запрос пользователя и имеющийся список продуктов"+
"Продукты даны в формате id: {content}.\n\t"+
"Формат выходных данных: [id1, id2, ...]"+
"\n\t\n\tПродукты:\n\t"+productsContext+"\n\t\n\t Запрос:"+queryString)
promt := "Ты автоматизированный ассистент по поиску товаров на основе имеющейся базы." +
"\n\tЗадача: Анализировать запрос пользователя, сравнить с имеющимся списком продуктов в формате json" +
" Ориентируясь исключительно на приведённый список товаров, определять наиболее подходящие товары " +
"и возвращать исключительно json со списком id товаров и комментарием о причине такого выбора на пару предложений." +
"Это строка вида: {\"Ids\":[int,int,...], \"comment\":string} Никакого больше форматирования." +
"Никакой другой текст или описание не нужен, только json. В комментарии говорим о товаре, не об id +" +
"конечный пользователь id не увидит никогда\n\t" +
"Инструкции: Внимательно анализировать запрос пользователя и имеющийся список продуктов" +
"Продукты даны в формате id: contentJson.\n\t" +
"Формат выходных данных: {\"Ids\":[int,int,...], \"comment\":string" +
"\n\t\n\tПродукты:\n\t" + productsContext
messageHistory := []llms.MessageContent{
llms.TextParts(llms.ChatMessageTypeSystem, promt),
llms.TextParts(llms.ChatMessageTypeHuman, queryString),
}
response, err := chat.client.GenerateContent(
ctx,
messageHistory,
llms.WithTemperature(0))
if err != nil {
return "", err
}
return response.Choices[0].Content, nil
}
func (chat *ChatAssistant) EmbedQuery(ctx context.Context, queryString string) ([]float32, error) {
......@@ -79,3 +89,13 @@ func (chat *ChatAssistant) EmbedQuery(ctx context.Context, queryString string) (
}
return queryEmbedding, nil
}
func updateMessageHistory(messageHistory []llms.MessageContent, resp *llms.ContentResponse) []llms.MessageContent {
respchoice := resp.Choices[0]
assistantResponse := llms.TextParts(llms.ChatMessageTypeAI, respchoice.Content)
for _, tc := range respchoice.ToolCalls {
assistantResponse.Parts = append(assistantResponse.Parts, tc)
}
return append(messageHistory, assistantResponse)
}
......@@ -30,7 +30,7 @@ func NewStore(ctx context.Context, config *config.Config) (*Store, error) {
DB: db,
}, nil
}
func (s *Store) GetProducts(ctx context.Context, embedding []float32, limit int) ([]Product, error) {
func (s *Store) GetProductsByEmbedding(ctx context.Context, embedding []float32, limit int) ([]Product, error) {
var products []Product
query := `
SELECT *
......@@ -44,3 +44,9 @@ func (s *Store) GetProducts(ctx context.Context, embedding []float32, limit int)
}
return products, nil
}
func (s *Store) SelectProductById(ctx context.Context, id int) Product {
var product Product
s.DB.First(&product, id)
return product
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment