mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-10-25 17:54:34 +08:00
fix: strengthen agent evidence retrieval
This commit is contained in:
1 parent
03c07a4fe9
commit
3583548f1f
12 files changed
+161
-28
No files matched your search
@@ -86,7 +86,8 @@ export class Agent {
|
||||
this.context.ownerInfo,
|
||||
this.locale,
|
||||
this.skillCtx,
|
||||
this.context.mentionedMembers
|
||||
this.context.mentionedMembers,
|
||||
this.context.dataSnapshot
|
||||
)
|
||||
const answerWithoutToolsPrompt = i18nT('ai.agent.answerWithoutTools', { lng: this.locale })
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
|
||||
import { t as i18nT } from '../../i18n'
|
||||
import { buildSystemPrompt as buildSystemPromptCore } from '@openchatlab/node-runtime'
|
||||
import type { OwnerInfo, SkillContext, MentionedMember } from '@openchatlab/node-runtime'
|
||||
import type { OwnerInfo, SkillContext, MentionedMember, DataSnapshot } from '@openchatlab/node-runtime'
|
||||
|
||||
export type { OwnerInfo, SkillContext, MentionedMember }
|
||||
export type { OwnerInfo, SkillContext, MentionedMember, DataSnapshot }
|
||||
|
||||
export function buildSystemPrompt(
|
||||
chatType: 'group' | 'private' = 'group',
|
||||
@@ -15,7 +15,8 @@ export function buildSystemPrompt(
|
||||
ownerInfo?: OwnerInfo,
|
||||
locale: string = 'zh-CN',
|
||||
skillCtx?: SkillContext,
|
||||
mentionedMembers?: MentionedMember[]
|
||||
mentionedMembers?: MentionedMember[],
|
||||
dataSnapshot?: DataSnapshot
|
||||
): string {
|
||||
return buildSystemPromptCore({
|
||||
t: i18nT,
|
||||
@@ -25,5 +26,6 @@ export function buildSystemPrompt(
|
||||
locale,
|
||||
skillCtx,
|
||||
mentionedMembers,
|
||||
dataSnapshot,
|
||||
})
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
import type { AgentTool } from '@openchatlab/node-runtime'
|
||||
import type { PreprocessConfig } from '@openchatlab/node-runtime'
|
||||
import type { DataSnapshot } from '@openchatlab/node-runtime'
|
||||
|
||||
export type ToolCategory = 'core' | 'analysis'
|
||||
|
||||
@@ -36,6 +37,8 @@ export interface ToolContext {
|
||||
sessionId: string
|
||||
/** 当前 AI 对话 ID(用于上下文管理隔离) */
|
||||
conversationId?: string
|
||||
/** 当前聊天数据库快照(仅用于提示模型当前数据范围,不能替代工具检索结果) */
|
||||
dataSnapshot?: DataSnapshot
|
||||
/** 时间过滤器 */
|
||||
timeFilter?: {
|
||||
startTs: number
|
||||
|
||||
@@ -57,7 +57,7 @@ export default {
|
||||
ai: {
|
||||
tools: {
|
||||
search_messages: {
|
||||
desc: 'Search group chat records by keywords. Suitable for finding specific topics or keyword-related chat content. Can specify time range and sender to filter messages. Supports minute-level time queries.',
|
||||
desc: 'Search chat records by keywords. This is the primary evidence tool for factual questions about keywords, member messages, or whether a topic appeared. Can specify time range and sender to filter messages. Supports minute-level time queries.',
|
||||
params: {
|
||||
keywords:
|
||||
'List of search keywords, using OR logic to match messages containing any keyword. Pass an empty array [] to filter by sender only',
|
||||
@@ -75,7 +75,7 @@ export default {
|
||||
},
|
||||
},
|
||||
deep_search_messages: {
|
||||
desc: 'Exact substring match search for chat records. Slower but never misses any message containing the keyword. Use when regular search (search_messages) results are insufficient, or when searching for partial words or single characters.',
|
||||
desc: 'Exact substring match search for chat records. Slower but never misses any message containing the keyword. Use when search_messages results are insufficient, when verifying whether a phrase really exists, or when searching partial words or single characters.',
|
||||
params: {
|
||||
keywords: 'List of search keywords, using substring match (LIKE). Returns messages matching any keyword',
|
||||
sender_id: 'Sender member ID for filtering messages from a specific member',
|
||||
@@ -89,7 +89,7 @@ export default {
|
||||
},
|
||||
},
|
||||
get_recent_messages: {
|
||||
desc: 'Get chat messages within a specified time period. Suitable for overview questions like "what has everyone been chatting about recently" or "what was discussed in month X". Supports minute-level time queries.',
|
||||
desc: 'Get chat messages within a specified time period. This is the preferred evidence tool for overview questions like "what has everyone been chatting about recently", "what was discussed in month X", and latest content after new records were appended. Supports minute-level time queries.',
|
||||
params: {
|
||||
limit: 'Message count limit, default 100 (saves tokens, can be increased as needed)',
|
||||
year: 'Filter messages by year, e.g. 2024',
|
||||
@@ -103,7 +103,7 @@ export default {
|
||||
},
|
||||
},
|
||||
get_chat_overview: {
|
||||
desc: 'Get basic overview of the chat: name, platform, type, total messages, total members, time range, and top active members. Use this first to understand the data before deeper analysis.',
|
||||
desc: 'Get basic overview of the chat: name, platform, type, total messages, total members, time range, and top active members. Use this before analysis or after appended records to confirm the current database range.',
|
||||
params: {
|
||||
top_n: 'Return top N active members, default 10',
|
||||
},
|
||||
@@ -146,7 +146,7 @@ export default {
|
||||
},
|
||||
},
|
||||
get_message_context: {
|
||||
desc: 'Get surrounding context messages for a given message ID. Suitable for viewing what was discussed before and after a specific message. Supports single or batch message IDs.',
|
||||
desc: 'Get surrounding context messages for a given message ID. Use it to verify facts around specific quoted messages, such as what was discussed before and after a message or whether a phrase has context. Supports single or batch message IDs.',
|
||||
params: {
|
||||
message_ids:
|
||||
'List of message IDs to query context for. Can be single or multiple IDs. Message IDs can be obtained from search_messages and other tool results',
|
||||
@@ -334,6 +334,13 @@ Returned summaries are brief descriptions of each session, helping quickly locat
|
||||
'Members explicitly @-selected by the user in this round (member_id can be used directly without another search):',
|
||||
timeParamsIntro: 'Time parameters: use start_time/end_time to specify the range, format "YYYY-MM-DD HH:mm"',
|
||||
defaultYearNote: 'When no time range is specified, queries default to all data. Current year is {{year}}.',
|
||||
dataSnapshotNote:
|
||||
'Current chat database snapshot: {{name}} ({{platform}}), {{totalMessages}} total messages, {{totalMembers}} members, time range {{firstMessageDate}} ~ {{lastMessageDate}}. This snapshot only helps judge data coverage; concrete chat facts still require tool retrieval from the current database.',
|
||||
evidencePolicy: `Evidence policy:
|
||||
- AI conversation history, prior AI replies, and compressed summaries are only for understanding the user's intent; they are not evidence for chat-record facts.
|
||||
- Whenever the user asks about chat content, recent topics, what someone said, rankings/statistics, whether a topic appeared, or asks for exact quotes, first call an appropriate data tool to retrieve the current database.
|
||||
- If new chat records were appended, the current database may differ from old window history; prefer get_chat_overview or get_recent_messages/search_messages to get fresh evidence.
|
||||
- Only messages, statistics, or database overviews returned by tools may be used as factual basis. If tools do not return enough evidence, say the data is insufficient and do not fabricate nonexistent chat records.`,
|
||||
currentTask: 'Current Task',
|
||||
skillPriorityNote:
|
||||
'Note: When executing this task, prioritize the output format requirements below. This can override your usual response style.',
|
||||
|
||||
@@ -58,7 +58,7 @@ export default {
|
||||
ai: {
|
||||
tools: {
|
||||
search_messages: {
|
||||
desc: 'キーワードでグループチャット履歴を検索する。ユーザーが特定のトピックやキーワードに関連するチャット内容を探したい場合に使用する。時間範囲や送信者でメッセージをフィルタリングできる。分単位の精度で時間クエリをサポートする。',
|
||||
desc: 'キーワードでチャット履歴を検索する。キーワード、メンバーの発言、特定トピックが出たかどうかなどの事実質問に対する主要な証拠取得ツール。時間範囲や送信者でメッセージをフィルタリングできる。分単位の精度で時間クエリをサポートする。',
|
||||
params: {
|
||||
keywords:
|
||||
'検索キーワードリスト。OR ロジックでいずれかのキーワードを含むメッセージにマッチする。送信者のみでフィルタリングする場合は空配列 [] を渡す',
|
||||
@@ -76,7 +76,7 @@ export default {
|
||||
},
|
||||
},
|
||||
deep_search_messages: {
|
||||
desc: '完全部分文字列マッチでチャット履歴を検索する。速度は遅いが、キーワードを含むメッセージを漏らさない。通常の検索(search_messages)の結果が不十分な場合や、部分的な単語・単一文字を検索する場合に使用する。',
|
||||
desc: '完全部分文字列マッチでチャット履歴を検索する。速度は遅いが、キーワードを含むメッセージを漏らさない。search_messages の結果が不十分な場合、ある文言が実在するか確認する場合、または部分的な単語・単一文字を検索する場合に使用する。',
|
||||
params: {
|
||||
keywords:
|
||||
'検索キーワードリスト。部分文字列マッチ(LIKE)を使用し、いずれかのキーワードにマッチしたメッセージを返す',
|
||||
@@ -91,7 +91,7 @@ export default {
|
||||
},
|
||||
},
|
||||
get_recent_messages: {
|
||||
desc: '指定期間内のグループチャットメッセージを取得する。「最近みんな何を話していた?」「X月にグループで何が話題だった?」などの概要的な質問に適している。分単位の精度で時間クエリをサポートする。',
|
||||
desc: '指定期間内のチャットメッセージを取得する。「最近みんな何を話していた?」「X月に何が話題だった?」、新しい記録追加後の最新内容などの概要質問に対する優先証拠ツール。分単位の精度で時間クエリをサポートする。',
|
||||
params: {
|
||||
limit: '返却メッセージ数の上限。デフォルト 100(Token を節約したい場合の目安。必要に応じて増やせる)',
|
||||
year: '指定年のメッセージをフィルタリング(例:2024)',
|
||||
@@ -105,7 +105,7 @@ export default {
|
||||
},
|
||||
},
|
||||
get_chat_overview: {
|
||||
desc: 'チャット記録の基本概要を取得する:グループ名、プラットフォーム、タイプ、総メッセージ数、総メンバー数、期間、最もアクティブなメンバーランキング。分析前にデータの全体像を把握するのに適している。',
|
||||
desc: 'チャット記録の基本概要を取得する:グループ名、プラットフォーム、タイプ、総メッセージ数、総メンバー数、期間、最もアクティブなメンバーランキング。分析前や新しい記録追加後に、現在のデータベース範囲を確認するのに適している。',
|
||||
params: {
|
||||
top_n: '上位 N 名のアクティブメンバーを返却。デフォルト 10',
|
||||
},
|
||||
@@ -148,7 +148,7 @@ export default {
|
||||
},
|
||||
},
|
||||
get_message_context: {
|
||||
desc: 'メッセージ ID に基づいて前後のコンテキストメッセージを取得する。特定のメッセージの前後のチャット内容を確認したいシーンに適している。例えば「このメッセージの前後で何を話していた?」「あるメッセージのコンテキストを見る」など。単一または複数のメッセージ ID をサポートする。',
|
||||
desc: 'メッセージ ID に基づいて前後のコンテキストメッセージを取得する。具体的な引用メッセージ周辺の事実確認、例えば「このメッセージの前後で何を話していた?」「この文言には文脈があるか」を確認する場合に使用する。単一または複数のメッセージ ID をサポートする。',
|
||||
params: {
|
||||
message_ids:
|
||||
'コンテキストを取得するメッセージ ID リスト。単一 ID または複数 ID が可能。メッセージ ID は search_messages などのツールの返却結果から取得できる',
|
||||
@@ -336,6 +336,13 @@ export default {
|
||||
'このラウンドでユーザーが明示的に @ 選択したメンバー(member_id を再検索なしで直接使えます):',
|
||||
timeParamsIntro: '時間パラメータ:start_time/end_time で範囲を指定。形式は "YYYY-MM-DD HH:mm"',
|
||||
defaultYearNote: '時間範囲を指定しない場合は全期間が対象。現在の年は{{year}}年',
|
||||
dataSnapshotNote:
|
||||
'現在のチャットデータベースのスナップショット:{{name}}({{platform}})、総メッセージ {{totalMessages}} 件、メンバー {{totalMembers}} 人、期間 {{firstMessageDate}} ~ {{lastMessageDate}}。このスナップショットはデータ範囲の判断にのみ使い、具体的なチャット事実の回答には必ず現在のデータベースをツールで検索する。',
|
||||
evidencePolicy: `証拠ポリシー:
|
||||
- AI 会話履歴、過去の AI 回答、圧縮要約はユーザー意図の理解にのみ使用し、チャット記録の事実証拠として扱わない。
|
||||
- ユーザーがチャット内容、最近の話題、誰かの発言、ランキング/統計、ある話題が出たかどうか、または原文引用を求めた場合は、必ず先に適切なデータツールで現在のデータベースを検索する。
|
||||
- 新しいチャット記録が追加されている場合、現在のデータベースは古いウィンドウ履歴と異なる可能性がある。最新の証拠取得には get_chat_overview または get_recent_messages/search_messages を優先する。
|
||||
- 事実根拠として使えるのは、ツールが返したメッセージ、統計、またはデータベース概要のみ。ツールが十分な証拠を返さない場合は、データ不足と明示し、存在しないチャット記録を作らない。`,
|
||||
responseInstruction: 'ユーザーの質問に応じて適切なツールを選択してデータを取得し、データに基づいて回答する。',
|
||||
fallbackRoleDefinition: {
|
||||
group: `あなたはプロフェッショナルだがカジュアルなスタイルのグループチャット履歴分析アシスタントです。
|
||||
|
||||
@@ -56,7 +56,7 @@ export default {
|
||||
ai: {
|
||||
tools: {
|
||||
search_messages: {
|
||||
desc: '根据关键词搜索群聊记录。适用于用户想要查找特定话题、关键词相关的聊天内容。可以指定时间范围和发送者来筛选消息。支持精确到分钟级别的时间查询。',
|
||||
desc: '根据关键词搜索聊天记录,是回答关键词、成员发言、是否出现过某话题等事实问题的主要证据工具。可以指定时间范围和发送者来筛选消息。支持精确到分钟级别的时间查询。',
|
||||
params: {
|
||||
keywords: '搜索关键词列表,会用 OR 逻辑匹配包含任一关键词的消息。如果只需要按发送者筛选,可以传空数组 []',
|
||||
sender_id: '发送者的成员 ID,用于筛选特定成员发送的消息。可以通过 get_members 工具获取成员 ID',
|
||||
@@ -66,7 +66,7 @@ export default {
|
||||
},
|
||||
},
|
||||
deep_search_messages: {
|
||||
desc: '精确子串匹配搜索聊天记录,速度较慢但不会遗漏任何包含关键词的消息。当普通搜索(search_messages)结果不足、需要搜索部分词或单个字符时使用。',
|
||||
desc: '精确子串匹配搜索聊天记录,速度较慢但不会遗漏任何包含关键词的消息。当 search_messages 结果不足、需要验证某段话是否真实存在、或搜索部分词/单个字符时使用。',
|
||||
params: {
|
||||
keywords: '搜索关键词列表,使用子串匹配(LIKE),任一关键词匹配即返回',
|
||||
sender_id: '发送者的成员 ID,用于筛选特定成员发送的消息',
|
||||
@@ -76,7 +76,7 @@ export default {
|
||||
},
|
||||
},
|
||||
get_recent_messages: {
|
||||
desc: '获取指定时间段内的群聊消息。适用于回答"最近大家聊了什么"、"X月群里聊了什么"等概览性问题。支持精确到分钟级别的时间查询。',
|
||||
desc: '获取指定时间段内的聊天消息,是回答"最近大家聊了什么"、"X月聊了什么"、追加新记录后最新内容等概览性问题的首选证据工具。支持精确到分钟级别的时间查询。',
|
||||
params: {
|
||||
limit: '返回消息数量限制,默认 100(节省 token,可根据需要增加)',
|
||||
start_time: '开始时间,格式 "YYYY-MM-DD HH:mm",如 "2024-03-15 14:00"',
|
||||
@@ -84,7 +84,7 @@ export default {
|
||||
},
|
||||
},
|
||||
get_chat_overview: {
|
||||
desc: '获取聊天记录的基本概览信息,包括群名/平台/类型/总消息数/总成员数/时间跨度/最活跃成员排名。适合在分析前先了解数据全貌。',
|
||||
desc: '获取聊天记录的基本概览信息,包括群名/平台/类型/总消息数/总成员数/时间跨度/最活跃成员排名。适合在分析前或追加新记录后确认当前数据库范围。',
|
||||
params: {
|
||||
top_n: '返回前 N 名活跃成员,默认 10',
|
||||
},
|
||||
@@ -125,7 +125,7 @@ export default {
|
||||
},
|
||||
},
|
||||
get_message_context: {
|
||||
desc: '根据消息 ID 获取前后的上下文消息。适用于需要查看某条消息前后聊天内容的场景,比如"这条消息的前后在聊什么"、"查看某条消息的上下文"等。支持单个或批量消息 ID。',
|
||||
desc: '根据消息 ID 获取前后的上下文消息。适用于引用具体消息前后进行事实验证,比如"这条消息前后在聊什么"、"确认这段话是否有上下文"等。支持单个或批量消息 ID。',
|
||||
params: {
|
||||
message_ids:
|
||||
'要查询上下文的消息 ID 列表,可以是单个 ID 或多个 ID。消息 ID 可以从 search_messages 等工具的返回结果中获取',
|
||||
@@ -306,6 +306,13 @@ export default {
|
||||
mentionedMembersNote: '本轮用户显式 @ 的成员(可直接使用 member_id,无需再次搜索):',
|
||||
timeParamsIntro: '时间参数:使用 start_time/end_time 指定时间范围,格式 "YYYY-MM-DD HH:mm"',
|
||||
defaultYearNote: '未指定时间范围时默认查询全部。当前年份为{{year}}年',
|
||||
dataSnapshotNote:
|
||||
'当前聊天数据库快照:{{name}}({{platform}}),总消息 {{totalMessages}} 条,成员 {{totalMembers}} 人,时间范围 {{firstMessageDate}} ~ {{lastMessageDate}}。该快照只用于判断数据范围;回答具体聊天事实仍必须调用工具检索当前数据库。',
|
||||
evidencePolicy: `证据策略:
|
||||
- AI 对话历史、历史 AI 回复和压缩摘要只用于理解用户意图,不能作为聊天记录事实证据。
|
||||
- 只要用户询问聊天记录内容、最近聊什么、某人说过什么、统计排行、是否出现过某话题或要求引用原话,必须先调用合适的数据工具检索当前数据库。
|
||||
- 如果已追加新聊天记录,当前数据库可能不同于旧窗口历史;优先使用 get_chat_overview 或 get_recent_messages/search_messages 获取最新证据。
|
||||
- 只有工具返回的消息、统计或数据库概览可以作为事实依据;工具没有返回足够证据时,明确说明数据不足,不要编造不存在的聊天记录。`,
|
||||
currentTask: '当前任务',
|
||||
skillPriorityNote: '注意:在执行此任务时,请优先遵循以下任务的输出格式要求,这可以覆盖你的常规回复习惯。',
|
||||
responseInstruction: '根据用户的问题,选择合适的工具获取数据,然后基于数据给出回答。',
|
||||
|
||||
@@ -56,7 +56,7 @@ export default {
|
||||
ai: {
|
||||
tools: {
|
||||
search_messages: {
|
||||
desc: '根據關鍵詞搜尋群聊紀錄。適用於使用者想要查找特定話題、關鍵詞相關的聊天內容。可以指定時間範圍和傳送者來篩選訊息。支援精確到分鐘級別的時間查詢。',
|
||||
desc: '根據關鍵詞搜尋聊天紀錄,是回答關鍵詞、成員發言、是否出現過某話題等事實問題的主要證據工具。可以指定時間範圍和傳送者來篩選訊息。支援精確到分鐘級別的時間查詢。',
|
||||
params: {
|
||||
keywords: '搜尋關鍵詞清單,會用 OR 邏輯匹配包含任一關鍵詞的訊息。如果只需要按傳送者篩選,可以傳空陣列 []',
|
||||
sender_id: '傳送者的成員 ID,用於篩選特定成員傳送的訊息。可以透過 get_members 工具取得成員 ID',
|
||||
@@ -70,7 +70,7 @@ export default {
|
||||
},
|
||||
},
|
||||
deep_search_messages: {
|
||||
desc: '精確子串匹配搜尋聊天紀錄,速度較慢但不會遺漏任何包含關鍵詞的訊息。當普通搜尋(search_messages)結果不足、需要搜尋部分詞或單個字元時使用。',
|
||||
desc: '精確子串匹配搜尋聊天紀錄,速度較慢但不會遺漏任何包含關鍵詞的訊息。當 search_messages 結果不足、需要驗證某段話是否真實存在、或搜尋部分詞/單個字元時使用。',
|
||||
params: {
|
||||
keywords: '搜尋關鍵詞清單,使用子串匹配(LIKE),任一關鍵詞匹配即回傳',
|
||||
sender_id: '傳送者的成員 ID,用於篩選特定成員傳送的訊息',
|
||||
@@ -84,7 +84,7 @@ export default {
|
||||
},
|
||||
},
|
||||
get_recent_messages: {
|
||||
desc: '取得指定時間段內的群聊訊息。適用於回答「最近大家聊了什麼」、「X月群裡聊了什麼」等概覽性問題。支援精確到分鐘級別的時間查詢。',
|
||||
desc: '取得指定時間段內的聊天訊息,是回答「最近大家聊了什麼」、「X月聊了什麼」、追加新紀錄後最新內容等概覽性問題的首選證據工具。支援精確到分鐘級別的時間查詢。',
|
||||
params: {
|
||||
limit: '回傳訊息數量限制,預設 100(可節省 Token,必要時再增加)',
|
||||
year: '篩選指定年份的訊息,如 2024',
|
||||
@@ -96,7 +96,7 @@ export default {
|
||||
},
|
||||
},
|
||||
get_chat_overview: {
|
||||
desc: '取得聊天記錄的基本概覽資訊,包括群名/平台/類型/總訊息數/總成員數/時間跨度/最活躍成員排名。適合在分析前先了解資料全貌。',
|
||||
desc: '取得聊天記錄的基本概覽資訊,包括群名/平台/類型/總訊息數/總成員數/時間跨度/最活躍成員排名。適合在分析前或追加新紀錄後確認目前資料庫範圍。',
|
||||
params: {
|
||||
top_n: '回傳前 N 名活躍成員,預設 10',
|
||||
},
|
||||
@@ -137,7 +137,7 @@ export default {
|
||||
},
|
||||
},
|
||||
get_message_context: {
|
||||
desc: '根據訊息 ID 取得前後的上下文訊息。適用於想了解某條訊息前後內容的情境,例如「這則訊息前後在聊什麼」、「檢視某條訊息的上下文」等。支援單筆或批次訊息 ID。',
|
||||
desc: '根據訊息 ID 取得前後的上下文訊息。適用於引用具體訊息前後進行事實驗證,例如「這則訊息前後在聊什麼」、「確認這段話是否有上下文」等。支援單筆或批次訊息 ID。',
|
||||
params: {
|
||||
message_ids:
|
||||
'要查詢上下文的訊息 ID 清單,可以是單個 ID 或多個 ID。訊息 ID 可以從 search_messages 等工具的回傳結果中取得',
|
||||
@@ -324,6 +324,13 @@ export default {
|
||||
mentionedMembersNote: '本輪使用者顯式 @ 的成員(可直接使用 member_id,無需再次搜尋):',
|
||||
timeParamsIntro: '時間參數:使用 start_time/end_time 指定時間範圍,格式 "YYYY-MM-DD HH:mm"',
|
||||
defaultYearNote: '未指定時間範圍時預設查詢全部。當前年份為{{year}}年',
|
||||
dataSnapshotNote:
|
||||
'目前聊天資料庫快照:{{name}}({{platform}}),總訊息 {{totalMessages}} 條,成員 {{totalMembers}} 人,時間範圍 {{firstMessageDate}} ~ {{lastMessageDate}}。該快照只用於判斷資料範圍;回答具體聊天事實仍必須呼叫工具檢索目前資料庫。',
|
||||
evidencePolicy: `證據策略:
|
||||
- AI 對話歷史、歷史 AI 回覆和壓縮摘要只用於理解使用者意圖,不能作為聊天紀錄事實證據。
|
||||
- 只要使用者詢問聊天紀錄內容、最近聊什麼、某人說過什麼、統計排行、是否出現過某話題或要求引用原話,必須先呼叫合適的資料工具檢索目前資料庫。
|
||||
- 如果已追加新聊天紀錄,目前資料庫可能不同於舊視窗歷史;優先使用 get_chat_overview 或 get_recent_messages/search_messages 取得最新證據。
|
||||
- 只有工具回傳的訊息、統計或資料庫概覽可以作為事實依據;工具沒有回傳足夠證據時,明確說明資料不足,不要編造不存在的聊天紀錄。`,
|
||||
responseInstruction: '根據使用者的問題,選擇合適的工具取得資料,然後基於資料給出回答。',
|
||||
fallbackRoleDefinition: {
|
||||
group: `你是一個專業但風格輕鬆的群聊紀錄分析助手。
|
||||
|
||||
+20
-1
@@ -7,6 +7,7 @@ import * as llm from '../ai/llm'
|
||||
import { aiLogger, setDebugMode } from '../ai/logger'
|
||||
import { serializeError } from '../ai/serialize-error'
|
||||
import { getLogsDir } from '../paths'
|
||||
import * as workerManager from '../worker/workerManager'
|
||||
import { Agent, type AgentStreamChunk, type SkillContext } from '../ai/agent'
|
||||
import { getDefaultGeneralAssistantId } from '../ai/assistant/defaultGeneral'
|
||||
import { getDefaultAssistantConfig, buildPiModel, findModelDefinition } from '../ai/llm'
|
||||
@@ -1271,7 +1272,25 @@ export function registerAIHandlers({ win }: IpcContext): void {
|
||||
const modelDef = llm.findModelDefinition(activeAIConfig.provider, activeAIConfig.model || '')
|
||||
const resolvedContextWindow = modelDef?.contextWindow || 128000
|
||||
const maxToolResultTokens = Math.floor(resolvedContextWindow * (maxToolResultPercent / 100))
|
||||
const enrichedContext: ToolContext = { ...context, maxToolResultTokens }
|
||||
let dataSnapshot: ToolContext['dataSnapshot'] | undefined = context.dataSnapshot
|
||||
try {
|
||||
const overview = await workerManager.getChatOverview(context.sessionId, 5)
|
||||
if (overview) {
|
||||
dataSnapshot = {
|
||||
name: overview.name,
|
||||
platform: overview.platform,
|
||||
type: overview.type,
|
||||
totalMessages: overview.totalMessages,
|
||||
totalMembers: overview.totalMembers,
|
||||
firstMessageTs: overview.firstMessageTs,
|
||||
lastMessageTs: overview.lastMessageTs,
|
||||
capturedAt: Math.floor(Date.now() / 1000),
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
aiLogger.warn('IPC', `Failed to load agent data snapshot: ${requestId}`, { error: String(error) })
|
||||
}
|
||||
const enrichedContext: ToolContext = { ...context, maxToolResultTokens, dataSnapshot }
|
||||
|
||||
const agent = new Agent(
|
||||
enrichedContext,
|
||||
|
||||
@@ -17,6 +17,11 @@ const mockT: TranslateFn = (key, options) => {
|
||||
if (key === 'ai.agent.memberNotePrivate') return '[member-private]'
|
||||
if (key === 'ai.agent.timeParamsIntro') return '[time-params]'
|
||||
if (key === 'ai.agent.defaultYearNote') return `[year:${(options as Record<string, unknown>)?.year}]`
|
||||
if (key === 'ai.agent.dataSnapshotNote') {
|
||||
const opts = options as Record<string, unknown>
|
||||
return `[snapshot:${opts.name}:${opts.totalMessages}:${opts.lastMessageDate}]`
|
||||
}
|
||||
if (key === 'ai.agent.evidencePolicy') return '[evidence-policy]'
|
||||
if (key === 'ai.agent.responseInstruction') return '[response-instruction]'
|
||||
if (key === 'ai.agent.mentionedMembersNote') return 'Mentioned members:'
|
||||
if (key === 'ai.agent.currentTask') return 'Current task'
|
||||
@@ -81,4 +86,29 @@ describe('buildSystemPrompt', () => {
|
||||
assert.ok(result.includes('Current date is'))
|
||||
assert.ok(result.includes('[response-instruction]'))
|
||||
})
|
||||
|
||||
it('includes evidence policy in locked section', () => {
|
||||
const result = buildSystemPrompt({ t: mockT })
|
||||
assert.ok(result.includes('[evidence-policy]'))
|
||||
assert.ok(result.indexOf('[evidence-policy]') < result.indexOf('[response-instruction]'))
|
||||
})
|
||||
|
||||
it('includes current data snapshot when provided', () => {
|
||||
const result = buildSystemPrompt({
|
||||
t: mockT,
|
||||
dataSnapshot: {
|
||||
name: 'Team Chat',
|
||||
platform: 'wechat',
|
||||
type: 'group',
|
||||
totalMessages: 1234,
|
||||
totalMembers: 56,
|
||||
firstMessageTs: 1700000000,
|
||||
lastMessageTs: 1700003600,
|
||||
capturedAt: 1700007200,
|
||||
},
|
||||
})
|
||||
|
||||
assert.ok(result.includes('[snapshot:Team Chat:1234:'))
|
||||
assert.ok(result.includes('[evidence-policy]'))
|
||||
})
|
||||
})
|
||||
@@ -23,6 +23,17 @@ export interface SkillContext {
|
||||
skillMenu?: string
|
||||
}
|
||||
|
||||
export interface DataSnapshot {
|
||||
name: string
|
||||
platform: string
|
||||
type: string
|
||||
totalMessages: number
|
||||
totalMembers: number
|
||||
firstMessageTs: number | null
|
||||
lastMessageTs: number | null
|
||||
capturedAt: number
|
||||
}
|
||||
|
||||
export type TranslateFn = (key: string, options?: Record<string, unknown>) => string
|
||||
|
||||
export interface BuildSystemPromptOptions {
|
||||
@@ -33,6 +44,7 @@ export interface BuildSystemPromptOptions {
|
||||
locale?: string
|
||||
skillCtx?: SkillContext
|
||||
mentionedMembers?: MentionedMember[]
|
||||
dataSnapshot?: DataSnapshot
|
||||
}
|
||||
|
||||
function agentT(t: TranslateFn, key: string, locale: string, options?: Record<string, unknown>): string {
|
||||
@@ -44,7 +56,8 @@ function getLockedPromptSection(
|
||||
chatType: 'group' | 'private',
|
||||
ownerInfo: OwnerInfo | undefined,
|
||||
locale: string,
|
||||
mentionedMembers: MentionedMember[] | undefined
|
||||
mentionedMembers: MentionedMember[] | undefined,
|
||||
dataSnapshot: DataSnapshot | undefined
|
||||
): string {
|
||||
const now = new Date()
|
||||
const dateLocale = locale.startsWith('zh') ? 'zh-CN' : 'en-US'
|
||||
@@ -81,13 +94,25 @@ function getLockedPromptSection(
|
||||
: ''
|
||||
|
||||
const year = now.getFullYear()
|
||||
const dataSnapshotNote = dataSnapshot
|
||||
? `${agentT(t, 'ai.agent.dataSnapshotNote', locale, {
|
||||
name: dataSnapshot.name,
|
||||
platform: dataSnapshot.platform,
|
||||
totalMessages: dataSnapshot.totalMessages,
|
||||
totalMembers: dataSnapshot.totalMembers,
|
||||
firstMessageDate: formatTimestamp(dataSnapshot.firstMessageTs, locale),
|
||||
lastMessageDate: formatTimestamp(dataSnapshot.lastMessageTs, locale),
|
||||
})}\n`
|
||||
: ''
|
||||
|
||||
return `${agentT(t, 'ai.agent.currentDateIs', locale)} ${currentDate}。
|
||||
${ownerNote}
|
||||
${mentionedMembersNote}
|
||||
${dataSnapshotNote}
|
||||
${memberNote}
|
||||
${agentT(t, 'ai.agent.timeParamsIntro', locale)}
|
||||
${agentT(t, 'ai.agent.defaultYearNote', locale, { year })}
|
||||
${agentT(t, 'ai.agent.evidencePolicy', locale)}
|
||||
|
||||
${agentT(t, 'ai.agent.responseInstruction', locale)}`
|
||||
}
|
||||
@@ -96,6 +121,22 @@ function getFallbackRoleDefinition(t: TranslateFn, chatType: 'group' | 'private'
|
||||
return agentT(t, `ai.agent.fallbackRoleDefinition.${chatType}`, locale)
|
||||
}
|
||||
|
||||
function formatTimestamp(timestamp: number | null | undefined, locale: string): string {
|
||||
if (!timestamp) return locale.startsWith('zh') ? '未知' : 'unknown'
|
||||
|
||||
const date = new Date(timestamp * 1000)
|
||||
if (Number.isNaN(date.getTime())) return locale.startsWith('zh') ? '未知' : 'unknown'
|
||||
|
||||
const dateLocale = locale.startsWith('zh') ? 'zh-CN' : locale
|
||||
return date.toLocaleString(dateLocale, {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})
|
||||
}
|
||||
|
||||
export function buildSystemPrompt(options: BuildSystemPromptOptions): string {
|
||||
const {
|
||||
t,
|
||||
@@ -105,10 +146,11 @@ export function buildSystemPrompt(options: BuildSystemPromptOptions): string {
|
||||
locale = 'zh-CN',
|
||||
skillCtx,
|
||||
mentionedMembers,
|
||||
dataSnapshot,
|
||||
} = options
|
||||
|
||||
const systemPrompt = assistantSystemPrompt || getFallbackRoleDefinition(t, chatType, locale)
|
||||
const lockedSection = getLockedPromptSection(t, chatType, ownerInfo, locale, mentionedMembers)
|
||||
const lockedSection = getLockedPromptSection(t, chatType, ownerInfo, locale, mentionedMembers, dataSnapshot)
|
||||
|
||||
let skillSection = ''
|
||||
if (skillCtx?.skillDef) {
|
||||
|
||||
@@ -96,6 +96,7 @@ export type {
|
||||
export { buildSystemPrompt } from './agent/prompt-builder'
|
||||
export type {
|
||||
BuildSystemPromptOptions,
|
||||
DataSnapshot,
|
||||
OwnerInfo,
|
||||
MentionedMember,
|
||||
SkillContext,
|
||||
|
||||
@@ -129,7 +129,14 @@ export type { TokenUsage, AgentRuntimeStatus, AgentStreamChunk, EventHandlerConf
|
||||
|
||||
// Agent Prompt Builder
|
||||
export { buildSystemPrompt } from './ai'
|
||||
export type { BuildSystemPromptOptions, OwnerInfo, MentionedMember, SkillContext, TranslateFn } from './ai'
|
||||
export type {
|
||||
BuildSystemPromptOptions,
|
||||
DataSnapshot,
|
||||
OwnerInfo,
|
||||
MentionedMember,
|
||||
SkillContext,
|
||||
TranslateFn,
|
||||
} from './ai'
|
||||
|
||||
// LLM Model Builder
|
||||
export { buildPiModel, normalizeAnthropicBaseUrl, normalizeOpenAICompatibleBaseUrl } from './ai'
|
||||
|
||||
Reference in new issue
Block a user