feat: use language-neutral English prompts for multilingual LLM responses

Convert all LLM-facing prompts, templates, and tool output labels to
language-neutral English so the model responds in the user's input
language. Update frontend relevance/match-type mappings to match.

Output language strategy:
- User-facing: reply in user's input language
- Vector-indexed: match source document language
- LLM-internal: fixed English
This commit is contained in:
ochan.kwon
2026-03-06 20:24:23 +09:00
committed by lyingbug
parent db7cef5f17
commit edcf7116cd
28 changed files with 819 additions and 841 deletions
+336 -382
View File
@@ -1,9 +1,9 @@
# 服务器配置
# Server configuration
server:
port: 8080
host: "0.0.0.0"
# 对话服务配置
# Conversation service configuration
conversation:
max_rounds: 5
keyword_threshold: 0.3
@@ -12,121 +12,124 @@ conversation:
rerank_threshold: 0.3
rerank_top_k: 30
fallback_strategy: "model"
fallback_response: "抱歉,我无法回答这个问题。"
fallback_response: "Sorry, I am unable to answer this question."
fallback_prompt: |
你是一个专业、友好的AI助手。请根据你的知识直接回答用户的问题。
You are a professional and friendly AI assistant. Please answer the user's question based on your knowledge.
## 回复要求
- 直接回答用户的问题
- 简洁清晰,言之有物
- 如果涉及实时数据或个人隐私信息,诚实说明无法获取
- 使用礼貌、专业的语气
## Response Requirements
- Answer the user's question directly
- Be concise, clear, and substantive
- If real-time data or personal privacy information is involved, honestly state that it cannot be obtained
- Use a polite and professional tone
- IMPORTANT: Always respond in the same language as the user's question
## 用户的问题是:
## User's question:
{{query}}
enable_rewrite: true
enable_query_expansion: true
enable_rerank: true
rewrite_prompt_system: |
你是一个专注于指代消解和省略补全的智能助手,你的任务是根据历史对话上下文,清晰识别用户问题中的代词并替换为明确的主语,同时补全省略的关键信息。
You are an intelligent assistant specialized in coreference resolution and ellipsis completion. Your task is to clearly identify pronouns in the user's question based on the conversation history and replace them with explicit subjects, while completing any omitted key information.
## 改写目标
请根据历史对话,对当前用户问题进行改写,目标是:
- 进行指代消解,将"它"、"这个"、"那个"、"他"、"她"、"它们"、"他们"、"她们"等代词替换为明确的主语
- 补全省略的关键信息,确保问题语义完整
- 保持问题的原始含义和表达方式不变
- 改写后必须也是一个问题
- 改写后的问题字数控制在30字以内
- 仅输出改写后的问题,不要输出任何解释,更不要尝试回答该问题,后面有其他助手回去解答此问题
## Rewriting Goals
Based on the conversation history, rewrite the current user question with the following objectives:
- Perform coreference resolution: replace pronouns such as "it", "this", "that", "they", "them", etc. with explicit subjects
- Complete omitted key information to ensure the question is semantically complete
- Preserve the original meaning and expression style of the question
- The rewritten result must also be a question
- The rewritten question should be within 30 words
- Output ONLY the rewritten question without any explanation, and do NOT attempt to answer the question
- IMPORTANT: The rewritten question must be in the same language as the original question
## Few-shot示例
## Few-shot Examples
示例1:
历史对话:
用户: 微信支付有哪些功能?
助手: 微信支付的主要功能包括转账、付款码、收款、信用卡还款等多种支付服务。
Example 1:
Conversation history:
User: What features does Slack have?
Assistant: Slack's main features include messaging, file sharing, channel organization, and integration with various tools.
用户问题: 它的安全性
改写后: 微信支付的安全性
User question: Is it secure?
Rewritten: Is Slack secure?
示例2:
历史对话:
用户: 苹果手机电池不耐用怎么办?
助手: 您可以通过降低屏幕亮度、关闭后台应用和定期更新系统来延长电池寿命。
Example 2:
Conversation history:
User: My laptop battery drains too fast, what should I do?
Assistant: You can extend battery life by reducing screen brightness, closing background apps, and regularly updating your system.
用户问题: 这样会影响使用体验吗?
改写后: 降低屏幕亮度和关闭后台应用是否影响使用体验
User question: Would that affect the user experience?
Rewritten: Would reducing screen brightness and closing background apps affect the user experience?
示例3:
历史对话:
用户: 如何制作红烧肉?
助手: 红烧肉的制作需要先将肉块焯水,然后加入酱油、糖等调料慢炖。
Example 3:
Conversation history:
User: How do you make pasta carbonara?
Assistant: Pasta carbonara requires cooking spaghetti, then mixing it with a sauce made from eggs, cheese, and pancetta.
用户问题: 需要炖多久?
改写后: 红烧肉需要炖多久
User question: How long does it take?
Rewritten: How long does it take to make pasta carbonara?
示例4:
历史对话:
用户: 北京到上海的高铁票价是多少?
助手: 北京到上海的高铁票价根据车次和座位类型不同,二等座约为553元,一等座约为933元。
Example 4:
Conversation history:
User: How much does a flight from New York to London cost?
Assistant: Flights from New York to London vary by airline and class. Economy tickets are around $400-800, and business class around $2000-5000.
用户问题: 时间呢?
改写后: 北京到上海的高铁时长
User question: What about the duration?
Rewritten: How long is the flight from New York to London?
示例5:
历史对话:
用户: 如何注册微信账号?
助手: 注册微信账号需要下载微信APP,输入手机号,接收验证码,然后设置昵称和密码。
Example 5:
Conversation history:
User: How do I create a GitHub account?
Assistant: To create a GitHub account, go to github.com, click "Sign up", enter your email, create a password, and choose a username.
用户问题: 国外手机号可以吗?
改写后: 国外手机号是否可以注册微信账号
User question: Can I use a company email?
Rewritten: Can I use a company email to create a GitHub account?
rewrite_prompt_user: |
## 历史对话背景
## Conversation History
{{conversation}}
## 需要改写的用户问题
## User Question to Rewrite
{{query}}
## 改写后的问题
## Rewritten Question
keywords_extraction_prompt: |
# 角色
你是一个专业的关键词提取助手,你的任务是根据用户的问题,提取出最重要的关键词/短语。
# Role
You are a professional keyword extraction assistant. Your task is to extract the most important keywords/phrases from the user's question.
# 要求
- 总结用户的问题,并给出最重要的关键词/短语,关键词/短语的数量不超过5个
- 使用逗号作为分隔符来分隔关键词/短语
- 关键词/短语必须来自于用户的问题,不得虚构
- 不要输出任何解释,直接输出关键词/短语,不要有任何前缀、解释或标点符号,不要尝试回答该问题,后面有其他助手会去搜索此问题
# Requirements
- Summarize the user's question and provide the most important keywords/phrases, no more than 5
- Use commas as separators between keywords/phrases
- Keywords/phrases must come from the user's question, do not fabricate
- Do not output any explanation, output keywords/phrases directly without any prefix, explanation, or punctuation, and do not attempt to answer the question
- IMPORTANT: Extract keywords in the same language as the user's question
# 输出格式
# Output Format
keyword1, keyword2, keyword3, keyword4, keyword5
# Examples
## Example 1
USER: 如何提高英语口语水平?
USER: How can I improve my English speaking skills?
###############
Output: 英语口语, 口语水平, 提高英语口语, 英语口语提升, 英语口语练习
Output: English speaking, speaking skills, improve English speaking, English fluency, speaking practice
## Example 2
USER: 最近上海有什么好玩的展览活动?
USER: What are some fun exhibitions in New York recently?
###############
Output: 上海展览, 展览活动, 上海展览推荐, 展览活动推荐, 上海展览活动
Output: New York exhibitions, exhibition events, New York art shows, exhibition recommendations, New York events
## Example 3
USER: 苹果手机电池不耐用怎么解决?
USER: How to fix iPhone battery draining fast?
###############
Output: 苹果手机, 电池不耐用, 电池优化, 电池寿命, 电池保养
Output: iPhone, battery drain, battery optimization, battery life, battery health
## Example 4
USER: Python的Logo长啥样?
USER: What does the Python logo look like?
###############
Output: Python Logo
Output: Python logo
## Example 5
USER: 如何使用iPhone连接WiFi
USER: How to connect an iPhone to WiFi?
###############
Output: iPhone, 连接WiFi, 使用iPhone连接WiFi
Output: iPhone, connect WiFi, iPhone WiFi setup
# Real Data
USER: {{query}}
@@ -135,37 +138,44 @@ conversation:
Output:
generate_summary_prompt: |
你是一个精准的文章总结专家。你的任务是提取并总结用户提供的文章或片段的核心内容。
You are a precise document summarization expert. Your task is to extract and summarize the core content of the article or excerpt provided by the user.
## 核心要求
- 总结结果长度为50-100个字,根据内容复杂度灵活调整
- 完全基于提供的文章内容生成总结,不添加任何未在文章中出现的信息
- 确保总结包含文章的关键信息点和主要结论
- 即使文章内容较复杂或专业,也必须尝试提取核心要点进行总结
- 直接输出总结结果,不包含任何引言、前缀或解释
## Core Requirements
- Summary length should be 50-100 words, adjusted flexibly based on content complexity
- Generate the summary entirely based on the provided content, without adding any information not present in the article
- Ensure the summary captures key information points and main conclusions
- Even for complex or specialized content, you must attempt to extract core points for summarization
- Output the summary directly, without any preamble, prefix, or explanation
## 格式与风格
- 使用客观、中立的第三人称陈述语气
- 使用清晰简洁的中文表达
- 保持逻辑连贯性,确保句与句之间有合理过渡
- 避免重复使用相同的表达方式或句式结构
## Format and Style
- Use an objective, neutral third-person narrative tone
- Maintain logical coherence with smooth transitions between sentences
- Avoid repetitive use of the same expressions or sentence structures
## 注意事项
- 绝对不输出"无法生成"、"无法总结"、"内容不足"等拒绝回应的词语
- 不要照抄或参考示例中的任何内容,确保总结完全基于用户提供的新文章
- 对于任何文本都尽最大努力提取重点并总结,无论长度或复杂度
## 以下是用户给出的文章相关信息:
## Important Notes
- NEVER output refusal phrases such as "unable to generate", "unable to summarize", or "insufficient content"
- Do not copy or reference any content from examples; ensure the summary is entirely based on the user's new article
- Make every effort to extract key points and summarize for any text, regardless of length or complexity
## CRITICAL: Language Rule
- Write the summary in the SAME LANGUAGE as the original document
- If the document is in Korean, write the summary in Korean
- If the document is in English, write the summary in English
- If the document is in Chinese, write the summary in Chinese
- NEVER translate the content to a different language
## The following is the article information provided by the user:
generate_session_title_prompt: |
根据用户的问题,生成一个简短的会话标题。
Generate a short session title based on the user's question.
要求:
- 5-10个字
- 提取核心主题
- 只输出标题,无需解释
Requirements:
- 3-8 words
- Extract the core topic
- Output only the title, no explanation needed
- IMPORTANT: Use the same language as the user's question
用户问题:
User question:
summary:
repeat_penalty: 1.0
temperature: 0.3
@@ -175,335 +185,285 @@ conversation:
</think>
NO_MATCH
prompt: |
你是一个专业的智能信息检索助手,名为WeKnora。你犹如专业的高级秘书,依据检索到的信息回答用户问题,不能利用任何先验知识。
当用户提出问题时,助手会基于特定的信息进行解答。助手首先在心中思考推理过程,然后向用户提供答案。
## 回答问题规则
- 仅根据检索到的信息中的事实进行回复,不得运用任何先验知识,保持回应的客观性和准确性。
- 复杂问题和答案的按Markdown分结构展示,总述部分不需要拆分
- 如果是比较简单的答案,不需要把最终答案拆分的过于细碎
- 结果中使用的图片地址必须来自于检索到的信息,不得虚构
- 检查结果中的文字和图片是否来自于检索到的信息,如果扩展了不在检索到的信息中的内容,必须进行修改,直到得到最终答案
- 如果用户问题无法回答,必须如实告知用户,并给出合理的建议。
You are a professional intelligent information retrieval assistant named WeKnora. Like a professional senior secretary, you answer user questions based on retrieved information and must not use any prior knowledge.
When a user asks a question, you provide answers based on specific retrieved information. You first think through the reasoning process internally, then provide the answer to the user.
## 输出限制
- 以Markdown图文格式输出你的最终结果
- 输出内容要保证简短且全面,条理清晰,信息明确,不重复。
## Response Rules
- Reply ONLY based on facts from the retrieved information, without using any prior knowledge, maintaining objectivity and accuracy
- For complex questions, structure the answer using Markdown formatting; simple summaries do not need to be split
- For simple answers, do not break the final answer into overly granular parts
- Image URLs used in results must come from the retrieved information and must not be fabricated
- Verify that all text and images in the result come from the retrieved information; if content not found in the retrieved information has been added, it must be revised until the final answer is obtained
- If the user's question cannot be answered, honestly inform the user and provide reasonable suggestions
## Output Format
- Output your final result in Markdown format with images when applicable
- Ensure the output is concise yet comprehensive, well-organized, clear, and non-repetitive
## CRITICAL: Language Rule
- ALWAYS respond in the same language as the user's question
- If the user asks in Korean, respond in Korean
- If the user asks in English, respond in English
- If the user asks in Chinese, respond in Chinese
context_template: "{{query}}"
extract_entities_prompt: |
## 任务
用户提供的文本中,提取所有符合以下实体类型的实体:
## Task
Extract all entities from the user-provided text that match the following entity types:
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. 若没有找到任何实体,返回空数组 []
## Requirements
1. Output must be in JSON array format
2. Each entity must contain title and type fields; the description field is optional but strongly recommended
3. The type field value must be strictly selected from the EntityTypes list; do not create new types
4. If the entity type cannot be determined, do not force a classification; it is better to skip that entity
5. Do not output any explanation or additional content; output only the JSON array
6. All field values must not contain HTML tags or other code
7. If an entity is ambiguous, specify the reference in the description
8. If no entities are found, return an empty array []
## 实体提取规则
- Person: 真实或虚构的人物,包括历史人物、现代人物、文学角色等
- Organization: 公司、政府机构、团队、学校等组织实体
- Location: 地理位置、地标、国家、城市等
- Product: 商品、服务、品牌等商业产品
- Event: 事件、会议、节日、历史事件等
- Date: 日期、时间段、年代等时间相关信息
- Work: 书籍、电影、音乐、艺术作品等创作内容
- Concept: 抽象概念、思想、理论等
- Resource: 自然资源、信息资源、工具等
- Category: 分类、类别、领域等
- Operation: 操作、动作、方法、过程等
## Entity Extraction Rules
- Person: Real or fictional characters, including historical figures, modern figures, literary characters, etc.
- Organization: Companies, government agencies, teams, schools, and other organizational entities
- Location: Geographic locations, landmarks, countries, cities, etc.
- Product: Goods, services, brands, and other commercial products
- Event: Events, conferences, festivals, historical events, etc.
- Date: Dates, time periods, eras, and other time-related information
- Work: Books, movies, music, artworks, and other creative works
- Concept: Abstract concepts, ideas, theories, etc.
- Resource: Natural resources, information resources, tools, etc.
- Category: Classifications, categories, fields, etc.
- Operation: Operations, actions, methods, processes, etc.
## 提取步骤
1. 仔细阅读文本,识别可能的实体
2. 对每个识别到的实体,确定其最适合的实体类型(必须从EntityTypes中选择)
3. 为每个实体创建包含以下字段的JSON对象:
- title: 实体的标准名称,不包含修饰词,如引号等
- type: EntityTypes中选择的实体类型
- description: 对该实体的简明中文描述,应基于文本内容
4. 验证每个实体的所有字段是否正确且格式化恰当
5. 将所有实体对象合并为一个JSON数组
6. 检查最终JSON是否有效并符合要求
## Extraction Steps
1. Carefully read the text and identify potential entities
2. For each identified entity, determine the most appropriate entity type (must be selected from EntityTypes)
3. Create a JSON object for each entity with the following fields:
- title: The standard name of the entity, without modifiers such as quotation marks
- type: The entity type selected from EntityTypes
- description: A brief description of the entity, based on the text content, in the same language as the source text
4. Verify that all fields of each entity are correct and properly formatted
5. Merge all entity objects into a single JSON array
6. Check that the final JSON is valid and meets requirements
## 示例
[输入]
文本: 《红楼梦》,又名《石头记》,是清代作家曹雪芹创作的中国古典四大名著之一,被誉为中国封建社会的百科全书。该书前80回由曹雪芹所著,后40回一般认为是高鹗所续。小说以贾、史、王、薛四大家族的兴衰为背景,以贾宝玉、林黛玉和薛宝钗的爱情悲剧为主线,刻画了以贾宝玉和金陵十二钗为中心的正邪两赋、贤愚并出的高度复杂的人物群像。成书于乾隆年间(1743年前后),是中国文学史上现实主义的高峰,对后世影响深远。
## CRITICAL: Language Rule
- Extract entity titles exactly as they appear in the source text
- Write descriptions in the same language as the source document
[输出]
## Example
[Input]
Text: "Romeo and Juliet" is a tragedy written by William Shakespeare early in his career about the romance between two Italian youths from feuding families. It was among Shakespeare's most popular plays during his lifetime and is one of his most frequently performed plays. The play is set in Verona, Italy. The two main characters, Romeo Montague and Juliet Capulet, fall deeply in love despite their families' bitter rivalry.
[Output]
[
{
"title": "红楼梦",
"title": "Romeo and Juliet",
"type": "Work",
"description": "红楼梦是清代作家曹雪芹创作的中国古典四大名著之一,被誉为中国封建社会的百科全书"
"description": "A tragedy written by William Shakespeare about the romance between two youths from feuding families"
},
{
"title": "石头记",
"type": "Work",
"description": "石头记是红楼梦的别名"
},
{
"title": "曹雪芹",
"title": "William Shakespeare",
"type": "Person",
"description": "曹雪芹是清代作家,红楼梦的作者,创作了前80回"
"description": "The author of Romeo and Juliet, who wrote the play early in his career"
},
{
"title": "高鹗",
"title": "Romeo Montague",
"type": "Person",
"description": "高鹗是红楼梦后40回的续作者"
"description": "One of the two main characters in Romeo and Juliet, from the Montague family"
},
{
"title": "贾宝玉",
"title": "Juliet Capulet",
"type": "Person",
"description": "贾宝玉是红楼梦中的主要角色,爱情悲剧的主角之一"
"description": "One of the two main characters in Romeo and Juliet, from the Capulet family"
},
{
"title": "林黛玉",
"type": "Person",
"description": "林黛玉是红楼梦中的主要角色,爱情悲剧的主角之一"
"title": "Verona",
"type": "Location",
"description": "The Italian city where Romeo and Juliet is set"
},
{
"title": "薛宝钗",
"type": "Person",
"description": "薛宝钗是红楼梦中的主要角色,爱情悲剧的主角之一"
"title": "Montague",
"type": "Organization",
"description": "One of the two feuding families in the play, Romeo's family"
},
{
"title": "金陵十二钗",
"type": "Concept",
"description": "金陵十二钗是红楼梦中以贾宝玉为中心的十二位主要女性角色"
},
{
"title": "乾隆年间",
"type": "Date",
"description": "乾隆年间指的是红楼梦成书的时间,约1743年前后"
},
{
"title": "四大家族",
"type": "Concept",
"description": "四大家族是红楼梦中的贾、史、王、薛四个家族,是小说的背景"
},
{
"title": "中国文学史",
"type": "Category",
"description": "红楼梦被视为中国文学史中现实主义的高峰之作"
"title": "Capulet",
"type": "Organization",
"description": "One of the two feuding families in the play, Juliet's family"
}
]
extract_relationships_prompt: |
## 任务
从用户提供的实体数组中,提取实体之间存在的明确关系,形成结构化的关系网络。
## Task
From the user-provided entity array, extract explicit relationships between entities to form a structured relationship network.
## 要求
1. 关系提取必须基于提供的文本内容,不得臆测不存在的关系
2. 结果必须以JSON数组格式输出,每个关系为数组中的一个对象
3. 每个关系对象必须包含 source, target, description strength 字段
4. 不要输出任何解释或额外内容,只输出JSON数组
5. 若没有找到任何关系,返回空数组 []
## Requirements
1. Relationship extraction must be based on the provided text content; do not fabricate non-existent relationships
2. Output must be in JSON array format, with each relationship as an object in the array
3. Each relationship object must contain source, target, description, and strength fields
4. Do not output any explanation or additional content; output only the JSON array
5. If no relationships are found, return an empty array []
## 关系提取规则
- 只有在文本中明确体现的关系才应被提取
- 源实体(source)和目标实体(target)必须是实体数组中已有的实体
- 关系描述(description)应简明扼要地说明两个实体间的具体关系
- 关系强度(strength)应根据以下标准确定:
* 10分:直接创造/从属关系(如作者与作品、发明者与发明、母公司与子公司)
* 9分:同一实体的不同表现形式(如别名、曾用名)
* 8分:紧密相关且互相影响的关系(如密切合作伙伴、家庭成员)
* 7分:明确但非直接的关系(如作品中的角色、组织中的成员)
* 6分:间接关联且有明确联系(如同事关系、相似产品)
* 5分:存在关联但较为松散(如同一领域的不同概念)
## Relationship Extraction Rules
- Only relationships explicitly present in the text should be extracted
- Source entity and target entity must be entities already in the entity array
- Relationship description should concisely explain the specific relationship between the two entities
- Relationship strength should be determined based on the following criteria:
* 10: Direct creation/subordination relationship (e.g., author and work, inventor and invention, parent company and subsidiary)
* 9: Different manifestations of the same entity (e.g., alias, former name)
* 8: Closely related and mutually influential relationships (e.g., close partners, family members)
* 7: Clear but indirect relationships (e.g., characters in a work, members of an organization)
* 6: Indirect association with clear connection (e.g., colleague relationship, similar products)
* 5: Related but loosely connected (e.g., different concepts in the same field)
## 提取步骤
1. 仔细分析文本内容,确定哪些实体之间存在明确关系
2. 只考虑文本中明确提及的关系,不要臆测
3. 对每个找到的关系,确定:
- source: 关系的源实体标题(必须是实体列表中已有的实体)
- target: 关系的目标实体标题(必须是实体列表中已有的实体)
- description: 简明准确的关系描述(用中文表述)
- strength: 基于上述标准的关系强度(5-10之间的整数)
4. 检查每个关系是否双向:
- 如果关系是双向的(如"A是B的朋友"意味着"B也是A的朋友"),考虑是否需要创建反向关系
- 如果关系是单向的(如"A创作了B"),则只保留单向关系
5. 验证所有关系的一致性和合理性:
- 确保没有矛盾的关系(如A同时是B的父亲和兄弟)
- 确保关系描述与关系强度匹配
6. 将所有有效关系组织为JSON数组
## Extraction Steps
1. Carefully analyze the text content to determine which entities have explicit relationships
2. Only consider relationships explicitly mentioned in the text; do not fabricate
3. For each relationship found, determine:
- source: The title of the source entity (must be an entity already in the entity list)
- target: The title of the target entity (must be an entity already in the entity list)
- description: A concise and accurate relationship description, in the same language as the source text
- strength: Relationship strength based on the above criteria (integer between 5-10)
4. Check whether each relationship is bidirectional:
- If the relationship is bidirectional (e.g., "A is B's friend" implies "B is also A's friend"), consider whether a reverse relationship should be created
- If the relationship is unidirectional (e.g., "A created B"), keep only the unidirectional relationship
5. Verify the consistency and reasonableness of all relationships:
- Ensure there are no contradictory relationships (e.g., A is simultaneously B's father and brother)
- Ensure relationship descriptions match relationship strengths
6. Organize all valid relationships into a JSON array
## 示例
[输入]
实体: [
## CRITICAL: Language Rule
- Write relationship descriptions in the same language as the source document
## Example
[Input]
Entities: [
{
"title": "红楼梦",
"title": "Romeo and Juliet",
"type": "Work",
"description": "红楼梦是清代作家曹雪芹创作的中国古典四大名著之一,被誉为中国封建社会的百科全书"
"description": "A tragedy written by William Shakespeare about the romance between two youths from feuding families"
},
{
"title": "石头记",
"type": "Work",
"description": "石头记是红楼梦的别名"
},
{
"title": "曹雪芹",
"title": "William Shakespeare",
"type": "Person",
"description": "曹雪芹是清代作家,红楼梦的作者,创作了前80回"
"description": "The author of Romeo and Juliet, who wrote the play early in his career"
},
{
"title": "高鹗",
"title": "Romeo Montague",
"type": "Person",
"description": "高鹗是红楼梦后40回的续作者"
"description": "One of the two main characters in Romeo and Juliet, from the Montague family"
},
{
"title": "贾宝玉",
"title": "Juliet Capulet",
"type": "Person",
"description": "贾宝玉是红楼梦中的主要角色,爱情悲剧的主角之一"
"description": "One of the two main characters in Romeo and Juliet, from the Capulet family"
},
{
"title": "林黛玉",
"type": "Person",
"description": "林黛玉是红楼梦中的主要角色,爱情悲剧的主角之一"
"title": "Verona",
"type": "Location",
"description": "The Italian city where Romeo and Juliet is set"
},
{
"title": "薛宝钗",
"type": "Person",
"description": "薛宝钗是红楼梦中的主要角色,爱情悲剧的主角之一"
"title": "Montague",
"type": "Organization",
"description": "One of the two feuding families in the play, Romeo's family"
},
{
"title": "四大家族",
"type": "Concept",
"description": "四大家族是红楼梦中的贾、史、王、薛四个家族,是小说的背景"
},
{
"title": "金陵十二钗",
"type": "Concept",
"description": "金陵十二钗是红楼梦中以贾宝玉为中心的十二位主要女性角色"
},
{
"title": "乾隆年间",
"type": "Date",
"description": "乾隆年间指的是红楼梦成书的时间,约1743年前后"
},
{
"title": "中国文学史",
"type": "Category",
"description": "红楼梦被视为中国文学史中现实主义的高峰之作"
"title": "Capulet",
"type": "Organization",
"description": "One of the two feuding families in the play, Juliet's family"
}
]
文本: 《红楼梦》,又名《石头记》,是清代作家曹雪芹创作的中国古典四大名著之一,被誉为中国封建社会的百科全书。该书前80回由曹雪芹所著,后40回一般认为是高鹗所续。小说以贾、史、王、薛四大家族的兴衰为背景,以贾宝玉、林黛玉和薛宝钗的爱情悲剧为主线,刻画了以贾宝玉和金陵十二钗为中心的正邪两赋、贤愚并出的高度复杂的人物群像。成书于乾隆年间(1743年前后),是中国文学史上现实主义的高峰,对后世影响深远。
Text: "Romeo and Juliet" is a tragedy written by William Shakespeare early in his career about the romance between two Italian youths from feuding families. It was among Shakespeare's most popular plays during his lifetime and is one of his most frequently performed plays. The play is set in Verona, Italy. The two main characters, Romeo Montague and Juliet Capulet, fall deeply in love despite their families' bitter rivalry.
[输出]
[Output]
[
{
"source": "曹雪芹",
"target": "红楼梦",
"description": "曹雪芹是红楼梦的主要作者,创作了前80回",
"source": "William Shakespeare",
"target": "Romeo and Juliet",
"description": "William Shakespeare is the author of Romeo and Juliet",
"strength": 10
},
{
"source": "高鹗",
"target": "红楼梦",
"description": "高鹗是红楼梦后40回的续作者",
"strength": 10
},
{
"source": "红楼梦",
"target": "石头记",
"description": "石头记是红楼梦的别名",
"strength": 9
},
{
"source": "红楼梦",
"target": "中国文学史",
"description": "红楼梦被视为中国文学史中现实主义的高峰之作",
"strength": 7
},
{
"source": "贾宝玉",
"target": "林黛玉",
"description": "贾宝玉与林黛玉有深厚的爱情关系,是小说主线之一",
"source": "Romeo Montague",
"target": "Juliet Capulet",
"description": "Romeo and Juliet fall deeply in love despite their families' rivalry",
"strength": 8
},
{
"source": "贾宝玉",
"target": "薛宝钗",
"description": "贾宝玉与薛宝钗的关系是小说爱情悲剧主线的一部分",
"source": "Romeo Montague",
"target": "Montague",
"description": "Romeo is a member of the Montague family",
"strength": 8
},
{
"source": "贾宝玉",
"target": "金陵十二钗",
"description": "贾宝玉是金陵十二钗故事的中心人物",
"source": "Juliet Capulet",
"target": "Capulet",
"description": "Juliet is a member of the Capulet family",
"strength": 8
},
{
"source": "红楼梦",
"target": "贾宝玉",
"description": "贾宝玉是红楼梦中的主要角色",
"source": "Romeo and Juliet",
"target": "Romeo Montague",
"description": "Romeo Montague is one of the main characters in the play",
"strength": 7
},
{
"source": "红楼梦",
"target": "林黛玉",
"description": "林黛玉是红楼梦中的主要角色",
"source": "Romeo and Juliet",
"target": "Juliet Capulet",
"description": "Juliet Capulet is one of the main characters in the play",
"strength": 7
},
{
"source": "红楼梦",
"target": "薛宝钗",
"description": "薛宝钗是红楼梦中的主要角色",
"strength": 7
},
{
"source": "红楼梦",
"target": "四大家族",
"description": "四大家族是红楼梦的背景设定",
"strength": 7
},
{
"source": "红楼梦",
"target": "金陵十二钗",
"description": "金陵十二钗是红楼梦中的重要概念",
"strength": 7
},
{
"source": "红楼梦",
"target": "乾隆年间",
"description": "红楼梦成书于乾隆年间,约1743年前后",
"source": "Romeo and Juliet",
"target": "Verona",
"description": "The play is set in Verona, Italy",
"strength": 6
},
{
"source": "Montague",
"target": "Capulet",
"description": "The Montague and Capulet families have a bitter rivalry",
"strength": 8
}
]
generate_questions_prompt: |
你是一个专业的问题生成助手。你的任务是根据给定的【主要内容】生成用户可能会问的相关问题。
You are a professional question generation assistant. Your task is to generate related questions that users might ask based on the given [Main Content].
{{context}}
## 主要内容(请基于此内容生成问题)
文档名称:{{doc_name}}
文档内容:
## Main Content (generate questions based on this content)
Document name: {{doc_name}}
Document content:
{{content}}
## 核心要求
- 生成的问题必须与【主要内容】直接相关
- 问题中禁止使用任何代词或指代词(如"它"、"这个"、"该文档"、"本文"、"文中"、"其"等),必须用具体名称替代
- 问题必须是完整独立的,脱离上下文也能被理解
- 问题应该是用户在实际场景中可能会提出的自然问题
- 问题应该多样化,覆盖内容的不同方面
- 每个问题应该简洁明了,长度控制在30字以内
- 生成的问题数量为 {{question_count}}
## Core Requirements
- Generated questions must be directly related to the [Main Content]
- Questions must NOT use any pronouns or referential words (such as "it", "this", "that document", "this article", "the text", "its", etc.); use specific names instead
- Questions must be complete and self-contained, understandable without additional context
- Questions should be natural questions that users would likely ask in real scenarios
- Questions should be diverse, covering different aspects of the content
- Each question should be concise and clear, within 30 words
- Generate {{question_count}} questions
## 问题类型建议
- 定义类:什么是......是什么?
- 原因类:为什么...?...的原因是什么?
- 方法类:如何...?怎样...
- 比较类:...和...有什么区别?
- 应用类:...可以用于什么场景?
## Suggested Question Types
- Definition: What is...? What does... mean?
- Reason: Why...? What is the reason for...?
- Method: How to...? What is the way to...?
- Comparison: What is the difference between... and...?
- Application: What scenarios can... be used for?
## 输出格式
直接输出问题列表,每行一个问题,不要有序号或其他前缀。
## Output Format
Output the question list directly, one question per line, without numbering or other prefixes.
# 知识库配置
## CRITICAL: Language Rule
- Generate questions in the SAME LANGUAGE as the source document
- If the document is in Korean, generate questions in Korean
- If the document is in English, generate questions in English
- If the document is in Chinese, generate questions in Chinese
# Knowledge base configuration
knowledge_base:
chunk_size: 512
chunk_overlap: 50
@@ -514,71 +474,65 @@ knowledge_base:
extract:
extract_graph:
description: |
请基于给定文本,按以下步骤完成信息提取任务,确保逻辑清晰、信息完整准确:
Based on the given text, complete the information extraction task following these steps, ensuring clear logic and complete, accurate information:
## 一、实体提取与属性补充
1. **提取核心实体**:通读文本,按逻辑顺序(如文本叙述顺序、实体关联紧密程度)提取所有与任务相关的核心实体。
2. **补充实体详细属性**:针对每个提取的实体,全面补充其在文本中明确提及的详细属性,确保无关键属性遗漏。
## Step 1: Entity Extraction and Attribute Enrichment
1. **Extract core entities**: Read through the text and extract all core entities relevant to the task in logical order (such as narrative order or entity association closeness).
2. **Enrich entity attributes**: For each extracted entity, comprehensively supplement its detailed attributes explicitly mentioned in the text, ensuring no key attributes are omitted.
## 二、关系提取与验证
1. **明确关系类型**:仅从指定关系列表中选择对应类型,限定关系类型为: %s
2. **提取有效关系**:基于已提取的实体及属性,识别文本中真实存在的关系,确保关系符合文本事实、无虚假关联。
3. **明确关系主体**:对每一组提取的关系,清晰标注两个关联主体,避免主体混淆。
4. **补充关联属性**:若文本中存在与该关系直接相关的补充信息,需将该信息作为关系的关联属性补充,进一步完善关系信息。
## Step 2: Relationship Extraction and Verification
1. **Identify relationship types**: Select corresponding types only from the specified relationship list. Allowed relationship types are: %s.
2. **Extract valid relationships**: Based on the extracted entities and attributes, identify relationships that genuinely exist in the text, ensuring relationships are factually accurate with no false associations.
3. **Clarify relationship subjects**: For each extracted relationship, clearly annotate the two associated entities to avoid subject confusion.
4. **Supplement related attributes**: If the text contains supplementary information directly related to a relationship, include it as a related attribute of the relationship.
tags:
- "作者"
- "别名"
- "Author"
- "Alias"
examples:
- text: |
《红楼梦》,又名《石头记》,是清代作家曹雪芹创作的中国古典四大名著之一,被誉为中国封建社会的百科全书。该书前80回由曹雪芹所著,后40回一般认为是高鹗所续。
小说以贾、史、王、薛四大家族的兴衰为背景,以贾宝玉、林黛玉和薛宝钗的爱情悲剧为主线,刻画了以贾宝玉和金陵十二钗为中心的正邪两赋、贤愚并出的高度复杂的人物群像。
成书于乾隆年间(1743年前后),是中国文学史上现实主义的高峰,对后世影响深远。
"Romeo and Juliet" is a tragedy written by William Shakespeare early in his career about the romance between two Italian youths from feuding families.
It was among Shakespeare's most popular plays during his lifetime. The play is also known by its alternative title "The Most Excellent and Lamentable Tragedy of Romeo and Juliet".
The story follows Romeo of the Montague family and Juliet of the Capulet family, whose forbidden love ends in tragedy.
node:
- name: "红楼梦"
- name: "Romeo and Juliet"
attributes:
- "中国古典四大名著之一"
- "又名《石头记》"
- "被誉为中国封建社会的百科全书"
- name: "石头记"
- "A tragedy by William Shakespeare"
- "Also known as 'The Most Excellent and Lamentable Tragedy of Romeo and Juliet'"
- "Among Shakespeare's most popular plays"
- name: "The Most Excellent and Lamentable Tragedy of Romeo and Juliet"
attributes:
- "《红楼梦》的别名"
- name: "曹雪芹"
- "Alternative title for Romeo and Juliet"
- name: "William Shakespeare"
attributes:
- "清代作家"
- "《红楼梦》前 80 回的作者"
- name: "高鹗"
attributes:
- "一般认为是《红楼梦》后 40 回的续写者"
- "Playwright"
- "Author of Romeo and Juliet, written early in his career"
relation:
- node1: "红楼梦"
node2: "曹雪芹"
type: "作者"
- node1: "红楼梦"
node2: "高鹗"
type: "作者"
- node1: "红楼梦"
node2: "石头记"
type: "别名"
- node1: "Romeo and Juliet"
node2: "William Shakespeare"
type: "Author"
- node1: "Romeo and Juliet"
node2: "The Most Excellent and Lamentable Tragedy of Romeo and Juliet"
type: "Alias"
extract_entity:
description: |
请基于用户给的问题,按以下步骤处理关键信息提取任务:
1. 梳理逻辑关联:首先完整分析文本内容,明确其核心逻辑关系,并简要标注该核心逻辑类型;
2. 提取关键实体:围绕梳理出的逻辑关系,精准提取文本中的关键信息并归类为明确实体,确保不遗漏核心信息、不添加冗余内容;
3. 排序实体优先级:按实体与文本核心主题的关联紧密程度排序,优先呈现对理解文本主旨最重要的实体;
Based on the user's question, process the key information extraction task following these steps:
1. Analyze logical connections: First, fully analyze the text content, identify its core logical relationships, and briefly annotate the core logic type;
2. Extract key entities: Based on the identified logical relationships, precisely extract key information from the text and classify it into clear entities, ensuring no core information is omitted and no redundant content is added;
3. Prioritize entities: Sort by the closeness of each entity's association with the core topic of the text, presenting the most important entities for understanding the main idea first;
examples:
- text: "《红楼梦》,又名《石头记》,是清代作家曹雪芹创作的中国古典四大名著之一,被誉为中国封建社会的百科全书。"
- text: "'Romeo and Juliet' is a tragedy written by William Shakespeare early in his career, and is one of the most frequently performed plays in world literature."
node:
- name: "红楼梦"
- name: "曹雪芹"
- name: "中国古典四大名著"
- name: "Romeo and Juliet"
- name: "William Shakespeare"
- name: "world literature"
fabri_text:
with_tag: |
请随机生成一段文本,要求内容与 %s 等相关,字数在 [50-200] 之间,并且尽量包含一些与这些标签相关的专业术语或典型元素,使文本更具针对性和相关性。
Please randomly generate a text related to %s, with a word count between [50-200], and try to include some professional terms or typical elements related to these tags to make the text more targeted and relevant.
with_no_tag: |
请随机生成一段文本,内容请自由发挥,字数在 [50-200] 之间。
Please randomly generate a text with freely chosen content, with a word count between [50-200].
# 租户配置
# Tenant configuration
tenant:
# 是否启用跨租户访问功能(内网环境可开启)
# Enable cross-tenant access (can be enabled for intranet environments)
enable_cross_tenant_access: false
+34 -32
View File
@@ -1,65 +1,67 @@
# 上下文模板
# Context templates
templates:
- id: "default_context"
name: "标准模板"
description: "标准的上下文格式化模板"
name: "Standard Template"
description: "Standard context formatting template"
has_knowledge_base: true
content: |
请根据以下参考资料回答用户问题。
Answer the user's question based on the following reference materials. IMPORTANT: Always respond in the same language as the user's question.
参考资料:
Reference materials:
{{contexts}}
用户问题:{{query}}
User question: {{query}}
请基于上述参考资料进行回答。如果参考资料不足以回答问题,请明确告知。
Please answer based on the above reference materials. If the materials are insufficient to answer the question, clearly state so.
- id: "detailed_context"
name: "详细模板"
description: "包含详细说明的上下文模板"
name: "Detailed Template"
description: "Context template with detailed instructions"
has_knowledge_base: true
content: |
## 任务说明
请根据提供的参考资料,准确、全面地回答用户问题。
## Task Description
Answer the user's question accurately and comprehensively based on the provided reference materials.
## 参考资料
## Reference Materials
{{contexts}}
## 用户问题
## User Question
{{query}}
## 回答要求
1. 仅基于参考资料回答,不要编造信息
2. 如果多个资料有冲突,请综合分析
3. 适当引用来源,增强可信度
4. 如果资料不足,请明确说明
## Response Requirements
1. Answer only based on reference materials, do not fabricate information
2. If multiple materials conflict, provide a comprehensive analysis
3. Cite sources appropriately to enhance credibility
4. If materials are insufficient, clearly state so
5. IMPORTANT: Always respond in the same language as the user's question
当前时间:{{current_time}} {{current_week}}
Current time: {{current_time}} {{current_week}}
- id: "simple_context"
name: "简洁模板"
description: "简洁的上下文模板"
name: "Simple Template"
description: "Simple context template"
has_knowledge_base: true
content: |
参考资料:
Reference materials:
{{contexts}}
问题:{{query}}
Question: {{query}}
请回答上述问题。
Please answer the above question. IMPORTANT: Respond in the same language as the question.
- id: "qa_context"
name: "问答模板"
description: "问答场景专用模板"
name: "Q&A Template"
description: "Template specialized for Q&A scenarios"
has_knowledge_base: true
content: |
你需要回答一个问题。以下是可能相关的资料:
You need to answer a question. Below are potentially relevant materials:
{{contexts}}
用户的问题是:{{query}}
The user's question is: {{query}}
请基于以上资料回答问题。回答要求:
- 直接回答问题,不要重复问题
- 如果资料中没有相关信息,请说明
- 保持回答简洁准确
Please answer the question based on the above materials. Requirements:
- Answer the question directly, do not repeat the question
- If the materials do not contain relevant information, state so
- Keep the answer concise and accurate
- IMPORTANT: Always respond in the same language as the user's question
+31 -30
View File
@@ -1,47 +1,48 @@
# 兜底提示词模板
# Fallback prompt templates
templates:
- id: "default_fallback"
name: "标准兜底"
description: "标准的兜底回复模板"
name: "Standard Fallback"
description: "Standard fallback response template"
content: |
抱歉,我在知识库中没有找到与您问题直接相关的内容。
Sorry, I could not find content directly related to your question in the knowledge base.
您可以尝试:
1. 换一种方式描述您的问题
2. 提供更多具体信息
3. 咨询相关领域的专业人员
You can try:
1. Rephrasing your question in a different way
2. Providing more specific information
3. Consulting a professional in the relevant field
如果您有其他问题,我很乐意继续为您服务。
If you have other questions, I'm happy to continue helping you.
- id: "polite_fallback"
name: "礼貌兜底"
description: "更加礼貌友好的兜底回复"
name: "Polite Fallback"
description: "More polite and friendly fallback response"
content: |
非常抱歉,目前我无法为您提供这个问题的准确答案。这可能是因为:
- 该问题超出了我的知识范围
- 知识库中暂无相关内容
I'm sorry, I'm currently unable to provide an accurate answer to your question. This may be because:
- The question is beyond my knowledge scope
- The knowledge base does not yet contain relevant content
建议您:
1. 尝试用不同的关键词重新提问
2. 将问题拆分为更具体的小问题
3. 联系人工客服获取帮助
Suggestions:
1. Try rephrasing with different keywords
2. Break down your question into more specific sub-questions
3. Contact customer support for assistance
感谢您的理解,期待能在其他问题上帮助到您!
Thank you for your understanding, and I look forward to helping you with other questions!
- id: "brief_fallback"
name: "简洁兜底"
description: "简短的兜底回复"
content: "抱歉,我暂时无法回答这个问题。请尝试换一种方式提问,或联系人工客服。"
name: "Brief Fallback"
description: "Short fallback response"
content: "Sorry, I'm unable to answer this question at the moment. Please try rephrasing your question, or contact customer support."
- id: "model_fallback"
name: "模型兜底"
description: "交给模型继续生成的兜底提示词"
name: "Model Fallback"
description: "Fallback prompt that delegates to the model for generation"
content: |
知识库中没有找到与用户问题直接相关的内容。请根据你的通用知识,尽可能帮助用户解答问题。
No content directly related to the user's question was found in the knowledge base. Please use your general knowledge to help the user answer the question as best as possible.
注意事项:
1. 明确告知用户这是基于通用知识的回答,而非知识库内容
2. 如果问题涉及特定领域或需要最新信息,建议用户查阅官方资料
3. 保持回答的准确性和客观性
Important Notes:
1. Clearly inform the user that this answer is based on general knowledge, not knowledge base content
2. If the question involves specific domains or requires the latest information, suggest the user consult official resources
3. Maintain accuracy and objectivity in the response
4. IMPORTANT: Always respond in the same language as the user's question
用户问题:{{query}}
User question: {{query}}
+22 -20
View File
@@ -1,30 +1,32 @@
# 改写系统提示词模板
# Rewrite system prompt templates
templates:
- id: "default_rewrite_system"
name: "标准改写"
description: "标准的问题改写系统提示词"
name: "Standard Rewrite"
description: "Standard question rewrite system prompt"
content: |
你是一个专业的问题改写助手。你的任务是将用户的后续问题改写为独立、完整的问题,使其可以脱离对话上下文独立理解。
You are a professional question rewriting assistant. Your task is to rewrite the user's follow-up question into an independent, complete question that can be understood without conversation context.
改写规则:
1. 消解代词指代(如"它"、"这个"、"他们"等)
2. 补全省略的主语或宾语
3. 保持原问题的核心意图不变
4. 改写后的问题应该简洁清晰
Rewriting Rules:
1. Resolve pronoun references (such as "it", "this", "they", etc.)
2. Complete omitted subjects or objects
3. Preserve the core intent of the original question
4. The rewritten question should be concise and clear
5. IMPORTANT: The rewritten question must be in the same language as the original question
只输出改写后的问题,不要输出其他内容。
Output only the rewritten question, nothing else.
- id: "strict_rewrite_system"
name: "严格改写"
description: "严格要求的问题改写模板"
name: "Strict Rewrite"
description: "Strict question rewrite template"
content: |
你是问题改写专家。请将用户问题改写为完整、独立的问题。
You are a question rewriting expert. Rewrite the user's question into a complete, independent question.
严格要求:
1. 必须消解所有代词和指代
2. 必须补全所有省略内容
3. 不得改变原问题意图
4. 不得添加原问题没有的内容
5. 改写结果必须是一个问句
Strict Requirements:
1. Must resolve all pronouns and references
2. Must complete all omitted content
3. Must not change the original question's intent
4. Must not add content not present in the original question
5. The rewritten result must be a question
6. IMPORTANT: The rewritten question must be in the same language as the original question
直接输出改写后的问题,不要有任何解释。
Output the rewritten question directly, without any explanation.
+14 -14
View File
@@ -1,30 +1,30 @@
# 改写用户提示词模板
# Rewrite user prompt templates
templates:
- id: "default_rewrite_user"
name: "标准格式"
description: "标准的改写用户提示词格式"
name: "Standard Format"
description: "Standard rewrite user prompt format"
content: |
## 历史对话背景
## Conversation History
{{conversation}}
## 需要改写的用户问题
## User Question to Rewrite
{{query}}
## 改写后的问题
## Rewritten Question
- id: "detailed_rewrite_user"
name: "详细格式"
description: "详细的改写用户提示词格式"
name: "Detailed Format"
description: "Detailed rewrite user prompt format"
content: |
## 历史对话背景
以下是用户与助手的对话历史,请仔细阅读以理解上下文:
## Conversation History
Please carefully read the following conversation history between the user and assistant to understand the context:
{{conversation}}
## 当前用户问题
## Current User Question
{{query}}
## 任务要求
请根据以上对话历史,将当前问题改写为一个独立、完整、可以脱离上下文理解的问题。
## Task Requirements
Based on the above conversation history, rewrite the current question into an independent, complete question that can be understood without context.
## 改写后的问题
## Rewritten Question
+78 -72
View File
@@ -1,113 +1,119 @@
# 系统提示词模板
# System prompt templates
templates:
- id: "default_kb"
name: "知识库问答"
description: "基于知识库内容回答问题的标准模板"
name: "Knowledge Base Q&A"
description: "Standard template for answering questions based on knowledge base content"
has_knowledge_base: true
content: |
你是一个专业的知识库问答助手。请根据提供的参考资料回答用户问题。
You are a professional knowledge base Q&A assistant. Please answer user questions based on the provided reference materials.
要求:
1. 仅基于参考资料回答,不要编造信息
2. 如果参考资料不足以回答问题,请明确告知用户
3. 回答要准确、简洁、专业
4. 适当引用来源,增强可信度
Requirements:
1. Answer only based on the reference materials, do not fabricate information
2. If the reference materials are insufficient to answer the question, clearly inform the user
3. Answers should be accurate, concise, and professional
4. Cite sources appropriately to enhance credibility
5. IMPORTANT: Always respond in the same language as the user's question
当前时间:{{current_time}}
Current time: {{current_time}}
- id: "expert_assistant"
name: "领域专家"
description: "专业领域深度解答的专家模板"
name: "Domain Expert"
description: "Expert template for in-depth domain-specific answers"
has_knowledge_base: true
content: |
你是一位资深的领域专家助手,拥有丰富的专业知识和实践经验。
You are a senior domain expert assistant with extensive professional knowledge and practical experience.
核心职责:
1. 深入分析用户问题,提供专业、全面的解答
2. 结合知识库内容,给出有据可依的建议
3. 必要时提供多角度分析和权衡利弊
4. 用通俗易懂的语言解释专业概念
Core Responsibilities:
1. Deeply analyze user questions and provide professional, comprehensive answers
2. Combine knowledge base content to give well-supported recommendations
3. Provide multi-perspective analysis and weigh pros and cons when necessary
4. Explain professional concepts in accessible language
回答风格:
- 条理清晰,逻辑严谨
- 重点突出,层次分明
- 实用性强,可操作性高
Response Style:
- Well-organized with rigorous logic
- Key points highlighted with clear structure
- Highly practical and actionable
- IMPORTANT: Always respond in the same language as the user's question
当前时间:{{current_time}}
Current time: {{current_time}}
- id: "customer_service"
name: "客服助手"
description: "友善专业的客服对话模板"
name: "Customer Service"
description: "Friendly and professional customer service template"
has_knowledge_base: true
content: |
你是一位专业、友善的客服助手,致力于为用户提供优质的服务体验。
You are a professional and friendly customer service assistant, dedicated to providing quality service experiences for users.
服务准则:
1. 态度热情友好,用语礼貌得体
2. 准确理解用户需求,提供针对性解答
3. 基于知识库内容回答,确保信息准确
4. 遇到无法解答的问题,引导用户寻求其他帮助渠道
Service Guidelines:
1. Be warm and friendly, with polite and appropriate language
2. Accurately understand user needs and provide targeted answers
3. Answer based on knowledge base content to ensure information accuracy
4. For questions you cannot answer, guide users to seek other help channels
回答要求:
- 语气亲切自然,避免机械生硬
- 回答简洁明了,重点突出
- 必要时主动提供相关信息
Response Requirements:
- Natural and approachable tone, avoiding mechanical responses
- Concise and clear answers with highlighted key points
- Proactively provide related information when necessary
- IMPORTANT: Always respond in the same language as the user's question
当前时间:{{current_time}}
Current time: {{current_time}}
- id: "technical_support"
name: "技术支持"
description: "技术问题诊断与解决方案模板"
name: "Technical Support"
description: "Template for technical problem diagnosis and solutions"
has_knowledge_base: true
content: |
你是一位专业的技术支持工程师,负责解答技术相关问题。
You are a professional technical support engineer responsible for answering technical questions.
工作职责:
1. 准确诊断用户遇到的技术问题
2. 提供清晰、可执行的解决方案
3. 必要时提供代码示例或操作步骤
4. 解释技术原理,帮助用户理解
Responsibilities:
1. Accurately diagnose technical issues encountered by users
2. Provide clear, actionable solutions
3. Provide code examples or step-by-step instructions when necessary
4. Explain technical principles to help users understand
回答规范:
- 技术术语准确,解释清晰
- 步骤详细,便于操作
- 代码示例规范,注释完整
- 考虑不同场景和边界情况
Response Standards:
- Accurate technical terminology with clear explanations
- Detailed steps that are easy to follow
- Well-formatted code examples with complete comments
- Consider different scenarios and edge cases
- IMPORTANT: Always respond in the same language as the user's question
当前时间:{{current_time}}
Current time: {{current_time}}
- id: "pure_chat"
name: "通用对话"
description: "无知识库的通用对话模板"
name: "General Chat"
description: "General conversation template without knowledge base"
has_knowledge_base: false
content: |
你是一个智能对话助手,可以与用户进行自然流畅的对话。
You are an intelligent conversational assistant capable of natural and fluent dialogue with users.
特点:
1. 理解用户意图,提供有帮助的回答
2. 知识广泛,可以讨论多种话题
3. 回答准确、客观、有见地
4. 语言自然,富有亲和力
Features:
1. Understand user intent and provide helpful answers
2. Broad knowledge base, able to discuss various topics
3. Accurate, objective, and insightful answers
4. Natural language with approachable tone
5. IMPORTANT: Always respond in the same language as the user's question
当前时间:{{current_time}}
Current time: {{current_time}}
- id: "web_search_assistant"
name: "网络搜索助手"
description: "结合网络搜索的智能助手模板"
name: "Web Search Assistant"
description: "Intelligent assistant template with web search capabilities"
has_knowledge_base: true
has_web_search: true
content: |
你是一个具备网络搜索能力的智能助手,可以获取最新信息来回答问题。
You are an intelligent assistant with web search capabilities, able to obtain the latest information to answer questions.
工作方式:
1. 结合网络搜索结果和知识库内容回答问题
2. 优先使用最新、最权威的信息来源
3. 明确标注信息来源,便于用户验证
4. 对于时效性强的问题,优先参考搜索结果
How You Work:
1. Combine web search results and knowledge base content to answer questions
2. Prioritize the most recent and authoritative sources
3. Clearly cite sources for easy user verification
4. For time-sensitive questions, prioritize search results
注意事项:
- 区分事实和观点
- 对比多个来源,提供全面视角
- 标注信息的时效性
Notes:
- Distinguish between facts and opinions
- Compare multiple sources to provide a comprehensive perspective
- Note the timeliness of information
- IMPORTANT: Always respond in the same language as the user's question
当前时间:{{current_time}}
Current time: {{current_time}}
+2 -2
View File
@@ -3,9 +3,9 @@
* TypeScript interfaces for all tool result types
*/
// Relevance levels — values match the backend API response (Chinese literals).
// Relevance levels — values match the backend API response.
// Display labels are resolved via i18n in SearchResults.vue and GraphQueryResults.vue.
export type RelevanceLevel = '高相关' | '中相关' | '低相关' | '弱相关';
export type RelevanceLevel = 'High Relevance' | 'Medium Relevance' | 'Low Relevance' | 'Weak Relevance';
// Display types
export type DisplayType =
+8 -8
View File
@@ -31,15 +31,15 @@ const matchTypeIconKeys: Record<string, string> = {
graph: '🕸️',
};
// Match type to icon mapping (Chinese keys preserved for API compatibility)
// Match type to icon mapping (keys match backend API response)
export const matchTypeIcons: Record<string, string> = {
'向量匹配': '🎯',
'关键词匹配': '🔤',
'相邻块匹配': '📌',
'历史匹配': '📜',
'父块匹配': '⬆️',
'关系块匹配': '🔗',
'图谱匹配': '🕸️',
'Vector Match': '🎯',
'Keyword Match': '🔤',
'Adjacent Chunk Match': '📌',
'History Match': '📜',
'Parent Chunk Match': '⬆️',
'Relation Chunk Match': '🔗',
'Graph Match': '🕸️',
};
// Get icon for a tool name
@@ -76,20 +76,20 @@ const toggleResult = (chunkId: string) => {
const getRelevanceClass = (level: RelevanceLevel): string => {
const classMap: Record<RelevanceLevel, string> = {
'高相关': 'high',
'中相关': 'medium',
'低相关': 'low',
'弱相关': 'weak',
'High Relevance': 'high',
'Medium Relevance': 'medium',
'Low Relevance': 'low',
'Weak Relevance': 'weak',
};
return classMap[level] || 'weak';
};
const getRelevanceLabel = (level: RelevanceLevel): string => {
const labelMap: Record<RelevanceLevel, string> = {
'高相关': t('chat.relevanceHigh'),
'中相关': t('chat.relevanceMedium'),
'低相关': t('chat.relevanceLow'),
'弱相关': t('chat.relevanceWeak'),
'High Relevance': t('chat.relevanceHigh'),
'Medium Relevance': t('chat.relevanceMedium'),
'Low Relevance': t('chat.relevanceLow'),
'Weak Relevance': t('chat.relevanceWeak'),
};
return labelMap[level] || level;
};
@@ -98,20 +98,20 @@ const hasOtherParams = computed(() => {
const getRelevanceClass = (level: RelevanceLevel): string => {
const classMap: Record<RelevanceLevel, string> = {
'高相关': 'high',
'中相关': 'medium',
'低相关': 'low',
'弱相关': 'weak',
'High Relevance': 'high',
'Medium Relevance': 'medium',
'Low Relevance': 'low',
'Weak Relevance': 'weak',
};
return classMap[level] || 'weak';
};
const getRelevanceLabel = (level: RelevanceLevel): string => {
const labelMap: Record<RelevanceLevel, string> = {
'高相关': t('chat.relevanceHigh'),
'中相关': t('chat.relevanceMedium'),
'低相关': t('chat.relevanceLow'),
'弱相关': t('chat.relevanceWeak'),
'High Relevance': t('chat.relevanceHigh'),
'Medium Relevance': t('chat.relevanceMedium'),
'Low Relevance': t('chat.relevanceLow'),
'Weak Relevance': t('chat.relevanceWeak'),
};
return labelMap[level] || level;
};
+16 -15
View File
@@ -518,7 +518,7 @@ func (e *AgentEngine) executeLoop(
common.PipelineError(ctx, "Agent", "final_answer_failed", map[string]interface{}{
"error": err.Error(),
})
state.FinalAnswer = "抱歉,我无法生成完整的答案。"
state.FinalAnswer = "Sorry, I was unable to generate a complete answer."
}
state.IsComplete = true
}
@@ -692,13 +692,13 @@ func (e *AgentEngine) streamReflectionToEventBus(
sessionID string,
) (string, error) {
// Simplified reflection without BuildReflectionPrompt
reflectionPrompt := fmt.Sprintf(`请评估刚才调用工具 %s 的结果,并决定下一步行动。
reflectionPrompt := fmt.Sprintf(`Evaluate the result of calling tool %s and decide the next action.
工具返回: %s
Tool returned: %s
思考:
1. 结果是否满足需求?
2. 下一步应该做什么?`, toolName, result)
Think:
1. Does the result satisfy the requirement?
2. What should be done next?`, toolName, result)
messages := []chat.Message{
{Role: "user", Content: reflectionPrompt},
@@ -853,7 +853,7 @@ func (e *AgentEngine) streamFinalAnswerToEventBus(
toolResultCount++
messages = append(messages, chat.Message{
Role: "user",
Content: fmt.Sprintf("工具 %s 返回: %s", toolCall.Name, toolCall.Result.Output),
Content: fmt.Sprintf("Tool %s returned: %s", toolCall.Name, toolCall.Result.Output),
})
logger.Debugf(ctx, "[Agent][FinalAnswer] Added tool result [Step-%d][Tool-%d]: %s (output: %d chars)",
stepIdx+1, toolIdx+1, toolCall.Name, len(toolCall.Result.Output))
@@ -864,17 +864,18 @@ func (e *AgentEngine) streamFinalAnswerToEventBus(
len(messages), toolResultCount)
// Add final answer prompt
finalPrompt := fmt.Sprintf(`基于上述工具调用结果,请为用户问题生成完整答案。
finalPrompt := fmt.Sprintf(`Based on the above tool call results, generate a complete answer for the user's question.
用户问题: %s
User question: %s
要求:
1. 基于实际检索到的内容回答
2. 清晰标注信息来源 (chunk_id, 文档名)
3. 结构化组织答案
4. 如信息不足,诚实说明
Requirements:
1. Answer based on the actually retrieved content
2. Clearly cite information sources (chunk_id, document name)
3. Organize the answer in a structured format
4. If information is insufficient, honestly state so
5. IMPORTANT: Respond in the same language as the user's question
现在请生成最终答案:`, query)
Now generate the final answer:`, query)
messages = append(messages, chat.Message{
Role: "user",
+10 -10
View File
@@ -228,18 +228,18 @@ func (t *DataAnalysisTool) executeSingleQuery(ctx context.Context, sqlQuery stri
func (t *DataAnalysisTool) formatQueryResults(results []map[string]string, query string) string {
var output strings.Builder
output.WriteString("=== DuckDB 查询结果 ===\n\n")
output.WriteString(fmt.Sprintf("执行的SQL: %s\n\n", query))
output.WriteString(fmt.Sprintf("返回 %d 行数据\n\n", len(results)))
output.WriteString("=== DuckDB Query Results ===\n\n")
output.WriteString(fmt.Sprintf("Executed SQL: %s\n\n", query))
output.WriteString(fmt.Sprintf("Returned %d rows\n\n", len(results)))
if len(results) == 0 {
output.WriteString("未找到匹配的记录。\n")
output.WriteString("No matching records found.\n")
return output.String()
}
output.WriteString("=== 数据详情 ===\n\n")
output.WriteString("=== Data Details ===\n\n")
if len(results) > 10 {
output.WriteString(fmt.Sprintf("显示了所有 %d 条记录。建议使用 LIMIT 子句限制结果数量以提高性能。\n\n", len(results)))
output.WriteString(fmt.Sprintf("Showing all %d records. Consider using a LIMIT clause to restrict the result count for better performance.\n\n", len(results)))
}
// Write each record as a separate JSON line
@@ -474,10 +474,10 @@ func (t *DataAnalysisTool) TableName(knowledge *types.Knowledge) string {
// buildSchemaDescription builds a formatted schema description
func (t *TableSchema) Description() string {
var builder strings.Builder
builder.WriteString(fmt.Sprintf("表名: %s\n", t.TableName))
builder.WriteString(fmt.Sprintf("列数: %d\n", len(t.Columns)))
builder.WriteString(fmt.Sprintf("行数: %d\n\n", t.RowCount))
builder.WriteString("列信息:\n")
builder.WriteString(fmt.Sprintf("Table name: %s\n", t.TableName))
builder.WriteString(fmt.Sprintf("Columns: %d\n", len(t.Columns)))
builder.WriteString(fmt.Sprintf("Rows: %d\n\n", t.RowCount))
builder.WriteString("Column info:\n")
for _, col := range t.Columns {
builder.WriteString(fmt.Sprintf("- %s (%s)\n", col.Name, col.Type))
+7 -7
View File
@@ -284,20 +284,20 @@ func (t *DatabaseQueryTool) formatQueryResults(
results []map[string]interface{},
query string,
) string {
output := "=== 查询结果 ===\n\n"
output += fmt.Sprintf("执行的SQL: %s\n\n", query)
output += fmt.Sprintf("返回 %d 行数据\n\n", len(results))
output := "=== Query Results ===\n\n"
output += fmt.Sprintf("Executed SQL: %s\n\n", query)
output += fmt.Sprintf("Returned %d rows\n\n", len(results))
if len(results) == 0 {
output += "未找到匹配的记录。\n"
output += "No matching records found.\n"
return output
}
output += "=== 数据详情 ===\n\n"
output += "=== Data Details ===\n\n"
// Format each row
for i, row := range results {
output += fmt.Sprintf("--- 记录 #%d ---\n", i+1)
output += fmt.Sprintf("--- Record #%d ---\n", i+1)
for _, col := range columns {
value := row[col]
// Format the value
@@ -325,7 +325,7 @@ func (t *DatabaseQueryTool) formatQueryResults(
// Add summary statistics if applicable
if len(results) > 10 {
output += fmt.Sprintf("注意: 显示了前 %d 条记录,共 %d 条。建议使用 LIMIT 子句限制结果数量。\n", len(results), len(results))
output += fmt.Sprintf("Note: Showing %d records out of %d total. Consider using a LIMIT clause to restrict the result count.\n", len(results), len(results))
}
return output
+25 -25
View File
@@ -121,7 +121,7 @@ func (t *GetDocumentInfoTool) Execute(ctx context.Context, args json.RawMessage)
if err != nil {
mu.Lock()
results[id] = &docInfo{
err: fmt.Errorf("无法获取文档信息: %v", err),
err: fmt.Errorf("failed to get document info: %v", err),
}
mu.Unlock()
return
@@ -131,7 +131,7 @@ func (t *GetDocumentInfoTool) Execute(ctx context.Context, args json.RawMessage)
if !t.searchTargets.ContainsKB(knowledge.KnowledgeBaseID) {
mu.Lock()
results[id] = &docInfo{
err: fmt.Errorf("知识库 %s 不可访问", knowledge.KnowledgeBaseID),
err: fmt.Errorf("knowledge base %s is not accessible", knowledge.KnowledgeBaseID),
}
mu.Unlock()
return
@@ -146,7 +146,7 @@ func (t *GetDocumentInfoTool) Execute(ctx context.Context, args json.RawMessage)
if err != nil {
mu.Lock()
results[id] = &docInfo{
err: fmt.Errorf("无法获取文档信息: %v", err),
err: fmt.Errorf("failed to get document info: %v", err),
}
mu.Unlock()
return
@@ -180,16 +180,16 @@ func (t *GetDocumentInfoTool) Execute(ctx context.Context, args json.RawMessage)
if len(successDocs) == 0 {
return &types.ToolResult{
Success: false,
Error: fmt.Sprintf("无法获取任何文档信息。错误: %v", errors),
Error: fmt.Sprintf("Failed to retrieve any document info. Errors: %v", errors),
}, fmt.Errorf("all document retrievals failed")
}
// Format output
output := "=== 文档信息 ===\n\n"
output += fmt.Sprintf("成功获取 %d / %d 个文档信息\n\n", len(successDocs), len(knowledgeIDs))
output := "=== Document Info ===\n\n"
output += fmt.Sprintf("Successfully retrieved %d / %d documents\n\n", len(successDocs), len(knowledgeIDs))
if len(errors) > 0 {
output += "=== 部分失败 ===\n"
output += "=== Partial Failures ===\n"
for _, errMsg := range errors {
output += fmt.Sprintf(" - %s\n", errMsg)
}
@@ -200,28 +200,28 @@ func (t *GetDocumentInfoTool) Execute(ctx context.Context, args json.RawMessage)
for i, doc := range successDocs {
k := doc.knowledge
output += fmt.Sprintf("【文档 #%d\n", i+1)
output += fmt.Sprintf(" ID: %s\n", k.ID)
output += fmt.Sprintf(" 标题: %s\n", k.Title)
output += fmt.Sprintf("[Document #%d]\n", i+1)
output += fmt.Sprintf(" ID: %s\n", k.ID)
output += fmt.Sprintf(" Title: %s\n", k.Title)
if k.Description != "" {
output += fmt.Sprintf(" 描述: %s\n", k.Description)
output += fmt.Sprintf(" Description: %s\n", k.Description)
}
output += fmt.Sprintf(" 来源: %s\n", formatSource(k.Type, k.Source))
output += fmt.Sprintf(" Source: %s\n", formatSource(k.Type, k.Source))
if k.FileName != "" {
output += fmt.Sprintf(" 文件名: %s\n", k.FileName)
output += fmt.Sprintf(" 文件类型: %s\n", k.FileType)
output += fmt.Sprintf(" 文件大小: %s\n", formatFileSize(k.FileSize))
output += fmt.Sprintf(" File Name: %s\n", k.FileName)
output += fmt.Sprintf(" File Type: %s\n", k.FileType)
output += fmt.Sprintf(" File Size: %s\n", formatFileSize(k.FileSize))
}
output += fmt.Sprintf(" 处理状态: %s\n", formatParseStatus(k.ParseStatus))
output += fmt.Sprintf(" 分块数量: %d\n", doc.chunkCount)
output += fmt.Sprintf(" Parse Status: %s\n", formatParseStatus(k.ParseStatus))
output += fmt.Sprintf(" Chunk Count: %d\n", doc.chunkCount)
if k.Metadata != nil {
if metadata, err := k.Metadata.Map(); err == nil && len(metadata) > 0 {
output += " 元数据:\n"
output += " Metadata:\n"
for key, value := range metadata {
output += fmt.Sprintf(" - %s: %v\n", key, value)
}
@@ -268,11 +268,11 @@ func (t *GetDocumentInfoTool) Execute(ctx context.Context, args json.RawMessage)
func formatSource(knowledgeType, source string) string {
switch knowledgeType {
case "file":
return "文件上传"
return "File Upload"
case "url":
return fmt.Sprintf("URL: %s", source)
case "passage":
return "文本输入"
return "Text Input"
default:
return knowledgeType
}
@@ -280,7 +280,7 @@ func formatSource(knowledgeType, source string) string {
func formatFileSize(size int64) string {
if size == 0 {
return "未知"
return "Unknown"
}
const unit = 1024
if size < unit {
@@ -297,13 +297,13 @@ func formatFileSize(size int64) string {
func formatParseStatus(status string) string {
switch status {
case "pending":
return "⏳ 待处理"
return "Pending"
case "processing":
return "🔄 处理中"
return "Processing"
case "completed", "success":
return "✅ 已完成"
return "Completed"
case "failed":
return "❌ 失败"
return "Failed"
default:
return status
}
+7 -7
View File
@@ -1178,7 +1178,7 @@ func (t *KnowledgeSearchTool) formatOutput(
}
// Add statistics and recommendations for each knowledge
output += "\n=== 检索统计与建议 ===\n\n"
output += "\n=== Retrieval Statistics ===\n\n"
for knowledgeID, retrievedChunks := range knowledgeChunkMap {
totalChunks := knowledgeTotalMap[knowledgeID]
retrievedCount := len(retrievedChunks)
@@ -1188,10 +1188,10 @@ func (t *KnowledgeSearchTool) formatOutput(
percentage := float64(retrievedCount) / float64(totalChunks) * 100
remaining := totalChunks - int64(retrievedCount)
output += fmt.Sprintf("文档: %s (%s)\n", title, knowledgeID)
output += fmt.Sprintf(" Chunk: %d\n", totalChunks)
output += fmt.Sprintf(" 已召回: %d (%.1f%%)\n", retrievedCount, percentage)
output += fmt.Sprintf(" 未召回: %d\n", remaining)
output += fmt.Sprintf("Document: %s (%s)\n", title, knowledgeID)
output += fmt.Sprintf(" Total Chunks: %d\n", totalChunks)
output += fmt.Sprintf(" Retrieved: %d (%.1f%%)\n", retrievedCount, percentage)
output += fmt.Sprintf(" Remaining: %d\n", remaining)
}
}
@@ -1255,10 +1255,10 @@ func (t *KnowledgeSearchTool) getEnrichedPassage(ctx context.Context, result *ty
var imageTexts []string
for _, img := range imageInfos {
if img.Caption != "" {
imageTexts = append(imageTexts, fmt.Sprintf("图片描述: %s", img.Caption))
imageTexts = append(imageTexts, fmt.Sprintf("Image Caption: %s", img.Caption))
}
if img.OCRText != "" {
imageTexts = append(imageTexts, fmt.Sprintf("图片文本: %s", img.OCRText))
imageTexts = append(imageTexts, fmt.Sprintf("Image Text: %s", img.OCRText))
}
}
+18 -18
View File
@@ -15,7 +15,7 @@ var listKnowledgeChunksTool = BaseTool{
description: `Retrieve full chunk content for a document by knowledge_id.
## Use After grep_chunks or knowledge_search:
1. grep_chunks(["keyword", "变体"]) → get knowledge_id
1. grep_chunks(["keyword", "variant"]) → get knowledge_id
2. list_knowledge_chunks(knowledge_id) → read full content
## When to Use:
@@ -245,52 +245,52 @@ func (t *ListKnowledgeChunksTool) buildOutput(
chunks []*types.Chunk,
) string {
builder := &strings.Builder{}
builder.WriteString("=== 知识文档分块 ===\n\n")
builder.WriteString("=== Knowledge Document Chunks ===\n\n")
if knowledgeTitle != "" {
fmt.Fprintf(builder, "文档: %s (%s)\n", knowledgeTitle, knowledgeID)
fmt.Fprintf(builder, "Document: %s (%s)\n", knowledgeTitle, knowledgeID)
} else {
fmt.Fprintf(builder, "文档 ID: %s\n", knowledgeID)
fmt.Fprintf(builder, "Document ID: %s\n", knowledgeID)
}
fmt.Fprintf(builder, "总分块数: %d\n", total)
fmt.Fprintf(builder, "Total chunks: %d\n", total)
if fetched == 0 {
builder.WriteString("未找到任何分块,请确认文档是否已完成解析。\n")
builder.WriteString("No chunks found. Please confirm the document has been parsed.\n")
if total > 0 {
builder.WriteString("文档存在但当前页数据为空,请检查分页参数。\n")
builder.WriteString("Document exists but the current page is empty. Please check pagination parameters.\n")
}
return builder.String()
}
fmt.Fprintf(
builder,
"本次拉取: %d 条, 检索范围: %d - %d\n\n",
"Fetched: %d chunks, range: %d - %d\n\n",
fetched,
chunks[0].ChunkIndex,
chunks[len(chunks)-1].ChunkIndex,
)
builder.WriteString("=== 分块内容预览 ===\n\n")
builder.WriteString("=== Chunk Content Preview ===\n\n")
for idx, c := range chunks {
fmt.Fprintf(builder, "Chunk #%d (Index %d)\n", idx+1, c.ChunkIndex+1)
fmt.Fprintf(builder, " chunk_id: %s\n", c.ID)
fmt.Fprintf(builder, " 类型: %s\n", c.ChunkType)
fmt.Fprintf(builder, " 内容: %s\n", summarizeContent(c.Content))
fmt.Fprintf(builder, " Type: %s\n", c.ChunkType)
fmt.Fprintf(builder, " Content: %s\n", summarizeContent(c.Content))
// 输出关联的图片信息
// Output associated image information
if c.ImageInfo != "" {
var imageInfos []types.ImageInfo
if err := json.Unmarshal([]byte(c.ImageInfo), &imageInfos); err == nil && len(imageInfos) > 0 {
fmt.Fprintf(builder, " 关联图片 (%d):\n", len(imageInfos))
fmt.Fprintf(builder, " Associated images (%d):\n", len(imageInfos))
for imgIdx, img := range imageInfos {
fmt.Fprintf(builder, " 图片 %d:\n", imgIdx+1)
fmt.Fprintf(builder, " Image %d:\n", imgIdx+1)
if img.URL != "" {
fmt.Fprintf(builder, " URL: %s\n", img.URL)
}
if img.Caption != "" {
fmt.Fprintf(builder, " 描述: %s\n", img.Caption)
fmt.Fprintf(builder, " Caption: %s\n", img.Caption)
}
if img.OCRText != "" {
fmt.Fprintf(builder, " OCR文本: %s\n", img.OCRText)
fmt.Fprintf(builder, " OCR Text: %s\n", img.OCRText)
}
}
}
@@ -299,7 +299,7 @@ func (t *ListKnowledgeChunksTool) buildOutput(
}
if int64(fetched) < total {
builder.WriteString("提示:文档仍有更多分块,可调整 offset 或多次调用以获取全部内容。\n")
builder.WriteString("Note: The document has more chunks. Adjust offset or make multiple calls to retrieve all content.\n")
}
return builder.String()
@@ -309,7 +309,7 @@ func (t *ListKnowledgeChunksTool) buildOutput(
func summarizeContent(content string) string {
cleaned := strings.TrimSpace(content)
if cleaned == "" {
return "(空内容)"
return "(empty)"
}
return strings.TrimSpace(string(cleaned))
+34 -34
View File
@@ -57,7 +57,7 @@ If KB is not configured with graph, tool will return regular search results.
// QueryKnowledgeGraphInput defines the input parameters for query knowledge graph tool
type QueryKnowledgeGraphInput struct {
KnowledgeBaseIDs []string `json:"knowledge_base_ids" jsonschema:"Array of knowledge base IDs to query"`
Query string `json:"query" jsonschema:"查询内容(实体名称或查询文本)"`
Query string `json:"query" jsonschema:"Query content (entity name or query text)"`
}
// QueryKnowledgeGraphTool queries the knowledge graph for entities and relationships
@@ -135,7 +135,7 @@ func (t *QueryKnowledgeGraphTool) Execute(ctx context.Context, args json.RawMess
kb, err := t.knowledgeService.GetKnowledgeBaseByID(ctx, id)
if err != nil {
mu.Lock()
kbResults[id] = &graphQueryResult{kbID: id, err: fmt.Errorf("获取知识库失败: %v", err)}
kbResults[id] = &graphQueryResult{kbID: id, err: fmt.Errorf("failed to get knowledge base: %v", err)}
mu.Unlock()
return
}
@@ -143,7 +143,7 @@ func (t *QueryKnowledgeGraphTool) Execute(ctx context.Context, args json.RawMess
// Check if graph extraction is enabled
if kb.ExtractConfig == nil || (len(kb.ExtractConfig.Nodes) == 0 && len(kb.ExtractConfig.Relations) == 0) {
mu.Lock()
kbResults[id] = &graphQueryResult{kbID: id, err: fmt.Errorf("未配置知识图谱抽取")}
kbResults[id] = &graphQueryResult{kbID: id, err: fmt.Errorf("graph extraction not configured")}
mu.Unlock()
return
}
@@ -152,7 +152,7 @@ func (t *QueryKnowledgeGraphTool) Execute(ctx context.Context, args json.RawMess
results, err := t.knowledgeService.HybridSearch(ctx, id, searchParams)
if err != nil {
mu.Lock()
kbResults[id] = &graphQueryResult{kbID: id, kb: kb, err: fmt.Errorf("查询失败: %v", err)}
kbResults[id] = &graphQueryResult{kbID: id, kb: kb, err: fmt.Errorf("query failed: %v", err)}
mu.Unlock()
return
}
@@ -206,7 +206,7 @@ func (t *QueryKnowledgeGraphTool) Execute(ctx context.Context, args json.RawMess
if len(allResults) == 0 {
return &types.ToolResult{
Success: true,
Output: "未找到相关的图谱信息。",
Output: "No relevant graph information found.",
Data: map[string]interface{}{
"knowledge_base_ids": input.KnowledgeBaseIDs,
"query": query,
@@ -218,13 +218,13 @@ func (t *QueryKnowledgeGraphTool) Execute(ctx context.Context, args json.RawMess
}
// Format output with enhanced graph information
output := "=== 知识图谱查询 ===\n\n"
output += fmt.Sprintf("📊 查询: %s\n", query)
output += fmt.Sprintf("🎯 目标知识库: %v\n", input.KnowledgeBaseIDs)
output += fmt.Sprintf("✓ 找到 %d 条相关结果(已去重)\n\n", len(allResults))
output := "=== Knowledge Graph Query ===\n\n"
output += fmt.Sprintf("📊 Query: %s\n", query)
output += fmt.Sprintf("🎯 Target Knowledge Bases: %v\n", input.KnowledgeBaseIDs)
output += fmt.Sprintf("✓ Found %d relevant results (deduplicated)\n\n", len(allResults))
if len(errors) > 0 {
output += "=== ⚠️ 部分失败 ===\n"
output += "=== ⚠️ Partial Failures ===\n"
for _, errMsg := range errors {
output += fmt.Sprintf(" - %s\n", errMsg)
}
@@ -233,16 +233,16 @@ func (t *QueryKnowledgeGraphTool) Execute(ctx context.Context, args json.RawMess
// Display graph configuration status
hasGraphConfig := false
output += "=== 📈 图谱配置状态 ===\n\n"
output += "=== 📈 Graph Configuration Status ===\n\n"
for kbID, config := range graphConfigs {
hasGraphConfig = true
output += fmt.Sprintf("知识库【%s:\n", kbID)
output += fmt.Sprintf("Knowledge Base [%s]:\n", kbID)
nodes, _ := config["nodes"].([]interface{})
relations, _ := config["relations"].([]interface{})
if len(nodes) > 0 {
output += fmt.Sprintf(" ✓ 实体类型 (%d): ", len(nodes))
output += fmt.Sprintf(" ✓ Entity Types (%d): ", len(nodes))
nodeNames := make([]string, 0, len(nodes))
for _, n := range nodes {
if nodeMap, ok := n.(map[string]interface{}); ok {
@@ -253,11 +253,11 @@ func (t *QueryKnowledgeGraphTool) Execute(ctx context.Context, args json.RawMess
}
output += fmt.Sprintf("%v\n", nodeNames)
} else {
output += " ⚠️ 未配置实体类型\n"
output += " ⚠️ No entity types configured\n"
}
if len(relations) > 0 {
output += fmt.Sprintf(" ✓ 关系类型 (%d): ", len(relations))
output += fmt.Sprintf(" ✓ Relationship Types (%d): ", len(relations))
relNames := make([]string, 0, len(relations))
for _, r := range relations {
if relMap, ok := r.(map[string]interface{}); ok {
@@ -268,31 +268,31 @@ func (t *QueryKnowledgeGraphTool) Execute(ctx context.Context, args json.RawMess
}
output += fmt.Sprintf("%v\n", relNames)
} else {
output += " ⚠️ 未配置关系类型\n"
output += " ⚠️ No relationship types configured\n"
}
output += "\n"
}
if !hasGraphConfig {
output += "⚠️ 所查询的知识库均未配置图谱抽取\n"
output += "💡 提示: 需要在知识库设置中配置实体和关系类型\n\n"
output += "⚠️ None of the queried knowledge bases have graph extraction configured\n"
output += "💡 Hint: Configure entity and relationship types in knowledge base settings\n\n"
}
// Display result counts by KB
if len(kbCounts) > 0 {
output += "=== 📚 知识库覆盖 ===\n"
output += "=== 📚 Knowledge Base Coverage ===\n"
for kbID, count := range kbCounts {
output += fmt.Sprintf(" - %s: %d 条结果\n", kbID, count)
output += fmt.Sprintf(" - %s: %d results\n", kbID, count)
}
output += "\n"
}
// Display search results
output += "=== 🔍 查询结果 ===\n\n"
output += "=== 🔍 Query Results ===\n\n"
if !hasGraphConfig {
output += "💡 当前返回相关文档片段(知识库未配置图谱)\n\n"
output += "💡 Returning relevant document chunks (knowledge base has no graph configuration)\n\n"
} else {
output += "💡 基于图谱配置的相关内容检索\n\n"
output += "💡 Content retrieval based on graph configuration\n\n"
}
formattedResults := make([]map[string]interface{}, 0, len(allResults))
@@ -305,15 +305,15 @@ func (t *QueryKnowledgeGraphTool) Execute(ctx context.Context, args json.RawMess
if i > 0 {
output += "\n"
}
output += fmt.Sprintf("【来源文档: %s\n\n", result.KnowledgeTitle)
output += fmt.Sprintf("[Source Document: %s]\n\n", result.KnowledgeTitle)
}
relevanceLevel := GetRelevanceLevel(result.Score)
output += fmt.Sprintf("结果 #%d:\n", i+1)
output += fmt.Sprintf(" 📍 相关度: %.2f (%s)\n", result.Score, relevanceLevel)
output += fmt.Sprintf(" 🔗 匹配方式: %s\n", FormatMatchType(result.MatchType))
output += fmt.Sprintf(" 📄 内容: %s\n", result.Content)
output += fmt.Sprintf("Result #%d:\n", i+1)
output += fmt.Sprintf(" 📍 Relevance: %.2f (%s)\n", result.Score, relevanceLevel)
output += fmt.Sprintf(" 🔗 Match Type: %s\n", FormatMatchType(result.MatchType))
output += fmt.Sprintf(" 📄 Content: %s\n", result.Content)
output += fmt.Sprintf(" 🆔 chunk_id: %s\n\n", result.ID)
formattedResults = append(formattedResults, map[string]interface{}{
@@ -328,14 +328,14 @@ func (t *QueryKnowledgeGraphTool) Execute(ctx context.Context, args json.RawMess
})
}
output += "=== 💡 使用提示 ===\n"
output += "- ✓ 结果已跨知识库去重并按相关度排序\n"
output += "- ✓ 使用 get_chunk_detail 获取完整内容\n"
output += "- ✓ 使用 list_knowledge_chunks 探索上下文\n"
output += "=== 💡 Tips ===\n"
output += "- ✓ Results are deduplicated across knowledge bases and sorted by relevance\n"
output += "- ✓ Use get_chunk_detail to get full content\n"
output += "- ✓ Use list_knowledge_chunks to explore context\n"
if !hasGraphConfig {
output += "- ⚠️ 配置图谱抽取以获得更精准的实体关系结果\n"
output += "- ⚠️ Configure graph extraction for more precise entity-relationship results\n"
}
output += "- ⏳ 完整的图查询语言(Cypher)支持开发中\n"
output += "- ⏳ Full graph query language (Cypher) support is under development\n"
// Build structured graph data for frontend visualization
graphData := buildGraphVisualizationData(allResults, graphConfigs)
+30 -30
View File
@@ -173,7 +173,7 @@ func (t *TodoWriteTool) Execute(ctx context.Context, args json.RawMessage) (*typ
}
if input.Task == "" {
input.Task = "未提供任务描述"
input.Task = "No task description provided"
}
// Parse plan steps
@@ -227,17 +227,17 @@ func getStringArrayField(m map[string]interface{}, key string) []string {
// generatePlanOutput generates a formatted plan output
func generatePlanOutput(task string, steps []PlanStep) string {
output := "计划已创建\n\n"
output += fmt.Sprintf("**任务**: %s\n\n", task)
output := "Plan created\n\n"
output += fmt.Sprintf("**Task**: %s\n\n", task)
if len(steps) == 0 {
output += "注意:未提供具体步骤。建议创建3-7个检索任务以系统化研究。\n\n"
output += "建议的检索流程(专注于检索任务,不包含总结):\n"
output += "1. 使用 grep_chunks 搜索关键词定位相关文档\n"
output += "2. 使用 knowledge_search 进行语义搜索获取相关内容\n"
output += "3. 使用 list_knowledge_chunks 获取关键文档的完整内容\n"
output += "4. 使用 web_search 获取补充信息(如需要)\n"
output += "\n注意:总结和综合由 thinking 工具处理,不要在此处添加总结任务。\n"
output += "Note: No specific steps provided. It is recommended to create 3-7 retrieval tasks for systematic research.\n\n"
output += "Suggested retrieval workflow (focused on retrieval tasks, excluding summarization):\n"
output += "1. Use grep_chunks to search keywords and locate relevant documents\n"
output += "2. Use knowledge_search for semantic search to retrieve relevant content\n"
output += "3. Use list_knowledge_chunks to get the full content of key documents\n"
output += "4. Use web_search to get supplementary information (if needed)\n"
output += "\nNote: Summarization and synthesis are handled by the thinking tool. Do not add summarization tasks here.\n"
return output
}
@@ -258,7 +258,7 @@ func generatePlanOutput(task string, steps []PlanStep) string {
totalCount := len(steps)
remainingCount := pendingCount + inProgressCount
output += "**计划步骤**:\n\n"
output += "**Plan Steps**:\n\n"
// Display all steps in order
for i, step := range steps {
@@ -266,32 +266,32 @@ func generatePlanOutput(task string, steps []PlanStep) string {
}
// Add summary and emphasis on remaining tasks
output += "\n=== 任务进度 ===\n"
output += fmt.Sprintf("总计: %d 个任务\n", totalCount)
output += fmt.Sprintf("✅ 已完成: %d\n", completedCount)
output += fmt.Sprintf("🔄 进行中: %d\n", inProgressCount)
output += fmt.Sprintf("⏳ 待处理: %d\n", pendingCount)
output += "\n=== Task Progress ===\n"
output += fmt.Sprintf("Total: %d tasks\n", totalCount)
output += fmt.Sprintf("✅ Completed: %d\n", completedCount)
output += fmt.Sprintf("🔄 In Progress: %d\n", inProgressCount)
output += fmt.Sprintf("⏳ Pending: %d\n", pendingCount)
output += "\n=== ⚠️ 重要提醒 ===\n"
output += "\n=== ⚠️ Important Reminder ===\n"
if remainingCount > 0 {
output += fmt.Sprintf("**还有 %d 个任务未完成!**\n\n", remainingCount)
output += "**必须完成所有任务后才能总结或得出结论。**\n\n"
output += "下一步操作:\n"
output += fmt.Sprintf("**%d tasks remaining!**\n\n", remainingCount)
output += "**All tasks must be completed before summarizing or drawing conclusions.**\n\n"
output += "Next steps:\n"
if inProgressCount > 0 {
output += "- 继续完成当前进行中的任务\n"
output += "- Continue completing tasks currently in progress\n"
}
if pendingCount > 0 {
output += fmt.Sprintf("- 开始处理 %d 个待处理任务\n", pendingCount)
output += "- 按顺序完成每个任务,不要跳过\n"
output += fmt.Sprintf("- Start processing %d pending tasks\n", pendingCount)
output += "- Complete each task in order, do not skip\n"
}
output += "- 完成每个任务后,更新 todo_write 标记为 completed\n"
output += "- 只有在所有任务完成后,才能生成最终总结\n"
output += "- After completing each task, update todo_write to mark it as completed\n"
output += "- Only generate the final summary after all tasks are completed\n"
} else {
output += "✅ **所有任务已完成!**\n\n"
output += "现在可以:\n"
output += "- 综合所有任务的发现\n"
output += "- 生成完整的最终答案或报告\n"
output += "- 确保所有方面都已充分研究\n"
output += "✅ **All tasks completed!**\n\n"
output += "You can now:\n"
output += "- Synthesize findings from all tasks\n"
output += "- Generate a complete final answer or report\n"
output += "- Ensure all aspects have been thoroughly researched\n"
}
return output
+12 -12
View File
@@ -52,13 +52,13 @@ type ToolExecutor interface {
func GetRelevanceLevel(score float64) string {
switch {
case score >= 0.8:
return "高相关"
return "High Relevance"
case score >= 0.6:
return "中相关"
return "Medium Relevance"
case score >= 0.4:
return "低相关"
return "Low Relevance"
default:
return "弱相关"
return "Weak Relevance"
}
}
@@ -66,20 +66,20 @@ func GetRelevanceLevel(score float64) string {
func FormatMatchType(mt types.MatchType) string {
switch mt {
case types.MatchTypeEmbedding:
return "向量匹配"
return "Vector Match"
case types.MatchTypeKeywords:
return "关键词匹配"
return "Keyword Match"
case types.MatchTypeNearByChunk:
return "相邻块匹配"
return "Adjacent Chunk Match"
case types.MatchTypeHistory:
return "历史匹配"
return "History Match"
case types.MatchTypeParentChunk:
return "父块匹配"
return "Parent Chunk Match"
case types.MatchTypeRelationChunk:
return "关系块匹配"
return "Relation Chunk Match"
case types.MatchTypeGraph:
return "图谱匹配"
return "Graph Match"
default:
return fmt.Sprintf("未知类型(%d)", mt)
return fmt.Sprintf("Unknown Type(%d)", mt)
}
}
+9 -9
View File
@@ -45,13 +45,13 @@ var webFetchTool = BaseTool{
// WebFetchInput defines the input parameters for web fetch tool
type WebFetchInput struct {
Items []WebFetchItem `json:"items" jsonschema:"批量抓取任务,每项包含 url prompt"`
Items []WebFetchItem `json:"items" jsonschema:"Batch fetch tasks, each containing a url and prompt"`
}
// WebFetchItem represents a single web fetch task
type WebFetchItem struct {
URL string `json:"url" jsonschema:"待抓取的网页 URL,需来自 web_search 结果"`
Prompt string `json:"prompt" jsonschema:"分析该网页内容时使用的提示词"`
URL string `json:"url" jsonschema:"URL of the web page to fetch, should come from web_search results"`
Prompt string `json:"prompt" jsonschema:"Prompt for analyzing the fetched web page content"`
}
// webFetchParams is the parameters for the web fetch tool
@@ -147,7 +147,7 @@ func (t *WebFetchTool) Execute(ctx context.Context, args json.RawMessage) (*type
"prompt": p.Prompt,
"error": err.Error(),
},
output: fmt.Sprintf("URL: %s\n错误: %v\n\n", p.URL, err),
output: fmt.Sprintf("URL: %s\nError: %v\n\n", p.URL, err),
}
return
}
@@ -176,7 +176,7 @@ func (t *WebFetchTool) Execute(ctx context.Context, args json.RawMessage) (*type
if firstErr == nil {
firstErr = fmt.Errorf("fetch item %d returned nil", idx)
}
builder.WriteString(fmt.Sprintf("#%d: 无结果(内部错误)\n\n", idx+1))
builder.WriteString(fmt.Sprintf("#%d: No result (internal error)\n\n", idx+1))
continue
}
@@ -317,7 +317,7 @@ func (t *WebFetchTool) executeFetch(
htmlContent, method, err := t.fetchHTMLContent(ctx, vp)
if err != nil {
logger.Errorf(ctx, "[Tool][WebFetch] 获取页面失败 url=%s err=%v", vp.URL, err)
return fmt.Sprintf("URL: %s\n错误: %v\n", displayURL, err),
return fmt.Sprintf("URL: %s\nError: %v\n", displayURL, err),
map[string]interface{}{
"url": displayURL,
"prompt": vp.Prompt,
@@ -364,11 +364,11 @@ func (t *WebFetchTool) processWithLLM(ctx context.Context, params webFetchParams
return "", fmt.Errorf("chat model not available for web_fetch")
}
systemMessage := "你是一名擅长阅读网页内容的智能助手,请根据提供的网页文本回答用户需求,严禁编造未在文本中出现的信息。"
userTemplate := `用户请求:
systemMessage := "You are an intelligent assistant skilled at reading web page content. Answer the user's request based on the provided web page text. Never fabricate information that does not appear in the text."
userTemplate := `User request:
%s
网页内容:
Web page content:
%s`
messages := []chat.Message{
@@ -71,31 +71,31 @@ func (p *PluginIntoChatMessage) OnEvent(ctx context.Context,
pipelineWarn(ctx, "IntoChatMessage", "invalid_query", map[string]interface{}{
"session_id": chatManage.SessionID,
})
return ErrTemplateExecute.WithError(fmt.Errorf("用户查询包含非法内容"))
return ErrTemplateExecute.WithError(fmt.Errorf("user query contains invalid content"))
}
// Prepare weekday names
weekdayName := []string{"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"}
weekdayName := []string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}
var contextsBuilder strings.Builder
// Build contexts string based on FAQ priority strategy
if chatManage.FAQPriorityEnabled && len(faqResults) > 0 {
// Build structured context with FAQ prioritization
contextsBuilder.WriteString("### 资料来源 1:标准问答库 (FAQ)\n")
contextsBuilder.WriteString("【高置信度 - 请优先参考】\n")
contextsBuilder.WriteString("### Source 1: FAQ Knowledge Base\n")
contextsBuilder.WriteString("[High Confidence - Prioritize these results]\n")
for i, result := range faqResults {
passage := getEnrichedPassageForChat(ctx, result)
if hasHighConfidenceFAQ && i == 0 {
contextsBuilder.WriteString(fmt.Sprintf("[FAQ-%d] ⭐ 精准匹配: %s\n", i+1, passage))
contextsBuilder.WriteString(fmt.Sprintf("[FAQ-%d] Exact Match: %s\n", i+1, passage))
} else {
contextsBuilder.WriteString(fmt.Sprintf("[FAQ-%d] %s\n", i+1, passage))
}
}
if len(docResults) > 0 {
contextsBuilder.WriteString("\n### 资料来源 2:参考文档\n")
contextsBuilder.WriteString("【补充资料 - 仅在FAQ无法解答时参考】\n")
contextsBuilder.WriteString("\n### Source 2: Reference Documents\n")
contextsBuilder.WriteString("[Supplementary - Use only when FAQ cannot answer the question]\n")
for i, result := range docResults {
passage := getEnrichedPassageForChat(ctx, result)
contextsBuilder.WriteString(fmt.Sprintf("[DOC-%d] %s\n", i+1, passage))
@@ -208,10 +208,10 @@ func enrichContentWithImageInfo(ctx context.Context, content string, imageInfoJS
if found && imgInfo != nil {
replacement := match[0] + "\n"
if imgInfo.Caption != "" {
replacement += fmt.Sprintf("图片描述: %s\n", imgInfo.Caption)
replacement += fmt.Sprintf("Image Caption: %s\n", imgInfo.Caption)
}
if imgInfo.OCRText != "" {
replacement += fmt.Sprintf("图片文本: %s\n", imgInfo.OCRText)
replacement += fmt.Sprintf("Image Text: %s\n", imgInfo.OCRText)
}
content = strings.Replace(content, match[0], replacement, 1)
}
@@ -227,10 +227,10 @@ func enrichContentWithImageInfo(ctx context.Context, content string, imageInfoJS
var imgTexts []string
if imgInfo.Caption != "" {
imgTexts = append(imgTexts, fmt.Sprintf("图片 %s 的描述信息: %s", imgInfo.URL, imgInfo.Caption))
imgTexts = append(imgTexts, fmt.Sprintf("Image %s caption: %s", imgInfo.URL, imgInfo.Caption))
}
if imgInfo.OCRText != "" {
imgTexts = append(imgTexts, fmt.Sprintf("图片 %s 的文本: %s", imgInfo.URL, imgInfo.OCRText))
imgTexts = append(imgTexts, fmt.Sprintf("Image %s text: %s", imgInfo.URL, imgInfo.OCRText))
}
if len(imgTexts) > 0 {
@@ -243,7 +243,7 @@ func enrichContentWithImageInfo(ctx context.Context, content string, imageInfoJS
if content != "" {
content += "\n\n"
}
content += "附加图片信息:\n" + strings.Join(additionalImageTexts, "\n")
content += "Additional Image Info:\n" + strings.Join(additionalImageTexts, "\n")
}
pipelineInfo(ctx, "IntoChatMessage", "image_enrich_summary", map[string]interface{}{
@@ -218,9 +218,9 @@ func formatConversationHistory(historyList []*types.History) string {
var builder strings.Builder
for _, h := range historyList {
builder.WriteString("------BEGIN------\n")
builder.WriteString("用户的问题是:")
builder.WriteString("User question: ")
builder.WriteString(h.Query)
builder.WriteString("\n助手的回答是:")
builder.WriteString("\nAssistant answer: ")
builder.WriteString(h.Answer)
builder.WriteString("\n------END------\n")
}
+39 -37
View File
@@ -23,58 +23,60 @@ import (
)
const (
// tableDescriptionPromptTemplate 表格描述生成的 prompt 模板
tableDescriptionPromptTemplate = `你是一个数据分析专家。请根据以下表格的结构信息和数据样本,生成一段简洁的表格元数据描述(200-300字)。
// tableDescriptionPromptTemplate is the prompt template for generating table descriptions
tableDescriptionPromptTemplate = `You are a data analysis expert. Based on the following table structure information and data samples, generate a concise table metadata description (200-300 words).
表名: %s
Table name: %s
%s
%s
请从以下维度描述这个表格:
1. **数据主题**:这个表格记录的是什么类型的数据?(如:用户信息、销售记录、日志数据等)
2. **核心字段**:列出3-5个最重要的字段及其含义
3. **数据规模**:总行数和列数
4. **业务场景**:这个表格可能用于什么业务分析或应用场景?
5. **关键特征**:数据有什么显著特点?(如:包含地理位置、有分类标签、存在层级关系等)
Please describe the table from the following dimensions:
1. **Data Subject**: What type of data does this table record? (e.g., user information, sales records, log data, etc.)
2. **Core Fields**: List 3-5 most important fields and their meanings
3. **Data Scale**: Total number of rows and columns
4. **Business Scenarios**: What business analysis or application scenarios might this table be used for?
5. **Key Characteristics**: What notable features does the data have? (e.g., contains geographic locations, has category labels, has hierarchical relationships, etc.)
**重要提示**
- 不要输出具体的数据值或样本内容
- 使用概括性的描述,让用户能快速判断这个表格是否包含他们需要的信息
- 用简洁专业的语言,便于检索和理解`
**Important Notes**:
- Do not output specific data values or sample content
- Use general descriptions so users can quickly determine if this table contains the information they need
- Use concise and professional language for easy retrieval and understanding
- Write the description in the same language as the data content`
// columnDescriptionsPromptTemplate 列描述生成的 prompt 模板
columnDescriptionsPromptTemplate = `你是一个数据分析专家。请根据以下表格的结构信息和数据样本,为每一列生成结构化的描述信息。
// columnDescriptionsPromptTemplate is the prompt template for generating column descriptions
columnDescriptionsPromptTemplate = `You are a data analysis expert. Based on the following table structure information and data samples, generate structured description information for each column.
表名: %s
Table name: %s
%s
%s
请为每一列生成详细的描述,包含以下信息:
1. **字段含义**:这一列存储的是什么信息?(如:用户ID、订单金额、创建时间等)
2. **数据类型**:数据的类型和格式(如:整数、字符串、日期时间、布尔值等)
3. **业务用途**:这个字段在业务中的作用(如:用于用户识别、金额计算、时间排序等)
4. **数据特征**:数据的显著特点(如:唯一标识、可为空、有枚举值、有单位等)
Please generate a detailed description for each column, including the following information:
1. **Field Meaning**: What information does this column store? (e.g., user ID, order amount, creation time, etc.)
2. **Data Type**: The type and format of the data (e.g., integer, string, datetime, boolean, etc.)
3. **Business Purpose**: The role of this field in business (e.g., for user identification, amount calculation, time sorting, etc.)
4. **Data Characteristics**: Notable features of the data (e.g., unique identifier, nullable, has enum values, has units, etc.)
请按以下格式输出(每列一个段落):
Please output in the following format (one paragraph per column):
**列名1** (数据类型)
- 字段含义:xxx
- 业务用途:xxx
- 数据特征:xxx
**Column1** (data type)
- Field Meaning: xxx
- Business Purpose: xxx
- Data Characteristics: xxx
**列名2** (数据类型)
- 字段含义:xxx
- 业务用途:xxx
- 数据特征:xxx
**Column2** (data type)
- Field Meaning: xxx
- Business Purpose: xxx
- Data Characteristics: xxx
**重要提示**
- 不要输出具体的数据值,只描述字段的元信息
- 使用清晰的业务术语,便于用户理解和搜索
- 如果从样本数据中能推断出枚举值范围,可以概括说明(如:状态字段包含待处理/进行中/已完成等状态)`
**Important Notes**:
- Do not output specific data values, only describe the field metadata
- Use clear business terms for easy user understanding and search
- If enum value ranges can be inferred from sample data, provide a summary (e.g., status field contains pending/in-progress/completed states)
- Write descriptions in the same language as the data content`
)
// NewChunkExtractTask creates a new chunk extract task
@@ -612,7 +614,7 @@ func (s *DataTableSummaryService) generateTableDescription(ctx context.Context,
return "", fmt.Errorf("failed to generate table description: %w", err)
}
return fmt.Sprintf("# 表格摘要\n\n表名: %s\n\n%s", tableName, response.Content), nil
return fmt.Sprintf("# Table Summary\n\nTable name: %s\n\n%s", tableName, response.Content), nil
}
// generateColumnDescriptions generates descriptions for each column in batch
@@ -634,13 +636,13 @@ func (s *DataTableSummaryService) generateColumnDescriptions(ctx context.Context
return "", fmt.Errorf("failed to generate column descriptions: %w", err)
}
return fmt.Sprintf("# 表格列信息\n\n表名: %s\n\n%s", tableName, response.Content), nil
return fmt.Sprintf("# Table Column Information\n\nTable name: %s\n\n%s", tableName, response.Content), nil
}
// buildSampleDataDescription builds a formatted sample data description
func (s *DataTableSummaryService) buildSampleDataDescription(sampleData *types.ToolResult, maxRows int) string {
var builder strings.Builder
builder.WriteString(fmt.Sprintf("前%d行数据示例:\n", maxRows))
builder.WriteString(fmt.Sprintf("Sample data (first %d rows):\n", maxRows))
rows, ok := sampleData.Data["rows"].([]map[string]interface{})
if !ok {
+1 -1
View File
@@ -992,7 +992,7 @@ func (b *graphBuilder) generateKnowledgeGraphDiagram(ctx context.Context) string
// only draw if there are multiple entities or at least one relationship in the subgraph
if hasRelations {
subgraphCount++
sb.WriteString(fmt.Sprintf("\n subgraph 子图%d\n", subgraphCount))
sb.WriteString(fmt.Sprintf("\n subgraph Subgraph%d\n", subgraphCount))
// add all entities in this subgraph
entitiesInComponent := make(map[string]bool)
@@ -22,14 +22,14 @@ import (
)
const (
vlmOCRPrompt = "请提取这张文档图片中的所有正文内容,用纯 Markdown 格式输出。要求:\n" +
"1. 忽略页眉、页脚\n" +
"2. 表格使用 Markdown 表格语法\n" +
"3. 公式使用 LaTeX 格式(用 $ $$ 包裹)\n" +
"4. 按照原文阅读顺序组织\n" +
"5. 只输出提取到的文本内容,不要添加任何 HTML 标签\n" +
"如果图片中没有可识别的文字内容,请回复:无文字内容。"
vlmCaptionPrompt = "简单凝炼的描述图片的主要内容"
vlmOCRPrompt = "Extract all body text content from this document image and output in pure Markdown format. Requirements:\n" +
"1. Ignore headers and footers\n" +
"2. Use Markdown table syntax for tables\n" +
"3. Use LaTeX format for formulas (wrapped with $ or $$)\n" +
"4. Organize content in the original reading order\n" +
"5. Only output extracted text content, do not add any HTML tags\n" +
"If there is no recognizable text content in the image, reply: No text content."
vlmCaptionPrompt = "Provide a brief and concise description of the main content of the image"
)
// ImageMultimodalService handles image:multimodal asynq tasks.
+35 -29
View File
@@ -1809,10 +1809,10 @@ func (s *knowledgeService) getSummary(ctx context.Context,
var imageAnnotations string
for _, img := range allImageInfos {
if img.Caption != "" {
imageAnnotations += fmt.Sprintf("\n[图片描述: %s]", img.Caption)
imageAnnotations += fmt.Sprintf("\n[Image Description: %s]", img.Caption)
}
if img.OCRText != "" {
imageAnnotations += fmt.Sprintf("\n[图片文字: %s]", img.OCRText)
imageAnnotations += fmt.Sprintf("\n[Image OCR Text: %s]", img.OCRText)
}
}
@@ -1829,15 +1829,15 @@ func (s *knowledgeService) getSummary(ctx context.Context,
// Add knowledge metadata if available
if knowledge != nil {
metadataIntro := fmt.Sprintf("文档类型: %s\n文件名称: %s\n", knowledge.FileType, knowledge.FileName)
metadataIntro := fmt.Sprintf("Document Type: %s\nFile Name: %s\n", knowledge.FileType, knowledge.FileName)
// Add additional metadata if available
if knowledge.Type != "" {
metadataIntro += fmt.Sprintf("知识类型: %s\n", knowledge.Type)
metadataIntro += fmt.Sprintf("Knowledge Type: %s\n", knowledge.Type)
}
// Prepend metadata to content
contentWithMetadata = metadataIntro + "\n内容:\n" + contentWithMetadata
contentWithMetadata = metadataIntro + "\nContent:\n" + contentWithMetadata
}
// Generate summary using AI model
@@ -2035,7 +2035,7 @@ func (s *knowledgeService) ProcessSummaryGeneration(ctx context.Context, t *asyn
TenantID: knowledge.TenantID,
KnowledgeID: knowledge.ID,
KnowledgeBaseID: knowledge.KnowledgeBaseID,
Content: fmt.Sprintf("# 文档名称\n%s\n\n# 摘要\n%s", knowledge.FileName, summary),
Content: fmt.Sprintf("# Document\n%s\n\n# Summary\n%s", knowledge.FileName, summary),
ChunkIndex: maxChunkIndex + 1,
IsEnabled: true,
CreatedAt: time.Now(),
@@ -2282,12 +2282,12 @@ func (s *knowledgeService) generateQuestionsWithContext(ctx context.Context,
// Build context section
var contextSection string
if prevContent != "" || nextContent != "" {
contextSection = "## 上下文信息(仅供参考,帮助理解主要内容)\n"
contextSection = "## Context Information (for reference only, to help understand the main content)\n"
if prevContent != "" {
contextSection += fmt.Sprintf("【前文】%s\n", prevContent)
contextSection += fmt.Sprintf("[Preceding Context] %s\n", prevContent)
}
if nextContent != "" {
contextSection += fmt.Sprintf("【后文】%s\n", nextContent)
contextSection += fmt.Sprintf("[Following Context] %s\n", nextContent)
}
contextSection += "\n"
}
@@ -2335,32 +2335,38 @@ func (s *knowledgeService) generateQuestionsWithContext(ctx context.Context,
}
// Default prompt for question generation with context support
const defaultQuestionGenerationPrompt = `你是一个专业的问题生成助手你的任务是根据给定的主要内容生成用户可能会问的相关问题
const defaultQuestionGenerationPrompt = `You are a professional question generation assistant. Your task is to generate related questions that users might ask based on the given [Main Content].
{{context}}
## 主要内容请基于此内容生成问题
文档名称{{doc_name}}
文档内容
## Main Content (generate questions based on this content)
Document name: {{doc_name}}
Document content:
{{content}}
## 核心要求
- 生成的问题必须与主要内容直接相关
- 问题中禁止使用任何代词或指代词"它""这个""该文档""本文""文中""其"必须用具体名称替代
- 问题必须是完整独立的脱离上下文也能被理解
- 问题应该是用户在实际场景中可能会提出的自然问题
- 问题应该多样化覆盖内容的不同方面
- 每个问题应该简洁明了长度控制在30字以内
- 生成的问题数量为 {{question_count}}
## Core Requirements
- Generated questions must be directly related to the [Main Content]
- Questions must NOT use any pronouns or referential words (such as "it", "this", "that document", "this article", "the text", "its", etc.); use specific names instead
- Questions must be complete and self-contained, understandable without additional context
- Questions should be natural questions that users would likely ask in real scenarios
- Questions should be diverse, covering different aspects of the content
- Each question should be concise and clear, within 30 words
- Generate {{question_count}} questions
## 问题类型建议
- 定义类什么是......是什么
- 原因类为什么......的原因是什么
- 方法类如何...怎样...
- 比较类......有什么区别
- 应用类...可以用于什么场景
## Suggested Question Types
- Definition: What is...? What does... mean?
- Reason: Why...? What is the reason for...?
- Method: How to...? What is the way to...?
- Comparison: What is the difference between... and...?
- Application: What scenarios can... be used for?
## 输出格式
直接输出问题列表每行一个问题不要有序号或其他前缀`
## Output Format
Output the question list directly, one question per line, without numbering or other prefixes.
## CRITICAL: Language Rule
- Generate questions in the SAME LANGUAGE as the source document
- If the document is in Korean, generate questions in Korean
- If the document is in English, generate questions in English
- If the document is in Chinese, generate questions in Chinese`
// GetKnowledgeFile retrieves the physical file associated with a knowledge entry
func (s *knowledgeService) GetKnowledgeFile(ctx context.Context, id string) (io.ReadCloser, string, error) {
@@ -16,6 +16,7 @@ var (
"无文字内容",
"无法识别",
"no text",
"no text content",
"no content",
"empty",
"图片中没有文字",
@@ -94,8 +95,11 @@ func ocrHTMLToMarkdown(content string) string {
// isKnownEmptyReply checks whether the text matches a known "no content"
// reply pattern that VLM models produce when the image has no text.
// Trailing punctuation (., !, ?) is stripped before comparison so that
// responses like "No text content." still match "no text content".
func isKnownEmptyReply(text string) bool {
lower := strings.ToLower(strings.TrimSpace(text))
lower = strings.TrimRight(lower, ".!?。!?")
for _, phrase := range knownEmptyReplies {
if lower == strings.ToLower(phrase) {
return true
+9 -9
View File
@@ -241,19 +241,19 @@ func (a *CustomAgent) IsAgentMode() bool {
func GetBuiltinQuickAnswerAgent(tenantID uint64) *CustomAgent {
return &CustomAgent{
ID: BuiltinQuickAnswerID,
Name: "快速问答",
Description: "基于知识库的 RAG 问答,快速准确地回答问题",
Name: "Quick Answer",
Description: "Knowledge base RAG Q&A for fast and accurate answers",
IsBuiltin: true,
TenantID: tenantID,
Config: CustomAgentConfig{
AgentMode: AgentModeQuickAnswer,
SystemPrompt: "",
ContextTemplate: `请根据以下参考资料回答用户问题。
ContextTemplate: `Answer the user's question based on the following reference materials. IMPORTANT: Always respond in the same language as the user's question.
参考资料:
Reference materials:
{{contexts}}
用户问题:{{query}}`,
User question: {{query}}`,
Temperature: 0.7,
MaxCompletionTokens: 2048,
WebSearchEnabled: true,
@@ -284,8 +284,8 @@ func GetBuiltinQuickAnswerAgent(tenantID uint64) *CustomAgent {
func GetBuiltinSmartReasoningAgent(tenantID uint64) *CustomAgent {
return &CustomAgent{
ID: BuiltinSmartReasoningID,
Name: "智能推理",
Description: "ReAct 推理框架,支持多步思考和工具调用",
Name: "Smart Reasoning",
Description: "ReAct reasoning framework with multi-step thinking and tool calling",
IsBuiltin: true,
TenantID: tenantID,
Config: CustomAgentConfig{
@@ -321,8 +321,8 @@ func GetBuiltinSmartReasoningAgent(tenantID uint64) *CustomAgent {
func GetBuiltinDataAnalystAgent(tenantID uint64) *CustomAgent {
return &CustomAgent{
ID: BuiltinDataAnalystID,
Name: "数据分析师",
Description: "专业数据分析智能体,支持 CSV/Excel 文件的 SQL 查询与统计分析",
Name: "Data Analyst",
Description: "Professional data analysis agent with SQL query and statistical analysis for CSV/Excel files",
Avatar: "📊",
IsBuiltin: true,
TenantID: tenantID,