[+]删除链路追踪,修改项目名称
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