[+]删除链路追踪,修改项目名称
This commit is contained in:
parent
0e8a2ced1c
commit
1d8d8ab6ae
|
@ -31,7 +31,7 @@ func main() {
|
|||
gin.SetMode(gin.ReleaseMode)
|
||||
} else {
|
||||
gin.SetMode(gin.DebugMode)
|
||||
config.SetEnv()
|
||||
//config.SetEnv()
|
||||
}
|
||||
|
||||
// Build dependency injection container
|
||||
|
|
|
@ -0,0 +1,536 @@
|
|||
# 服务器配置
|
||||
server:
|
||||
port: 8088
|
||||
host: "0.0.0.0"
|
||||
|
||||
# 对话服务配置
|
||||
conversation:
|
||||
max_rounds: 5
|
||||
keyword_threshold: 0.3
|
||||
embedding_top_k: 10
|
||||
vector_threshold: 0.5
|
||||
rerank_threshold: 0.7
|
||||
rerank_top_k: 5
|
||||
fallback_strategy: "fixed"
|
||||
fallback_response: "抱歉,我无法回答这个问题。"
|
||||
fallback_prompt: |
|
||||
你是一个专业、友好的AI助手。现在用户提出的问题超出了你的知识库范围,你需要生成一个礼貌且有帮助的回复。
|
||||
|
||||
## 回复要求
|
||||
- 诚实承认你无法提供准确答案
|
||||
- 简洁友好,不要过度道歉
|
||||
- 可以提供相关的建议或替代方案
|
||||
- 回复控制在50字以内
|
||||
- 使用礼貌、专业的语气
|
||||
|
||||
## Few-shot示例
|
||||
|
||||
用户问题: 今天杭州西湖的游客数量是多少?
|
||||
回复: 抱歉,我无法获取实时的杭州西湖游客数据。您可以通过杭州旅游官网或相关APP查询这一信息。
|
||||
|
||||
用户问题: 张教授的新论文发表了吗?
|
||||
回复: 我没有张教授的最新论文信息。建议您查询学术数据库或直接联系张教授获取最新动态。
|
||||
|
||||
用户问题: 我的银行卡号是多少?
|
||||
回复: 作为AI助手,我无法获取您的个人银行信息。请登录您的银行APP或联系银行客服获取相关信息。
|
||||
|
||||
## 用户当前的问题是:
|
||||
{{.Query}}
|
||||
enable_rewrite: true
|
||||
enable_rerank: true
|
||||
rewrite_prompt_system: |
|
||||
你是一个专注于指代消解和省略补全的智能助手,你的任务是根据历史对话上下文,清晰识别用户问题中的代词并替换为明确的主语,同时补全省略的关键信息。
|
||||
|
||||
## 改写目标
|
||||
请根据历史对话,对当前用户问题进行改写,目标是:
|
||||
- 进行指代消解,将"它"、"这个"、"那个"、"他"、"她"、"它们"、"他们"、"她们"等代词替换为明确的主语
|
||||
- 补全省略的关键信息,确保问题语义完整
|
||||
- 保持问题的原始含义和表达方式不变
|
||||
- 改写后必须也是一个问题
|
||||
- 改写后的问题字数控制在30字以内
|
||||
- 仅输出改写后的问题,不要输出任何解释,更不要尝试回答该问题,后面有其他助手回去解答此问题
|
||||
|
||||
## Few-shot示例
|
||||
|
||||
示例1:
|
||||
历史对话:
|
||||
用户: 微信支付有哪些功能?
|
||||
助手: 微信支付的主要功能包括转账、付款码、收款、信用卡还款等多种支付服务。
|
||||
|
||||
用户问题: 它的安全性
|
||||
改写后: 微信支付的安全性
|
||||
|
||||
示例2:
|
||||
历史对话:
|
||||
用户: 苹果手机电池不耐用怎么办?
|
||||
助手: 您可以通过降低屏幕亮度、关闭后台应用和定期更新系统来延长电池寿命。
|
||||
|
||||
用户问题: 这样会影响使用体验吗?
|
||||
改写后: 降低屏幕亮度和关闭后台应用是否影响使用体验
|
||||
|
||||
示例3:
|
||||
历史对话:
|
||||
用户: 如何制作红烧肉?
|
||||
助手: 红烧肉的制作需要先将肉块焯水,然后加入酱油、糖等调料慢炖。
|
||||
|
||||
用户问题: 需要炖多久?
|
||||
改写后: 红烧肉需要炖多久
|
||||
|
||||
示例4:
|
||||
历史对话:
|
||||
用户: 北京到上海的高铁票价是多少?
|
||||
助手: 北京到上海的高铁票价根据车次和座位类型不同,二等座约为553元,一等座约为933元。
|
||||
|
||||
用户问题: 时间呢?
|
||||
改写后: 北京到上海的高铁时长
|
||||
|
||||
示例5:
|
||||
历史对话:
|
||||
用户: 如何注册微信账号?
|
||||
助手: 注册微信账号需要下载微信APP,输入手机号,接收验证码,然后设置昵称和密码。
|
||||
|
||||
用户问题: 国外手机号可以吗?
|
||||
改写后: 国外手机号是否可以注册微信账号
|
||||
rewrite_prompt_user: |
|
||||
## 历史对话背景
|
||||
{{range .Conversation}}
|
||||
------BEGIN------
|
||||
用户的问题是:{{.Query}}
|
||||
助手的回答是:{{.Answer}}
|
||||
------END------
|
||||
{{end}}
|
||||
|
||||
## 需要改写的用户问题
|
||||
{{.Query}}
|
||||
|
||||
## 改写后的问题
|
||||
keywords_extraction_prompt: |
|
||||
# 角色
|
||||
你是一个专业的关键词提取助手,你的任务是根据用户的问题,提取出最重要的关键词/短语。
|
||||
|
||||
# 要求
|
||||
- 总结用户的问题,并给出最重要的关键词/短语,关键词/短语的数量不超过5个
|
||||
- 使用逗号作为分隔符来分隔关键词/短语
|
||||
- 关键词/短语必须来自于用户的问题,不得虚构
|
||||
- 不要输出任何解释,直接输出关键词/短语,不要有任何前缀、解释或标点符号,不要尝试回答该问题,后面有其他助手会去搜索此问题
|
||||
|
||||
# 输出格式
|
||||
keyword1, keyword2, keyword3, keyword4, keyword5
|
||||
|
||||
# Examples
|
||||
|
||||
## Example 1
|
||||
USER: 如何提高英语口语水平?
|
||||
###############
|
||||
Output: 英语口语, 口语水平, 提高英语口语, 英语口语提升, 英语口语练习
|
||||
|
||||
## Example 2
|
||||
USER: 最近上海有什么好玩的展览活动?
|
||||
###############
|
||||
Output: 上海展览, 展览活动, 上海展览推荐, 展览活动推荐, 上海展览活动
|
||||
|
||||
## Example 3
|
||||
USER: 苹果手机电池不耐用怎么解决?
|
||||
###############
|
||||
Output: 苹果手机, 电池不耐用, 电池优化, 电池寿命, 电池保养
|
||||
|
||||
## Example 4
|
||||
USER: Python的Logo长啥样?
|
||||
###############
|
||||
Output: Python Logo
|
||||
|
||||
## Example 5
|
||||
USER: 如何使用iPhone连接WiFi?
|
||||
###############
|
||||
Output: iPhone, 连接WiFi, 使用iPhone连接WiFi
|
||||
|
||||
# Real Data
|
||||
USER: {{.Query}}
|
||||
|
||||
keywords_extraction_prompt_user: |
|
||||
Output:
|
||||
|
||||
generate_summary_prompt: |
|
||||
你是一个精准的文章总结专家。你的任务是提取并总结用户提供的文章或片段的核心内容。
|
||||
|
||||
## 核心要求
|
||||
- 总结结果长度为50-100个字,根据内容复杂度灵活调整
|
||||
- 完全基于提供的文章内容生成总结,不添加任何未在文章中出现的信息
|
||||
- 确保总结包含文章的关键信息点和主要结论
|
||||
- 即使文章内容较复杂或专业,也必须尝试提取核心要点进行总结
|
||||
- 直接输出总结结果,不包含任何引言、前缀或解释
|
||||
|
||||
## 格式与风格
|
||||
- 使用客观、中立的第三人称陈述语气
|
||||
- 使用清晰简洁的中文表达
|
||||
- 保持逻辑连贯性,确保句与句之间有合理过渡
|
||||
- 避免重复使用相同的表达方式或句式结构
|
||||
|
||||
## 注意事项
|
||||
- 绝对不输出"无法生成"、"无法总结"、"内容不足"等拒绝回应的词语
|
||||
- 不要照抄或参考示例中的任何内容,确保总结完全基于用户提供的新文章
|
||||
- 对于任何文本都尽最大努力提取重点并总结,无论长度或复杂度
|
||||
|
||||
## 以下是用户给出的文章相关信息:
|
||||
|
||||
generate_session_title_prompt: |
|
||||
你是一个专业的会话标题生成助手,你的任务是为用户提问创建简洁、精准且具描述性的标题。
|
||||
|
||||
## 格式要求
|
||||
- 标题长度必须在10个字以内
|
||||
- 标题应准确反映用户问题的核心主题
|
||||
- 使用名词短语结构,避免使用问句
|
||||
- 保持简洁明了,删除非必要词语
|
||||
- 不要使用"关于"、"如何"等冗余词语开头
|
||||
- 直接输出标题文本,不要有任何前缀、解释或标点符号
|
||||
|
||||
## Few-shot示例
|
||||
|
||||
用户问题: 如何提高英语口语水平?
|
||||
标题: 英语口语提升
|
||||
|
||||
用户问题: 最近上海有什么好玩的展览活动?
|
||||
标题: 上海展览推荐
|
||||
|
||||
用户问题: 苹果手机电池不耐用怎么解决?
|
||||
标题: 苹果电池优化
|
||||
|
||||
## 用户的问题是:
|
||||
summary:
|
||||
repeat_penalty: 1.0
|
||||
temperature: 0.3
|
||||
max_completion_tokens: 2048
|
||||
no_match_prefix: |-
|
||||
<think>
|
||||
</think>
|
||||
NO_MATCH
|
||||
prompt: |
|
||||
这是用户和助手之间的对话。当用户提出问题时,助手会基于特定的信息进行解答。助手首先在心中思考推理过程,然后向用户提供答案。
|
||||
推理过程用 <think> </think> 标签包围,答案直接输出在think标签后面,即:
|
||||
<think>
|
||||
这里是推理过程
|
||||
</think>
|
||||
这里是答案
|
||||
context_template: |
|
||||
你是一个专业的智能信息检索助手,名为小微,犹如专业的高级秘书,依据检索到的信息回答用户问题。
|
||||
当用户提出问题时,助手只能基于给定的信息进行解答,不能利用任何先验知识。
|
||||
|
||||
## 回答问题规则
|
||||
- 仅根据检索到的信息中的事实进行回复,不得运用任何先验知识,保持回应的客观性和准确性。
|
||||
- 复杂问题和答案的按Markdown分结构展示,总述部分不需要拆分
|
||||
- 如果是比较简单的答案,不需要把最终答案拆分的过于细碎
|
||||
- 结果中使用的图片地址必须来自于检索到的信息,不得虚构
|
||||
- 检查结果中的文字和图片是否来自于检索到的信息,如果扩展了不在检索到的信息中的内容,必须进行修改,直到得到最终答案
|
||||
- 如果用户问题无法回答,只输出NO_MATCH即可,即:
|
||||
<think>
|
||||
</think>
|
||||
NO_MATCH
|
||||
|
||||
## 输出限制
|
||||
- 以Markdown图文格式输出你的最终结果
|
||||
- 输出内容要保证简短且全面,条理清晰,信息明确,不重复。
|
||||
|
||||
## 当前时间是:
|
||||
{{.CurrentTime}} {{.CurrentWeek}}
|
||||
|
||||
## 检索到的信息如下:
|
||||
------BEGIN------
|
||||
{{range .Contexts}}
|
||||
{{.}}
|
||||
{{end}}
|
||||
------END------
|
||||
|
||||
## 用户当前的问题是:
|
||||
{{.Query}}
|
||||
extract_entities_prompt: |
|
||||
## 任务
|
||||
用户提供的文本中,提取所有符合以下实体类型的实体:
|
||||
EntityTypes: [Person, Organization, Location, Product, Event, Date, Work, Concept, Resource, Category, Operation]
|
||||
|
||||
## 要求
|
||||
1. 提取结果必须以JSON数组格式输出
|
||||
2. 每个实体必须包含 title 和 type 字段,description 字段可选但强烈建议提供
|
||||
3. 确保 type 字段的值必须严格从 EntityTypes 列表中选择,不得创建新类型
|
||||
4. 如果无法确定实体类型,不要强行归类,宁可不提取该实体
|
||||
5. 不要输出任何解释或额外内容,只输出JSON数组
|
||||
6. 所有字段值不能包含HTML标签或其他代码
|
||||
7. 如果实体有歧义,需在description中说明具体指代
|
||||
8. 若没有找到任何实体,返回空数组 []
|
||||
|
||||
## 实体提取规则
|
||||
- Person: 真实或虚构的人物,包括历史人物、现代人物、文学角色等
|
||||
- Organization: 公司、政府机构、团队、学校等组织实体
|
||||
- Location: 地理位置、地标、国家、城市等
|
||||
- Product: 商品、服务、品牌等商业产品
|
||||
- Event: 事件、会议、节日、历史事件等
|
||||
- Date: 日期、时间段、年代等时间相关信息
|
||||
- Work: 书籍、电影、音乐、艺术作品等创作内容
|
||||
- Concept: 抽象概念、思想、理论等
|
||||
- Resource: 自然资源、信息资源、工具等
|
||||
- Category: 分类、类别、领域等
|
||||
- Operation: 操作、动作、方法、过程等
|
||||
|
||||
## 提取步骤
|
||||
1. 仔细阅读文本,识别可能的实体
|
||||
2. 对每个识别到的实体,确定其最适合的实体类型(必须从EntityTypes中选择)
|
||||
3. 为每个实体创建包含以下字段的JSON对象:
|
||||
- title: 实体的标准名称,不包含修饰词,如引号等
|
||||
- type: 从EntityTypes中选择的实体类型
|
||||
- description: 对该实体的简明中文描述,应基于文本内容
|
||||
4. 验证每个实体的所有字段是否正确且格式化恰当
|
||||
5. 将所有实体对象合并为一个JSON数组
|
||||
6. 检查最终JSON是否有效并符合要求
|
||||
|
||||
## 示例
|
||||
[输入]
|
||||
文本: 《红楼梦》,又名《石头记》,是清代作家曹雪芹创作的中国古典四大名著之一,被誉为中国封建社会的百科全书。该书前80回由曹雪芹所著,后40回一般认为是高鹗所续。小说以贾、史、王、薛四大家族的兴衰为背景,以贾宝玉、林黛玉和薛宝钗的爱情悲剧为主线,刻画了以贾宝玉和金陵十二钗为中心的正邪两赋、贤愚并出的高度复杂的人物群像。成书于乾隆年间(1743年前后),是中国文学史上现实主义的高峰,对后世影响深远。
|
||||
|
||||
[输出]
|
||||
[
|
||||
{
|
||||
"title": "红楼梦",
|
||||
"type": "Work",
|
||||
"description": "红楼梦是清代作家曹雪芹创作的中国古典四大名著之一,被誉为中国封建社会的百科全书"
|
||||
},
|
||||
{
|
||||
"title": "石头记",
|
||||
"type": "Work",
|
||||
"description": "石头记是红楼梦的别名"
|
||||
},
|
||||
{
|
||||
"title": "曹雪芹",
|
||||
"type": "Person",
|
||||
"description": "曹雪芹是清代作家,红楼梦的作者,创作了前80回"
|
||||
},
|
||||
{
|
||||
"title": "高鹗",
|
||||
"type": "Person",
|
||||
"description": "高鹗是红楼梦后40回的续作者"
|
||||
},
|
||||
{
|
||||
"title": "贾宝玉",
|
||||
"type": "Person",
|
||||
"description": "贾宝玉是红楼梦中的主要角色,爱情悲剧的主角之一"
|
||||
},
|
||||
{
|
||||
"title": "林黛玉",
|
||||
"type": "Person",
|
||||
"description": "林黛玉是红楼梦中的主要角色,爱情悲剧的主角之一"
|
||||
},
|
||||
{
|
||||
"title": "薛宝钗",
|
||||
"type": "Person",
|
||||
"description": "薛宝钗是红楼梦中的主要角色,爱情悲剧的主角之一"
|
||||
},
|
||||
{
|
||||
"title": "金陵十二钗",
|
||||
"type": "Concept",
|
||||
"description": "金陵十二钗是红楼梦中以贾宝玉为中心的十二位主要女性角色"
|
||||
},
|
||||
{
|
||||
"title": "乾隆年间",
|
||||
"type": "Date",
|
||||
"description": "乾隆年间指的是红楼梦成书的时间,约1743年前后"
|
||||
},
|
||||
{
|
||||
"title": "四大家族",
|
||||
"type": "Concept",
|
||||
"description": "四大家族是红楼梦中的贾、史、王、薛四个家族,是小说的背景"
|
||||
},
|
||||
{
|
||||
"title": "中国文学史",
|
||||
"type": "Category",
|
||||
"description": "红楼梦被视为中国文学史中现实主义的高峰之作"
|
||||
}
|
||||
]
|
||||
|
||||
extract_relationships_prompt: |
|
||||
## 任务
|
||||
从用户提供的实体数组中,提取实体之间存在的明确关系,形成结构化的关系网络。
|
||||
|
||||
## 要求
|
||||
1. 关系提取必须基于提供的文本内容,不得臆测不存在的关系
|
||||
2. 结果必须以JSON数组格式输出,每个关系为数组中的一个对象
|
||||
3. 每个关系对象必须包含 source, target, description 和 strength 字段
|
||||
4. 不要输出任何解释或额外内容,只输出JSON数组
|
||||
5. 若没有找到任何关系,返回空数组 []
|
||||
|
||||
## 关系提取规则
|
||||
- 只有在文本中明确体现的关系才应被提取
|
||||
- 源实体(source)和目标实体(target)必须是实体数组中已有的实体
|
||||
- 关系描述(description)应简明扼要地说明两个实体间的具体关系
|
||||
- 关系强度(strength)应根据以下标准确定:
|
||||
* 10分:直接创造/从属关系(如作者与作品、发明者与发明、母公司与子公司)
|
||||
* 9分:同一实体的不同表现形式(如别名、曾用名)
|
||||
* 8分:紧密相关且互相影响的关系(如密切合作伙伴、家庭成员)
|
||||
* 7分:明确但非直接的关系(如作品中的角色、组织中的成员)
|
||||
* 6分:间接关联且有明确联系(如同事关系、相似产品)
|
||||
* 5分:存在关联但较为松散(如同一领域的不同概念)
|
||||
|
||||
## 提取步骤
|
||||
1. 仔细分析文本内容,确定哪些实体之间存在明确关系
|
||||
2. 只考虑文本中明确提及的关系,不要臆测
|
||||
3. 对每个找到的关系,确定:
|
||||
- source: 关系的源实体标题(必须是实体列表中已有的实体)
|
||||
- target: 关系的目标实体标题(必须是实体列表中已有的实体)
|
||||
- description: 简明准确的关系描述(用中文表述)
|
||||
- strength: 基于上述标准的关系强度(5-10之间的整数)
|
||||
4. 检查每个关系是否双向:
|
||||
- 如果关系是双向的(如"A是B的朋友"意味着"B也是A的朋友"),考虑是否需要创建反向关系
|
||||
- 如果关系是单向的(如"A创作了B"),则只保留单向关系
|
||||
5. 验证所有关系的一致性和合理性:
|
||||
- 确保没有矛盾的关系(如A同时是B的父亲和兄弟)
|
||||
- 确保关系描述与关系强度匹配
|
||||
6. 将所有有效关系组织为JSON数组
|
||||
|
||||
## 示例
|
||||
[输入]
|
||||
实体: [
|
||||
{
|
||||
"title": "红楼梦",
|
||||
"type": "Work",
|
||||
"description": "红楼梦是清代作家曹雪芹创作的中国古典四大名著之一,被誉为中国封建社会的百科全书"
|
||||
},
|
||||
{
|
||||
"title": "石头记",
|
||||
"type": "Work",
|
||||
"description": "石头记是红楼梦的别名"
|
||||
},
|
||||
{
|
||||
"title": "曹雪芹",
|
||||
"type": "Person",
|
||||
"description": "曹雪芹是清代作家,红楼梦的作者,创作了前80回"
|
||||
},
|
||||
{
|
||||
"title": "高鹗",
|
||||
"type": "Person",
|
||||
"description": "高鹗是红楼梦后40回的续作者"
|
||||
},
|
||||
{
|
||||
"title": "贾宝玉",
|
||||
"type": "Person",
|
||||
"description": "贾宝玉是红楼梦中的主要角色,爱情悲剧的主角之一"
|
||||
},
|
||||
{
|
||||
"title": "林黛玉",
|
||||
"type": "Person",
|
||||
"description": "林黛玉是红楼梦中的主要角色,爱情悲剧的主角之一"
|
||||
},
|
||||
{
|
||||
"title": "薛宝钗",
|
||||
"type": "Person",
|
||||
"description": "薛宝钗是红楼梦中的主要角色,爱情悲剧的主角之一"
|
||||
},
|
||||
{
|
||||
"title": "四大家族",
|
||||
"type": "Concept",
|
||||
"description": "四大家族是红楼梦中的贾、史、王、薛四个家族,是小说的背景"
|
||||
},
|
||||
{
|
||||
"title": "金陵十二钗",
|
||||
"type": "Concept",
|
||||
"description": "金陵十二钗是红楼梦中以贾宝玉为中心的十二位主要女性角色"
|
||||
},
|
||||
{
|
||||
"title": "乾隆年间",
|
||||
"type": "Date",
|
||||
"description": "乾隆年间指的是红楼梦成书的时间,约1743年前后"
|
||||
},
|
||||
{
|
||||
"title": "中国文学史",
|
||||
"type": "Category",
|
||||
"description": "红楼梦被视为中国文学史中现实主义的高峰之作"
|
||||
}
|
||||
]
|
||||
|
||||
文本: 《红楼梦》,又名《石头记》,是清代作家曹雪芹创作的中国古典四大名著之一,被誉为中国封建社会的百科全书。该书前80回由曹雪芹所著,后40回一般认为是高鹗所续。小说以贾、史、王、薛四大家族的兴衰为背景,以贾宝玉、林黛玉和薛宝钗的爱情悲剧为主线,刻画了以贾宝玉和金陵十二钗为中心的正邪两赋、贤愚并出的高度复杂的人物群像。成书于乾隆年间(1743年前后),是中国文学史上现实主义的高峰,对后世影响深远。
|
||||
|
||||
[输出]
|
||||
[
|
||||
{
|
||||
"source": "曹雪芹",
|
||||
"target": "红楼梦",
|
||||
"description": "曹雪芹是红楼梦的主要作者,创作了前80回",
|
||||
"strength": 10
|
||||
},
|
||||
{
|
||||
"source": "高鹗",
|
||||
"target": "红楼梦",
|
||||
"description": "高鹗是红楼梦后40回的续作者",
|
||||
"strength": 10
|
||||
},
|
||||
{
|
||||
"source": "红楼梦",
|
||||
"target": "石头记",
|
||||
"description": "石头记是红楼梦的别名",
|
||||
"strength": 9
|
||||
},
|
||||
{
|
||||
"source": "红楼梦",
|
||||
"target": "中国文学史",
|
||||
"description": "红楼梦被视为中国文学史中现实主义的高峰之作",
|
||||
"strength": 7
|
||||
},
|
||||
{
|
||||
"source": "贾宝玉",
|
||||
"target": "林黛玉",
|
||||
"description": "贾宝玉与林黛玉有深厚的爱情关系,是小说主线之一",
|
||||
"strength": 8
|
||||
},
|
||||
{
|
||||
"source": "贾宝玉",
|
||||
"target": "薛宝钗",
|
||||
"description": "贾宝玉与薛宝钗的关系是小说爱情悲剧主线的一部分",
|
||||
"strength": 8
|
||||
},
|
||||
{
|
||||
"source": "贾宝玉",
|
||||
"target": "金陵十二钗",
|
||||
"description": "贾宝玉是金陵十二钗故事的中心人物",
|
||||
"strength": 8
|
||||
},
|
||||
{
|
||||
"source": "红楼梦",
|
||||
"target": "贾宝玉",
|
||||
"description": "贾宝玉是红楼梦中的主要角色",
|
||||
"strength": 7
|
||||
},
|
||||
{
|
||||
"source": "红楼梦",
|
||||
"target": "林黛玉",
|
||||
"description": "林黛玉是红楼梦中的主要角色",
|
||||
"strength": 7
|
||||
},
|
||||
{
|
||||
"source": "红楼梦",
|
||||
"target": "薛宝钗",
|
||||
"description": "薛宝钗是红楼梦中的主要角色",
|
||||
"strength": 7
|
||||
},
|
||||
{
|
||||
"source": "红楼梦",
|
||||
"target": "四大家族",
|
||||
"description": "四大家族是红楼梦的背景设定",
|
||||
"strength": 7
|
||||
},
|
||||
{
|
||||
"source": "红楼梦",
|
||||
"target": "金陵十二钗",
|
||||
"description": "金陵十二钗是红楼梦中的重要概念",
|
||||
"strength": 7
|
||||
},
|
||||
{
|
||||
"source": "红楼梦",
|
||||
"target": "乾隆年间",
|
||||
"description": "红楼梦成书于乾隆年间,约1743年前后",
|
||||
"strength": 6
|
||||
}
|
||||
]
|
||||
|
||||
# 知识库配置
|
||||
knowledge_base:
|
||||
chunk_size: 512
|
||||
chunk_overlap: 50
|
||||
split_markers: ["\n\n", "\n", "。"]
|
||||
image_processing:
|
||||
enable_multimodal: true
|
|
@ -205,30 +205,19 @@ conversation:
|
|||
</think>
|
||||
NO_MATCH
|
||||
prompt: |
|
||||
这是用户和助手之间的对话。当用户提出问题时,助手会基于特定的信息进行解答。助手首先在心中思考推理过程,然后向用户提供答案。
|
||||
推理过程用 <think> </think> 标签包围,答案直接输出在think标签后面,即:
|
||||
<think>
|
||||
这里是推理过程
|
||||
</think>
|
||||
这里是答案
|
||||
这是用户和助手之间的对话。当用户提出问题时,助手会基于特定的信息进行解答。直接提供答案,没有思考过程
|
||||
context_template: |
|
||||
你是一个专业的智能信息检索助手,名为小微,犹如专业的高级秘书,依据检索到的信息回答用户问题。
|
||||
当用户提出问题时,助手只能基于给定的信息进行解答,不能利用任何先验知识。
|
||||
|
||||
## 回答问题规则
|
||||
- 仅根据检索到的信息中的事实进行回复,不得运用任何先验知识,保持回应的客观性和准确性。
|
||||
- 复杂问题和答案的按Markdown分结构展示,总述部分不需要拆分
|
||||
- 如果是比较简单的答案,不需要把最终答案拆分的过于细碎
|
||||
- 结果中使用的图片地址必须来自于检索到的信息,不得虚构
|
||||
- 检查结果中的文字和图片是否来自于检索到的信息,如果扩展了不在检索到的信息中的内容,必须进行修改,直到得到最终答案
|
||||
- 如果用户问题无法回答,只输出NO_MATCH即可,即:
|
||||
<think>
|
||||
</think>
|
||||
NO_MATCH
|
||||
- 回答问题仅依据检索到的信息中的事实,不利用任何先验知识。
|
||||
- 复杂问题与答案采用Markdown分结构展示,总述部分不拆分。
|
||||
- 简单答案无需过度拆分
|
||||
- 结果中使用的图片地址必须来自检索到的信息,不得虚构。
|
||||
- 检查文字和图片是否均来自检索到的信息,若扩展了不在其中的内容,需修改至符合要求。
|
||||
- 若用户问题无法回答,只输出“NO_MATCH”。
|
||||
|
||||
## 输出限制
|
||||
- 以Markdown图文格式输出你的最终结果
|
||||
- 输出内容要保证简短且全面,条理清晰,信息明确,不重复。
|
||||
- 以Markdown图文格式输出最终结果。
|
||||
- 输出内容简短全面、条理清晰、信息明确、无重复。
|
||||
|
||||
## 当前时间是:
|
||||
{{.CurrentTime}} {{.CurrentWeek}}
|
||||
|
@ -534,3 +523,16 @@ knowledge_base:
|
|||
split_markers: ["\n\n", "\n", "。"]
|
||||
image_processing:
|
||||
enable_multimodal: true
|
||||
|
||||
#{"chunk_size": 1000, "separators": ["============================= "], "chunk_overlap": 200, "enable_multimodal": true}
|
||||
# 知识库配置
|
||||
init_simple_conf:
|
||||
knowledge_base:
|
||||
chunk_size: 512
|
||||
chunk_overlap: 50
|
||||
split_markers: [ "\n\n", "\n", "。" ]
|
||||
image_processing:
|
||||
enable_multimodal: true
|
||||
emb_model: "6e500e62-63c0-4b87-ae10-4d6f2fa83dd5"
|
||||
chat_model: "3f733012-b496-4025-9ff2-30bf5e0f595c"
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ func prepareChatModel(ctx context.Context, modelService interfaces.ModelService,
|
|||
MaxCompletionTokens: chatManage.SummaryConfig.MaxCompletionTokens,
|
||||
FrequencyPenalty: chatManage.SummaryConfig.FrequencyPenalty,
|
||||
PresencePenalty: chatManage.SummaryConfig.PresencePenalty,
|
||||
Thinking: chatManage.SummaryConfig.Thinking,
|
||||
}
|
||||
|
||||
return chatModel, opt, nil
|
||||
|
|
|
@ -1,27 +1,88 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"knowlege-lsxd/internal/types"
|
||||
|
||||
"fmt"
|
||||
"knowlege-lsxd/internal/application/repository"
|
||||
"knowlege-lsxd/internal/config"
|
||||
"knowlege-lsxd/internal/models/utils/ollama"
|
||||
"knowlege-lsxd/internal/types/interfaces"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type MockKnowledgeBaseRepo struct {
|
||||
mock.Mock
|
||||
interfaces.KnowledgeBaseRepository
|
||||
var Repo = setRepo()
|
||||
|
||||
type repos struct {
|
||||
Chunk interfaces.ChunkRepository
|
||||
KnowlegeBase interfaces.KnowledgeBaseRepository
|
||||
Knowlege interfaces.KnowledgeRepository
|
||||
Model interfaces.ModelRepository
|
||||
Tenants interfaces.TenantRepository
|
||||
}
|
||||
|
||||
func (m *MockKnowledgeBaseRepo) Create(ctx context.Context, kb *types.KnowledgeBase) error {
|
||||
args := m.Called(ctx, kb)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func must(err error) {
|
||||
func setRepo() *repos {
|
||||
config.SetEnv()
|
||||
database, err := InitDatabase(nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &repos{
|
||||
Chunk: repository.NewChunkRepository(database),
|
||||
KnowlegeBase: repository.NewKnowledgeBaseRepository(database),
|
||||
Knowlege: repository.NewKnowledgeRepository(database),
|
||||
Model: repository.NewModelRepository(database),
|
||||
Tenants: repository.NewTenantRepository(database),
|
||||
}
|
||||
}
|
||||
|
||||
// Ollama 函数用于获取 OllamaService 实例
|
||||
// 如果获取过程中出现错误,会通过 panic 抛出异常
|
||||
// 返回值: *ollama.OllamaService - Ollama 服务的实例指针
|
||||
func Ollama() *ollama.OllamaService {
|
||||
// 调用 ollama 包的 GetOllamaService 函数获取服务实例
|
||||
ol, e := ollama.GetOllamaService()
|
||||
if e != nil {
|
||||
panic(e)
|
||||
}
|
||||
return ol
|
||||
}
|
||||
|
||||
func InitDatabase(cfg *config.Config) (*gorm.DB, error) {
|
||||
var dialector gorm.Dialector
|
||||
driver := os.Getenv("DB_DRIVER")
|
||||
switch driver {
|
||||
case "postgres":
|
||||
dsn := fmt.Sprintf(
|
||||
"host=%s port=%s user=%s password=%s dbname=%s sslmode=%s",
|
||||
os.Getenv("DB_HOST"),
|
||||
os.Getenv("DB_PORT"),
|
||||
os.Getenv("DB_USER"),
|
||||
os.Getenv("DB_PASSWORD"),
|
||||
os.Getenv("DB_NAME"),
|
||||
"disable",
|
||||
)
|
||||
dialector = postgres.Open(dsn)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported database driver: %s", os.Getenv("DB_DRIVER"))
|
||||
}
|
||||
db, err := gorm.Open(dialector, &gorm.Config{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get underlying SQL DB object
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Configure connection pool parameters
|
||||
sqlDB.SetMaxIdleConns(10)
|
||||
sqlDB.SetConnMaxLifetime(time.Duration(10) * time.Minute)
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
|
|
@ -3,18 +3,20 @@ package service
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"knowlege-lsxd/internal/config"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"encoding/json"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"knowlege-lsxd/internal/application/service/retriever"
|
||||
"knowlege-lsxd/internal/common"
|
||||
"knowlege-lsxd/internal/logger"
|
||||
"knowlege-lsxd/internal/models/embedding"
|
||||
"knowlege-lsxd/internal/types"
|
||||
"knowlege-lsxd/internal/types/interfaces"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// ErrInvalidTenantID represents an error for invalid tenant ID
|
||||
|
@ -29,7 +31,9 @@ type knowledgeBaseService struct {
|
|||
}
|
||||
|
||||
// NewKnowledgeBaseService creates a new knowledge base service
|
||||
func NewKnowledgeBaseService(repo interfaces.KnowledgeBaseRepository,
|
||||
func NewKnowledgeBaseService(
|
||||
cfg *config.Config,
|
||||
repo interfaces.KnowledgeBaseRepository,
|
||||
kgRepo interfaces.KnowledgeRepository,
|
||||
chunkRepo interfaces.ChunkRepository,
|
||||
modelService interfaces.ModelService,
|
||||
|
@ -258,13 +262,23 @@ func (s *knowledgeBaseService) CopyKnowledgeBase(ctx context.Context,
|
|||
return sourceKB, targetKB, nil
|
||||
}
|
||||
|
||||
// HybridSearch performs hybrid search, including vector retrieval and keyword retrieval
|
||||
// HybridSearch 执行混合搜索,结合向量和关键词检索方法
|
||||
// 参数:
|
||||
// - ctx: 上下文,用于传递请求信息和控制执行
|
||||
// - id: 知识库ID
|
||||
// - params: 搜索参数,包含查询文本、匹配数量等
|
||||
//
|
||||
// 返回值:
|
||||
// - []*types.SearchResult: 搜索结果列表
|
||||
// - error: 错误信息
|
||||
func (s *knowledgeBaseService) HybridSearch(ctx context.Context,
|
||||
id string,
|
||||
params types.SearchParams,
|
||||
) ([]*types.SearchResult, error) {
|
||||
// 记录混合搜索的参数信息,包括知识库ID和查询文本
|
||||
logger.Infof(ctx, "Hybrid search parameters, knowledge base ID: %s, query text: %s", id, params.QueryText)
|
||||
|
||||
// 从上下文中获取租户信息
|
||||
tenantInfo := ctx.Value(types.TenantInfoContextKey).(*types.Tenant)
|
||||
logger.Infof(ctx, "Creating composite retrieval engine, tenant ID: %d", tenantInfo.ID)
|
||||
|
||||
|
@ -374,43 +388,56 @@ func (s *knowledgeBaseService) HybridSearch(ctx context.Context,
|
|||
return s.processSearchResults(ctx, deduplicatedChunks)
|
||||
}
|
||||
|
||||
// processSearchResults handles the processing of search results, optimizing database queries
|
||||
// processSearchResults 处理搜索结果的方法
|
||||
// 参数:
|
||||
//
|
||||
// ctx - 上下文,用于传递请求相关的信息和控制请求的生命周期
|
||||
// chunks - 带有分数的索引块列表
|
||||
//
|
||||
// 返回值:
|
||||
//
|
||||
// []*types.SearchResult - 处理后的搜索结果列表
|
||||
// error - 处理过程中可能出现的错误
|
||||
func (s *knowledgeBaseService) processSearchResults(ctx context.Context,
|
||||
chunks []*types.IndexWithScore) ([]*types.SearchResult, error) {
|
||||
// 如果输入的块列表为空,直接返回nil
|
||||
|
||||
if len(chunks) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
// 从上下文中获取租户ID
|
||||
|
||||
tenantID := ctx.Value(types.TenantIDContextKey).(uint)
|
||||
|
||||
// Prepare data structures for efficient processing
|
||||
var knowledgeIDs []string
|
||||
var chunkIDs []string
|
||||
chunkScores := make(map[string]float64)
|
||||
chunkMatchTypes := make(map[string]types.MatchType)
|
||||
processedKnowledgeIDs := make(map[string]bool)
|
||||
// 初始化必要的变量
|
||||
var knowledgeIDs []string // 知识ID列表
|
||||
var chunkIDs []string // 块ID列表
|
||||
chunkScores := make(map[string]float64) // 块ID到分数的映射
|
||||
chunkMatchTypes := make(map[string]types.MatchType) // 块ID到匹配类型的映射
|
||||
processedKnowledgeIDs := make(map[string]bool) // 已处理的知识ID集合
|
||||
|
||||
// Collect all knowledge and chunk IDs
|
||||
// 遍历处理输入的块列表
|
||||
for _, chunk := range chunks {
|
||||
// 如果知识ID未处理过,则添加到知识ID列表
|
||||
if !processedKnowledgeIDs[chunk.KnowledgeID] {
|
||||
knowledgeIDs = append(knowledgeIDs, chunk.KnowledgeID)
|
||||
processedKnowledgeIDs[chunk.KnowledgeID] = true
|
||||
}
|
||||
|
||||
// 添加块ID到块ID列表,并记录分数和匹配类型
|
||||
chunkIDs = append(chunkIDs, chunk.ChunkID)
|
||||
chunkScores[chunk.ChunkID] = chunk.Score
|
||||
chunkMatchTypes[chunk.ChunkID] = chunk.MatchType
|
||||
}
|
||||
|
||||
// Batch fetch knowledge data
|
||||
// 获取知识数据
|
||||
logger.Infof(ctx, "Fetching knowledge data for %d IDs", len(knowledgeIDs))
|
||||
knowledgeMap, err := s.fetchKnowledgeData(ctx, tenantID, knowledgeIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Batch fetch all chunks in one go
|
||||
// 获取块数据
|
||||
logger.Infof(ctx, "Fetching chunk data for %d IDs", len(chunkIDs))
|
||||
allChunks, err := s.chunkRepo.ListChunksByID(ctx, tenantID, chunkIDs)
|
||||
if err != nil {
|
||||
|
@ -421,34 +448,33 @@ func (s *knowledgeBaseService) processSearchResults(ctx context.Context,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// Build chunk map and collect additional IDs to fetch
|
||||
// 创建块映射并处理相关块
|
||||
chunkMap := make(map[string]*types.Chunk, len(allChunks))
|
||||
var additionalChunkIDs []string
|
||||
processedChunkIDs := make(map[string]bool)
|
||||
|
||||
// First pass: Build chunk map and collect parent IDs
|
||||
for _, chunk := range allChunks {
|
||||
// 将块添加到块映射中
|
||||
chunkMap[chunk.ID] = chunk
|
||||
processedChunkIDs[chunk.ID] = true
|
||||
|
||||
// Collect parent chunks
|
||||
// 处理父块
|
||||
if chunk.ParentChunkID != "" && !processedChunkIDs[chunk.ParentChunkID] {
|
||||
additionalChunkIDs = append(additionalChunkIDs, chunk.ParentChunkID)
|
||||
processedChunkIDs[chunk.ParentChunkID] = true
|
||||
|
||||
// Pass score to parent
|
||||
chunkScores[chunk.ParentChunkID] = chunkScores[chunk.ID]
|
||||
chunkMatchTypes[chunk.ParentChunkID] = types.MatchTypeParentChunk
|
||||
}
|
||||
|
||||
// Collect related chunks
|
||||
// 处理相关块
|
||||
relationChunkIDs := s.collectRelatedChunkIDs(chunk, processedChunkIDs)
|
||||
for _, chunkID := range relationChunkIDs {
|
||||
additionalChunkIDs = append(additionalChunkIDs, chunkID)
|
||||
chunkMatchTypes[chunkID] = types.MatchTypeRelationChunk
|
||||
}
|
||||
|
||||
// Add nearby chunks (prev and next)
|
||||
// 处理文本类型块的相邻块
|
||||
if chunk.ChunkType == types.ChunkTypeText {
|
||||
if chunk.NextChunkID != "" && !processedChunkIDs[chunk.NextChunkID] {
|
||||
additionalChunkIDs = append(additionalChunkIDs, chunk.NextChunkID)
|
||||
|
@ -463,33 +489,37 @@ func (s *knowledgeBaseService) processSearchResults(ctx context.Context,
|
|||
}
|
||||
}
|
||||
|
||||
// Fetch all additional chunks in one go if needed
|
||||
// 获取额外的块数据
|
||||
if len(additionalChunkIDs) > 0 {
|
||||
logger.Infof(ctx, "Fetching %d additional chunks", len(additionalChunkIDs))
|
||||
additionalChunks, err := s.chunkRepo.ListChunksByID(ctx, tenantID, additionalChunkIDs)
|
||||
if err != nil {
|
||||
logger.Warnf(ctx, "Failed to fetch some additional chunks: %v", err)
|
||||
// Continue with what we have
|
||||
|
||||
} else {
|
||||
// Add to chunk map
|
||||
|
||||
// 将获取到的额外块添加到块映射中
|
||||
for _, chunk := range additionalChunks {
|
||||
chunkMap[chunk.ID] = chunk
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build final search results
|
||||
// 构建搜索结果
|
||||
var searchResults []*types.SearchResult
|
||||
for chunkID, chunk := range chunkMap {
|
||||
// 检查是否为有效的文本块
|
||||
if !s.isValidTextChunk(chunk) {
|
||||
continue
|
||||
}
|
||||
|
||||
// 获取块的分数
|
||||
score, hasScore := chunkScores[chunkID]
|
||||
if !hasScore || score <= 0 {
|
||||
score = 0.0
|
||||
}
|
||||
|
||||
// 构建搜索结果
|
||||
if knowledge, ok := knowledgeMap[chunk.KnowledgeID]; ok {
|
||||
matchType := types.MatchTypeParentChunk
|
||||
if specificType, exists := chunkMatchTypes[chunkID]; exists {
|
||||
|
|
|
@ -1,37 +1,18 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"knowlege-lsxd/internal/container"
|
||||
"knowlege-lsxd/internal/runtime"
|
||||
"knowlege-lsxd/internal/types"
|
||||
"knowlege-lsxd/internal/types/interfaces"
|
||||
)
|
||||
|
||||
func Test_CreateKnowledge(t *testing.T) {
|
||||
// 1. 构建容器
|
||||
container := container.BuildContainer(runtime.GetContainer())
|
||||
|
||||
// 2. Mock 依赖
|
||||
mockKBRepo := new(MockKnowledgeBaseRepo)
|
||||
must(container.Provide(func() interfaces.KnowledgeBaseRepository {
|
||||
return mockKBRepo
|
||||
}))
|
||||
|
||||
// 3. 解析服务(自动注入 Mock 的 KnowledgeBaseRepository)
|
||||
var service *knowledgeBaseService
|
||||
must(container.Invoke(func(k *knowledgeBaseService) {
|
||||
service = k
|
||||
}))
|
||||
|
||||
// 4. 设置 Mock 预期
|
||||
mockKBRepo.On("Create", context.Background(), mock.Anything).Return(nil)
|
||||
|
||||
// 5. 调用并断言
|
||||
base, err := service.CreateKnowledgeBase(context.Background(), &types.KnowledgeBase{Name: "Test"})
|
||||
assert.NoError(t, err, base)
|
||||
mockKBRepo.AssertExpectations(t)
|
||||
func KnowledgeBase() interfaces.KnowledgeBaseService {
|
||||
return NewKnowledgeBaseService(Repo.KnowlegeBase, Repo.Knowlege, Repo.Chunk, Model())
|
||||
}
|
||||
|
||||
func Test_CreateKnowledge(t *testing.T) {
|
||||
res, err := KnowledgeBase().CreateKnowledgeBase(context.Background(), &types.KnowledgeBase{})
|
||||
t.Log(res, err)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"knowlege-lsxd/internal/types/interfaces"
|
||||
)
|
||||
|
||||
func Model() interfaces.ModelService {
|
||||
|
||||
return NewModelService(Repo.Model, Ollama())
|
||||
}
|
||||
|
||||
func a() {
|
||||
s := `## 回答问题规则\n - 回答问题仅依据检索到的信息中的事实,不利用任何先验知识。\n - 复杂问题与答案采用Markdown分结构展示,总述部分不拆分。\n - 简单答案无需过度拆分\n - 结果中使用的图片地址必须来自检索到的信息,不得虚构。\n - 检查文字和图片是否均来自检索到的信息,若扩展了不在其中的内容,需修改至符合要求。\n - 若用户问题无法回答,只输出“NO_MATCH”。\n\n ## 输出限制\n - 以Markdown图文格式输出最终结果。\n - 输出内容简短全面、条理清晰、信息明确、无重复。\n\n ## 当前时间是:\n {{.CurrentTime}} {{.CurrentWeek}}\n\n ## 检索到的信息如下:\n ------BEGIN------\n {{range .Contexts}}\n {{.}}\n {{end}}\n ------END------\n\n ## 用户当前的问题是:\n {{.Query}}`
|
||||
|
||||
}
|
|
@ -5,8 +5,6 @@ import (
|
|||
"errors"
|
||||
"strings"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
chatpipline "knowlege-lsxd/internal/application/service/chat_pipline"
|
||||
"knowlege-lsxd/internal/config"
|
||||
"knowlege-lsxd/internal/logger"
|
||||
|
@ -14,6 +12,9 @@ import (
|
|||
"knowlege-lsxd/internal/tracing"
|
||||
"knowlege-lsxd/internal/types"
|
||||
"knowlege-lsxd/internal/types/interfaces"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
)
|
||||
|
||||
// sessionService implements the SessionService interface for managing conversation sessions
|
||||
|
@ -309,8 +310,6 @@ func (s *sessionService) GenerateTitle(ctx context.Context,
|
|||
func (s *sessionService) KnowledgeQA(ctx context.Context, sessionID, query string) (
|
||||
[]*types.SearchResult, <-chan types.StreamResponse, error,
|
||||
) {
|
||||
logger.Info(ctx, "Start knowledge base question answering")
|
||||
logger.Infof(ctx, "Knowledge base question answering parameters, session ID: %s, query: %s", sessionID, query)
|
||||
|
||||
// Get tenant ID from context
|
||||
tenantID := ctx.Value(types.TenantIDContextKey).(uint)
|
||||
|
@ -375,6 +374,70 @@ func (s *sessionService) KnowledgeQA(ctx context.Context, sessionID, query strin
|
|||
return chatManage.MergeResult, chatManage.ResponseChan, nil
|
||||
}
|
||||
|
||||
// KnowledgeQAQuick performs knowledge base question answering with LLM summarization
|
||||
func (s *sessionService) KnowledgeQAQuick(ctx context.Context, sessionID, query string) (
|
||||
[]*types.SearchResult, string, error,
|
||||
) {
|
||||
|
||||
// Get tenant ID from context
|
||||
tenantID := ctx.Value(types.TenantIDContextKey).(uint)
|
||||
logger.Infof(ctx, "Getting session info, session ID: %s, tenant ID: %d", sessionID, tenantID)
|
||||
|
||||
// Get session information
|
||||
session, err := s.sessionRepo.Get(ctx, tenantID, sessionID)
|
||||
if err != nil {
|
||||
logger.Errorf(ctx, "Failed to get session, session ID: %s, error: %v", sessionID, err)
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
// Validate knowledge base association
|
||||
if session.KnowledgeBaseID == "" {
|
||||
logger.Warnf(ctx, "Session has no associated knowledge base, session ID: %s", sessionID)
|
||||
return nil, "", errors.New("session has no knowledge base")
|
||||
}
|
||||
chatManage := &types.ChatManage{
|
||||
Query: query,
|
||||
RewriteQuery: query,
|
||||
SessionID: sessionID,
|
||||
KnowledgeBaseID: session.KnowledgeBaseID,
|
||||
VectorThreshold: session.VectorThreshold,
|
||||
KeywordThreshold: session.KeywordThreshold,
|
||||
EmbeddingTopK: session.EmbeddingTopK,
|
||||
RerankModelID: session.RerankModelID,
|
||||
RerankTopK: session.RerankTopK,
|
||||
RerankThreshold: session.RerankThreshold,
|
||||
ChatModelID: session.SummaryModelID,
|
||||
SummaryConfig: types.SummaryConfig{
|
||||
MaxTokens: session.SummaryParameters.MaxTokens,
|
||||
RepeatPenalty: session.SummaryParameters.RepeatPenalty,
|
||||
TopK: session.SummaryParameters.TopK,
|
||||
TopP: session.SummaryParameters.TopP,
|
||||
FrequencyPenalty: session.SummaryParameters.FrequencyPenalty,
|
||||
PresencePenalty: session.SummaryParameters.PresencePenalty,
|
||||
Prompt: session.SummaryParameters.Prompt,
|
||||
ContextTemplate: session.SummaryParameters.ContextTemplate,
|
||||
Temperature: session.SummaryParameters.Temperature,
|
||||
Seed: session.SummaryParameters.Seed,
|
||||
NoMatchPrefix: session.SummaryParameters.NoMatchPrefix,
|
||||
MaxCompletionTokens: session.SummaryParameters.MaxCompletionTokens,
|
||||
Thinking: session.SummaryParameters.Thinking,
|
||||
},
|
||||
FallbackResponse: session.FallbackResponse,
|
||||
}
|
||||
|
||||
err = s.KnowledgeQAByEvent(ctx, chatManage, types.Pipline["rag"])
|
||||
if err != nil {
|
||||
logger.ErrorWithFields(ctx, err, map[string]interface{}{
|
||||
"session_id": sessionID,
|
||||
"knowledge_base_id": session.KnowledgeBaseID,
|
||||
})
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
logger.Info(ctx, "Knowledge base question answering completed")
|
||||
return chatManage.MergeResult, chatManage.ChatResponse.Content, nil
|
||||
}
|
||||
|
||||
// KnowledgeQAByEvent processes knowledge QA through a series of events in the pipeline
|
||||
func (s *sessionService) KnowledgeQAByEvent(ctx context.Context,
|
||||
chatManage *types.ChatManage, eventList []types.EventType,
|
||||
|
|
|
@ -26,6 +26,13 @@ type Config struct {
|
|||
VectorDatabase *VectorDatabaseConfig `yaml:"vector_database" json:"vector_database"`
|
||||
DocReader *DocReaderConfig `yaml:"docreader" json:"docreader"`
|
||||
StreamManager *StreamManagerConfig `yaml:"stream_manager" json:"stream_manager"`
|
||||
InitSimpleConf *InitSimpleConf `yaml:"init_simple_conf" json:"init_simple_conf"`
|
||||
}
|
||||
|
||||
type InitSimpleConf struct {
|
||||
EmbModel string `yaml:"emb_model" json:"emb_model"`
|
||||
ChatModel string `yaml:"chat_model" json:"chat_model"`
|
||||
KnowledgeBase *KnowledgeBaseConfig `yaml:"knowledge_base" json:"knowledge_base"`
|
||||
}
|
||||
|
||||
type DocReaderConfig struct {
|
||||
|
|
|
@ -55,7 +55,7 @@ func BuildContainer(container *dig.Container) *dig.Container {
|
|||
// Core infrastructure configuration
|
||||
must(container.Provide(config.LoadConfig))
|
||||
must(container.Provide(initTracer))
|
||||
must(container.Provide(initDatabase))
|
||||
must(container.Provide(InitDatabase))
|
||||
must(container.Provide(initFileService))
|
||||
must(container.Provide(initAntsPool))
|
||||
|
||||
|
@ -146,7 +146,7 @@ func initTracer() (*tracing.Tracer, error) {
|
|||
return tracing.InitTracer()
|
||||
}
|
||||
|
||||
// initDatabase initializes database connection
|
||||
// InitDatabase initializes database connection
|
||||
// Creates and configures database connection based on environment configuration
|
||||
// Supports multiple database backends (PostgreSQL)
|
||||
// Parameters:
|
||||
|
@ -155,7 +155,7 @@ func initTracer() (*tracing.Tracer, error) {
|
|||
// Returns:
|
||||
// - Configured database connection
|
||||
// - Error if connection fails
|
||||
func initDatabase(cfg *config.Config) (*gorm.DB, error) {
|
||||
func InitDatabase(cfg *config.Config) (*gorm.DB, error) {
|
||||
var dialector gorm.Dialector
|
||||
driver := os.Getenv("DB_DRIVER")
|
||||
switch driver {
|
||||
|
|
|
@ -13,9 +13,6 @@ import (
|
|||
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/ollama/ollama/api"
|
||||
"knowlege-lsxd/internal/config"
|
||||
"knowlege-lsxd/internal/errors"
|
||||
"knowlege-lsxd/internal/logger"
|
||||
|
@ -25,6 +22,10 @@ import (
|
|||
"knowlege-lsxd/internal/types/interfaces"
|
||||
"knowlege-lsxd/services/docreader/src/client"
|
||||
"knowlege-lsxd/services/docreader/src/proto"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/ollama/ollama/api"
|
||||
)
|
||||
|
||||
// DownloadTask 下载任务信息
|
||||
|
@ -250,6 +251,7 @@ func (h *InitializationHandler) Initialize(c *gin.Context) {
|
|||
c.Error(errors.NewBadRequestError("分块重叠大小必须小于分块大小"))
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.DocumentSplitting.Separators) == 0 {
|
||||
logger.Error(ctx, "Document separators cannot be empty")
|
||||
c.Error(errors.NewBadRequestError("文档分隔符不能为空"))
|
||||
|
|
|
@ -7,11 +7,12 @@ import (
|
|||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"knowlege-lsxd/internal/errors"
|
||||
"knowlege-lsxd/internal/logger"
|
||||
"knowlege-lsxd/internal/types"
|
||||
"knowlege-lsxd/internal/types/interfaces"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// KnowledgeHandler processes HTTP requests related to knowledge resources
|
||||
|
@ -82,8 +83,12 @@ func (h *KnowledgeHandler) handleDuplicateKnowledgeError(c *gin.Context,
|
|||
}
|
||||
|
||||
// CreateKnowledgeFromFile handles requests to create knowledge from an uploaded file
|
||||
/**
|
||||
* 从文件创建知识的处理函数
|
||||
* @param c Gin上下文对象,包含请求和响应信息
|
||||
*/
|
||||
func (h *KnowledgeHandler) CreateKnowledgeFromFile(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
ctx := c.Request.Context() // 获取请求上下文 // 记录开始创建知识的日志
|
||||
logger.Info(ctx, "Start creating knowledge from file")
|
||||
|
||||
// Validate access to the knowledge base
|
||||
|
|
|
@ -1,27 +1,33 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"knowlege-lsxd/internal/config"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"knowlege-lsxd/internal/errors"
|
||||
"knowlege-lsxd/internal/logger"
|
||||
"knowlege-lsxd/internal/types"
|
||||
"knowlege-lsxd/internal/types/interfaces"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// KnowledgeBaseHandler defines the HTTP handler for knowledge base operations
|
||||
type KnowledgeBaseHandler struct {
|
||||
service interfaces.KnowledgeBaseService
|
||||
knowledgeService interfaces.KnowledgeService
|
||||
kbService interfaces.KnowledgeBaseService
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
// NewKnowledgeBaseHandler creates a new knowledge base handler instance
|
||||
func NewKnowledgeBaseHandler(
|
||||
cfg *config.Config,
|
||||
service interfaces.KnowledgeBaseService,
|
||||
knowledgeService interfaces.KnowledgeService,
|
||||
kbService interfaces.KnowledgeBaseService,
|
||||
) *KnowledgeBaseHandler {
|
||||
return &KnowledgeBaseHandler{service: service, knowledgeService: knowledgeService}
|
||||
return &KnowledgeBaseHandler{service: service, knowledgeService: knowledgeService, kbService: kbService, cfg: cfg}
|
||||
}
|
||||
|
||||
// HybridSearch handles requests to perform hybrid vector and keyword search on a knowledge base
|
||||
|
@ -63,6 +69,54 @@ func (h *KnowledgeBaseHandler) HybridSearch(c *gin.Context) {
|
|||
})
|
||||
}
|
||||
|
||||
// InitSimple 快速初始化知识库
|
||||
func (h *KnowledgeBaseHandler) InitSimple(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
logger.Info(ctx, "Starting InitSimple")
|
||||
|
||||
var req types.InitSimpleRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
logger.Error(ctx, "Failed to parse request parameters", err)
|
||||
c.Error(errors.NewBadRequestError("Invalid request parameters").WithDetails(err.Error()))
|
||||
return
|
||||
}
|
||||
//检查租户
|
||||
tenantID, exists := c.Get(types.TenantIDContextKey.String())
|
||||
if !exists {
|
||||
logger.Error(ctx, "Failed to get tenant ID")
|
||||
c.Error(errors.NewBadRequestError("Invalid request parameters"))
|
||||
return
|
||||
}
|
||||
|
||||
kb := &types.KnowledgeBase{
|
||||
Name: req.Name,
|
||||
Description: req.Description,
|
||||
TenantID: tenantID.(uint),
|
||||
ChunkingConfig: types.ChunkingConfig{
|
||||
ChunkSize: h.cfg.InitSimpleConf.KnowledgeBase.ChunkSize,
|
||||
ChunkOverlap: h.cfg.InitSimpleConf.KnowledgeBase.ChunkOverlap,
|
||||
Separators: h.cfg.InitSimpleConf.KnowledgeBase.SplitMarkers,
|
||||
EnableMultimodal: h.cfg.InitSimpleConf.KnowledgeBase.ImageProcessing.EnableMultimodal,
|
||||
},
|
||||
EmbeddingModelID: h.cfg.InitSimpleConf.EmbModel,
|
||||
SummaryModelID: h.cfg.InitSimpleConf.ChatModel,
|
||||
RerankModelID: "",
|
||||
VLMModelID: "",
|
||||
}
|
||||
// 处理知识库 - 检查是否存在,不存在则创建,存在则更新
|
||||
kbc, err := h.kbService.CreateKnowledgeBase(ctx, kb)
|
||||
if err != nil {
|
||||
logger.ErrorWithFields(ctx, err, nil)
|
||||
c.Error(errors.NewInternalServerError(err.Error()))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusCreated, gin.H{
|
||||
"success": true,
|
||||
"data": kbc,
|
||||
})
|
||||
}
|
||||
|
||||
// CreateKnowledgeBase handles requests to create a new knowledge base
|
||||
func (h *KnowledgeBaseHandler) CreateKnowledgeBase(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
|
|
@ -7,13 +7,14 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"knowlege-lsxd/internal/application/service"
|
||||
"knowlege-lsxd/internal/config"
|
||||
"knowlege-lsxd/internal/errors"
|
||||
"knowlege-lsxd/internal/logger"
|
||||
"knowlege-lsxd/internal/types"
|
||||
"knowlege-lsxd/internal/types/interfaces"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// SessionHandler handles all HTTP requests related to conversation sessions
|
||||
|
@ -784,6 +785,7 @@ func (h *SessionHandler) KnowledgeQA(c *gin.Context) {
|
|||
logger.GetLogger(ctx).Error("Complete stream failed", "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
for response := range respCh {
|
||||
response.ID = requestID
|
||||
c.SSEvent("message", response)
|
||||
|
@ -801,6 +803,65 @@ func (h *SessionHandler) KnowledgeQA(c *gin.Context) {
|
|||
}()
|
||||
}
|
||||
|
||||
func (h *SessionHandler) KnowledgeQuick(c *gin.Context) {
|
||||
ctx := logger.CloneContext(c.Request.Context())
|
||||
sessionID := c.Param("session_id")
|
||||
if sessionID == "" {
|
||||
c.Error(errors.NewBadRequestError(errors.ErrInvalidSessionID.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
var request CreateKnowledgeQARequest
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
c.Error(errors.NewBadRequestError(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
assistantMessage := &types.Message{
|
||||
SessionID: sessionID,
|
||||
Role: "assistant",
|
||||
RequestID: c.GetString(types.RequestIDContextKey.String()),
|
||||
IsCompleted: false,
|
||||
}
|
||||
defer h.completeAssistantMessage(ctx, assistantMessage)
|
||||
|
||||
if request.Query == "" {
|
||||
c.Error(errors.NewBadRequestError("Query content cannot be empty"))
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := h.messageService.CreateMessage(ctx, &types.Message{
|
||||
SessionID: sessionID,
|
||||
Role: "user",
|
||||
Content: request.Query,
|
||||
RequestID: c.GetString(types.RequestIDContextKey.String()),
|
||||
CreatedAt: time.Now(),
|
||||
IsCompleted: true,
|
||||
}); err != nil {
|
||||
c.Error(errors.NewInternalServerError(err.Error()))
|
||||
return
|
||||
}
|
||||
assistantMessage.CreatedAt = time.Now()
|
||||
if _, err := h.messageService.CreateMessage(ctx, assistantMessage); err != nil {
|
||||
c.Error(errors.NewInternalServerError(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
_, resp, err := h.sessionService.KnowledgeQAQuick(ctx, sessionID, request.Query)
|
||||
if err != nil {
|
||||
logger.ErrorWithFields(ctx, err, nil)
|
||||
c.Error(errors.NewInternalServerError(err.Error()))
|
||||
return
|
||||
}
|
||||
requestID := c.GetString(types.RequestIDContextKey.String())
|
||||
c.JSON(http.StatusOK, &types.QuickResponse{
|
||||
ID: requestID,
|
||||
ResponseType: types.ResponseTypeAnswer,
|
||||
Content: resp,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// completeAssistantMessage marks an assistant message as complete and updates it
|
||||
func (h *SessionHandler) completeAssistantMessage(ctx context.Context, assistantMessage *types.Message) {
|
||||
assistantMessage.UpdatedAt = time.Now()
|
||||
|
|
|
@ -5,9 +5,10 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
ollamaapi "github.com/ollama/ollama/api"
|
||||
"knowlege-lsxd/internal/logger"
|
||||
"knowlege-lsxd/internal/models/utils/ollama"
|
||||
|
||||
ollamaapi "github.com/ollama/ollama/api"
|
||||
)
|
||||
|
||||
// OllamaEmbedder implements text vectorization functionality using Ollama
|
||||
|
@ -79,7 +80,14 @@ func (e *OllamaEmbedder) Embed(ctx context.Context, text string) ([]float32, err
|
|||
return embedding[0], nil
|
||||
}
|
||||
|
||||
// BatchEmbed converts multiple texts to vectors in batch
|
||||
// BatchEmbed 为文本列表批量生成嵌入向量
|
||||
// 参数:
|
||||
// - ctx: 上下文信息,用于控制请求的生命周期
|
||||
// - texts: 需要生成嵌入向量的文本列表
|
||||
//
|
||||
// 返回值:
|
||||
// - [][]float32: 生成的嵌入向量列表,每个文本对应一个向量
|
||||
// - error: 错误信息,如果成功则为nil
|
||||
func (e *OllamaEmbedder) BatchEmbed(ctx context.Context, texts []string) ([][]float32, error) {
|
||||
// Ensure model is available
|
||||
if err := e.ensureModelAvailable(ctx); err != nil {
|
||||
|
|
|
@ -9,8 +9,9 @@ import (
|
|||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/ollama/ollama/api"
|
||||
"knowlege-lsxd/internal/logger"
|
||||
|
||||
"github.com/ollama/ollama/api"
|
||||
)
|
||||
|
||||
// OllamaService manages Ollama service
|
||||
|
@ -27,7 +28,7 @@ func GetOllamaService() (*OllamaService, error) {
|
|||
// Get Ollama base URL from environment variable, if not set use provided baseURL or default value
|
||||
logger.GetLogger(context.Background()).Infof("Ollama base URL: %s", os.Getenv("OLLAMA_BASE_URL"))
|
||||
baseURL := "http://localhost:11434"
|
||||
envURL := os.Getenv("OLLAMA_BASE_URL")
|
||||
envURL := "" //os.Getenv("OLLAMA_BASE_URL")
|
||||
if envURL != "" {
|
||||
baseURL = envURL
|
||||
}
|
||||
|
|
|
@ -66,7 +66,6 @@ func NewRouter(params RouterParams) *gin.Engine {
|
|||
// 初始化接口(不需要认证)
|
||||
r.GET("/api/v1/initialization/status", params.InitializationHandler.CheckStatus)
|
||||
r.GET("/api/v1/initialization/config", params.InitializationHandler.GetCurrentConfig)
|
||||
r.POST("/api/v1/initialization/initialize", params.InitializationHandler.Initialize)
|
||||
|
||||
// Ollama相关接口(不需要认证)
|
||||
r.GET("/api/v1/initialization/ollama/status", params.InitializationHandler.CheckOllamaStatus)
|
||||
|
@ -151,6 +150,7 @@ func RegisterKnowledgeBaseRoutes(r *gin.RouterGroup, handler *handler.KnowledgeB
|
|||
// 知识库路由组
|
||||
kb := r.Group("/knowledge-bases")
|
||||
{
|
||||
kb.POST("/initialize", handler.InitSimple)
|
||||
// 创建知识库
|
||||
kb.POST("", handler.CreateKnowledgeBase)
|
||||
// 获取知识库列表
|
||||
|
@ -200,6 +200,7 @@ func RegisterChatRoutes(r *gin.RouterGroup, handler *handler.SessionHandler) {
|
|||
knowledgeChat := r.Group("/knowledge-chat")
|
||||
{
|
||||
knowledgeChat.POST("/:session_id", handler.KnowledgeQA)
|
||||
knowledgeChat.POST("/:session_id/quick", handler.KnowledgeQuick)
|
||||
}
|
||||
|
||||
// 新增知识检索接口,不需要session_id
|
||||
|
@ -214,6 +215,7 @@ func RegisterTenantRoutes(r *gin.RouterGroup, handler *handler.TenantHandler) {
|
|||
// 租户路由组
|
||||
tenantRoutes := r.Group("/tenants")
|
||||
{
|
||||
|
||||
tenantRoutes.POST("", handler.CreateTenant)
|
||||
tenantRoutes.GET("/:id", handler.GetTenant)
|
||||
tenantRoutes.PUT("/:id", handler.UpdateTenant)
|
||||
|
|
|
@ -43,6 +43,16 @@ type StreamResponse struct {
|
|||
KnowledgeReferences References `json:"knowledge_references"`
|
||||
}
|
||||
|
||||
// QuickResponse quick response
|
||||
type QuickResponse struct {
|
||||
// Unique identifier
|
||||
ID string `json:"id"`
|
||||
// Response type
|
||||
ResponseType ResponseType `json:"response_type"`
|
||||
// Current fragment content
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
// References references
|
||||
type References []*SearchResult
|
||||
|
||||
|
|
|
@ -26,6 +26,9 @@ type SessionService interface {
|
|||
KnowledgeQA(ctx context.Context,
|
||||
sessionID, query string,
|
||||
) ([]*types.SearchResult, <-chan types.StreamResponse, error)
|
||||
KnowledgeQAQuick(ctx context.Context,
|
||||
sessionID, query string,
|
||||
) ([]*types.SearchResult, string, error)
|
||||
// KnowledgeQAByEvent performs knowledge-based question answering by event
|
||||
KnowledgeQAByEvent(ctx context.Context, chatManage *types.ChatManage, eventList []types.EventType) error
|
||||
// SearchKnowledge performs knowledge-based search, without summarization
|
||||
|
|
|
@ -46,6 +46,15 @@ type KnowledgeBase struct {
|
|||
DeletedAt gorm.DeletedAt `yaml:"deleted_at" json:"deleted_at" gorm:"index"`
|
||||
}
|
||||
|
||||
// initSimple
|
||||
type InitSimpleRequest struct {
|
||||
|
||||
// Name of the knowledge base
|
||||
Name string `yaml:"name" json:"name"`
|
||||
// Description of the knowledge base
|
||||
Description string `yaml:"description" json:"description"`
|
||||
}
|
||||
|
||||
// KnowledgeBaseConfig represents the knowledge base configuration
|
||||
type KnowledgeBaseConfig struct {
|
||||
// Chunking configuration
|
||||
|
|
|
@ -42,6 +42,8 @@ type SummaryConfig struct {
|
|||
Seed int `json:"seed"`
|
||||
// Max completion tokens
|
||||
MaxCompletionTokens int `json:"max_completion_tokens"`
|
||||
// Think
|
||||
Thinking *bool `json:"thinking"`
|
||||
}
|
||||
|
||||
// Session represents the session
|
||||
|
|
Loading…
Reference in New Issue