fix(captain): 试验场不查 FAQ 知识库 + 添加常见问题报 400
三个问题修复:
1. 添加常见问题报 400 (assistant_id 类型不匹配)
- CreateResponseDialog.vue: route.params.assistantId 是字符串,
传给后端 uint 字段导致 JSON 反序列化失败
- 修复: Number(route.params.assistantId) 转为数字
- 同时修复 POST /assistant_responses 无尾部斜杠 307 重定向问题
2. 试验场 playground 不做 RAG 检索
- generatePlaygroundLLMResponse 只构建 system prompt + 对话历史,
从不查 FAQ 知识库
- 新增 retrieveFAQContext(): embed 用户问题 → pgvector 搜索 approved
FAQ → 注入 system prompt
- 受 feature_faq 配置开关控制
3. FAQ embedding 无法写入 (pgvector 序列化 + 维度问题)
- pgvector stub 无 driver.Valuer, GORM Save() 报 SQLSTATE 42804
- 新增 UpdateEmbedding() 用 ?::vector 原始 SQL 绕过
- SimilaritySearch 排除 embedding 列 + 手动格式化向量字面量
- embedding 列从 vector(1536) 改为 vector (跟随模型维度)
- FAQ 创建/更新时自动索引 embedding (SetRAGService 注入)
This commit is contained in:
@@ -2,6 +2,8 @@ package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gochat/gochat/internal/model"
|
||||
"github.com/pgvector/pgvector-go"
|
||||
@@ -45,6 +47,21 @@ func (r *CaptainAssistantResponseRepo) Update(ctx context.Context, resp *model.C
|
||||
return r.db.WithContext(ctx).Save(resp).Error
|
||||
}
|
||||
|
||||
// UpdateEmbedding updates only the embedding column using raw SQL with explicit
|
||||
// ::vector cast to avoid GORM/pgvector serialization issue (SQLSTATE 42804).
|
||||
func (r *CaptainAssistantResponseRepo) UpdateEmbedding(ctx context.Context, id uint, embedding pgvector.Vector) error {
|
||||
// Format as PG vector literal: [0.1,0.2,...]
|
||||
strs := make([]string, len(embedding))
|
||||
for i, v := range embedding {
|
||||
strs[i] = strconv.FormatFloat(float64(v), 'f', -1, 32)
|
||||
}
|
||||
vecStr := "[" + strings.Join(strs, ",") + "]"
|
||||
return r.db.WithContext(ctx).Exec(
|
||||
"UPDATE captain_assistant_responses SET embedding = ?::vector WHERE id = ?",
|
||||
vecStr, id,
|
||||
).Error
|
||||
}
|
||||
|
||||
func (r *CaptainAssistantResponseRepo) Delete(ctx context.Context, id uint) error {
|
||||
return r.db.WithContext(ctx).Delete(&model.CaptainAssistantResponse{}, id).Error
|
||||
}
|
||||
@@ -80,10 +97,18 @@ func (r *CaptainAssistantResponseRepo) ListByDocument(ctx context.Context, docum
|
||||
// Reference: Chatwoot Captain::AssistantResponsesSearchService
|
||||
func (r *CaptainAssistantResponseRepo) SimilaritySearch(ctx context.Context, assistantID uint, embedding pgvector.Vector, limit int) ([]model.CaptainAssistantResponse, error) {
|
||||
var responses []model.CaptainAssistantResponse
|
||||
// Format embedding as PG vector literal for the <=> operator (stub has no driver.Valuer).
|
||||
strs := make([]string, len(embedding))
|
||||
for i, v := range embedding {
|
||||
strs[i] = strconv.FormatFloat(float64(v), 'f', -1, 32)
|
||||
}
|
||||
vecStr := "[" + strings.Join(strs, ",") + "]"
|
||||
// Cosine distance (<=>) orders by closest first.
|
||||
// Omit embedding column from SELECT — the pgvector stub can't scan it back.
|
||||
if err := r.db.WithContext(ctx).
|
||||
Select("id, account_id, assistant_id, documentable_id, documentable_type, question, answer, status, edited, created_at, updated_at").
|
||||
Where("assistant_id = ? AND status = ?", assistantID, model.ResponseStatusApproved).
|
||||
Order(gorm.Expr("embedding <=> ?", embedding)).
|
||||
Order(gorm.Expr("embedding <=> ?::vector", vecStr)).
|
||||
Limit(limit).
|
||||
Find(&responses).Error; err != nil {
|
||||
return nil, err
|
||||
|
||||
Reference in New Issue
Block a user