mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-10-26 02:04:37 +08:00
feat: AI对话中,系统提示词中支持传递给用户身份
This commit is contained in:
1 parent
18138a3563
commit
ce436da526
8 files changed
+124
-34
No files matched your search
@@ -6,7 +6,7 @@
|
||||
import type { ChatMessage, ChatOptions, ChatStreamChunk, ToolCall } from './llm/types'
|
||||
import { chatStream, chat } from './llm'
|
||||
import { getAllToolDefinitions, executeToolCalls } from './tools'
|
||||
import type { ToolContext } from './tools/types'
|
||||
import type { ToolContext, OwnerInfo } from './tools/types'
|
||||
import { aiLogger } from './logger'
|
||||
import { randomUUID } from 'crypto'
|
||||
|
||||
@@ -139,8 +139,9 @@ export interface PromptConfig {
|
||||
/**
|
||||
* 获取系统锁定部分的提示词(工具说明、时间处理等)
|
||||
* @param chatType 聊天类型 ('group' | 'private')
|
||||
* @param ownerInfo Owner 信息(当前用户在对话中的身份)
|
||||
*/
|
||||
function getLockedPromptSection(chatType: 'group' | 'private'): string {
|
||||
function getLockedPromptSection(chatType: 'group' | 'private', ownerInfo?: OwnerInfo): string {
|
||||
const now = new Date()
|
||||
const currentDate = now.toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
@@ -152,6 +153,15 @@ function getLockedPromptSection(chatType: 'group' | 'private'): string {
|
||||
const isPrivate = chatType === 'private'
|
||||
const chatTypeDesc = isPrivate ? '私聊记录' : '群聊记录'
|
||||
|
||||
// Owner 说明(当用户设置了"我是谁"时)
|
||||
const ownerNote = ownerInfo
|
||||
? `当前用户身份:
|
||||
- 用户在${isPrivate ? '对话' : '群聊'}中的身份是「${ownerInfo.displayName}」(platformId: ${ownerInfo.platformId})
|
||||
- 当用户提到"我"、"我的"时,指的就是「${ownerInfo.displayName}」
|
||||
- 查询"我"的发言时,使用 sender_id 参数筛选该成员
|
||||
`
|
||||
: ''
|
||||
|
||||
// 成员说明(私聊只有2人)
|
||||
const memberNote = isPrivate
|
||||
? `成员查询策略:
|
||||
@@ -166,7 +176,7 @@ function getLockedPromptSection(chatType: 'group' | 'private'): string {
|
||||
`
|
||||
|
||||
return `当前日期是 ${currentDate}。
|
||||
|
||||
${ownerNote}
|
||||
你可以使用以下工具来获取${chatTypeDesc}数据:
|
||||
|
||||
1. search_messages - 根据关键词搜索聊天记录,支持时间筛选和发送者筛选
|
||||
@@ -216,14 +226,19 @@ function getFallbackResponseRules(): string {
|
||||
*
|
||||
* @param chatType 聊天类型 ('group' | 'private')
|
||||
* @param promptConfig 用户自定义提示词配置(来自前端激活的预设)
|
||||
* @param ownerInfo Owner 信息(当前用户在对话中的身份)
|
||||
*/
|
||||
function buildSystemPrompt(chatType: 'group' | 'private' = 'group', promptConfig?: PromptConfig): string {
|
||||
function buildSystemPrompt(
|
||||
chatType: 'group' | 'private' = 'group',
|
||||
promptConfig?: PromptConfig,
|
||||
ownerInfo?: OwnerInfo
|
||||
): string {
|
||||
// 使用用户配置或 fallback
|
||||
const roleDefinition = promptConfig?.roleDefinition || getFallbackRoleDefinition(chatType)
|
||||
const responseRules = promptConfig?.responseRules || getFallbackResponseRules()
|
||||
|
||||
// 获取锁定的系统部分(包含动态日期和工具说明)
|
||||
const lockedSection = getLockedPromptSection(chatType)
|
||||
// 获取锁定的系统部分(包含动态日期、工具说明和 Owner 信息)
|
||||
const lockedSection = getLockedPromptSection(chatType, ownerInfo)
|
||||
|
||||
// 组合完整提示词
|
||||
return `${roleDefinition}
|
||||
@@ -300,7 +315,7 @@ export class Agent {
|
||||
}
|
||||
|
||||
// 初始化消息(包含历史记录)
|
||||
const systemPrompt = buildSystemPrompt(this.chatType, this.promptConfig)
|
||||
const systemPrompt = buildSystemPrompt(this.chatType, this.promptConfig, this.context.ownerInfo)
|
||||
this.messages = [
|
||||
{ role: 'system', content: systemPrompt },
|
||||
...this.historyMessages, // 插入历史对话
|
||||
@@ -405,7 +420,7 @@ export class Agent {
|
||||
}
|
||||
|
||||
// 初始化消息(包含历史记录)
|
||||
const systemPrompt = buildSystemPrompt(this.chatType, this.promptConfig)
|
||||
const systemPrompt = buildSystemPrompt(this.chatType, this.promptConfig, this.context.ownerInfo)
|
||||
this.messages = [
|
||||
{ role: 'system', content: systemPrompt },
|
||||
...this.historyMessages, // 插入历史对话
|
||||
|
||||
@@ -9,6 +9,14 @@ import type { ToolDefinition } from '../llm/types'
|
||||
* 工具执行上下文
|
||||
* 包含执行工具时需要的所有上下文信息
|
||||
*/
|
||||
/** Owner 信息(当前用户在对话中的身份) */
|
||||
export interface OwnerInfo {
|
||||
/** Owner 的 platformId */
|
||||
platformId: string
|
||||
/** Owner 的显示名称 */
|
||||
displayName: string
|
||||
}
|
||||
|
||||
export interface ToolContext {
|
||||
/** 当前会话 ID(数据库文件名) */
|
||||
sessionId: string
|
||||
@@ -19,6 +27,8 @@ export interface ToolContext {
|
||||
}
|
||||
/** 用户配置的消息条数限制(工具获取消息时使用) */
|
||||
maxMessagesLimit?: number
|
||||
/** Owner 信息(当前用户在对话中的身份) */
|
||||
ownerInfo?: OwnerInfo
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -381,6 +381,8 @@ export interface SearchMessageResult {
|
||||
id: number
|
||||
senderName: string
|
||||
senderPlatformId: string
|
||||
senderAliases: string[]
|
||||
senderAvatar: string | null
|
||||
content: string
|
||||
timestamp: number
|
||||
type: number
|
||||
|
||||
Vendored
+12
-6
@@ -19,12 +19,7 @@ import type {
|
||||
CheckInAnalysis,
|
||||
MemberWithStats,
|
||||
} from '../../src/types/analysis'
|
||||
import type {
|
||||
FileParseInfo,
|
||||
ConflictCheckResult,
|
||||
MergeParams,
|
||||
MergeResult,
|
||||
} from '../../src/types/format'
|
||||
import type { FileParseInfo, ConflictCheckResult, MergeParams, MergeResult } from '../../src/types/format'
|
||||
import type { TableSchema, SQLResult } from '../../src/components/analysis/SQLLab/types'
|
||||
|
||||
interface TimeFilter {
|
||||
@@ -107,6 +102,7 @@ interface SearchMessageResult {
|
||||
senderName: string
|
||||
senderPlatformId: string
|
||||
senderAliases: string[]
|
||||
senderAvatar: string | null
|
||||
content: string
|
||||
timestamp: number
|
||||
type: number
|
||||
@@ -322,11 +318,21 @@ interface AgentResult {
|
||||
totalUsage?: TokenUsage
|
||||
}
|
||||
|
||||
/** Owner 信息(当前用户在对话中的身份) */
|
||||
interface OwnerInfo {
|
||||
/** Owner 的 platformId */
|
||||
platformId: string
|
||||
/** Owner 的显示名称 */
|
||||
displayName: string
|
||||
}
|
||||
|
||||
interface ToolContext {
|
||||
sessionId: string
|
||||
timeFilter?: { startTs: number; endTs: number }
|
||||
/** 用户配置:每次发送给 AI 的最大消息条数 */
|
||||
maxMessagesLimit?: number
|
||||
/** Owner 信息(当前用户在对话中的身份) */
|
||||
ownerInfo?: OwnerInfo
|
||||
}
|
||||
|
||||
// 用户自定义提示词配置
|
||||
|
||||
@@ -20,12 +20,7 @@ import type {
|
||||
MemeBattleAnalysis,
|
||||
MemberWithStats,
|
||||
} from '../../src/types/analysis'
|
||||
import type {
|
||||
FileParseInfo,
|
||||
ConflictCheckResult,
|
||||
MergeParams,
|
||||
MergeResult,
|
||||
} from '../../src/types/format'
|
||||
import type { FileParseInfo, ConflictCheckResult, MergeParams, MergeResult } from '../../src/types/format'
|
||||
|
||||
// Custom APIs for renderer
|
||||
const api = {
|
||||
@@ -439,6 +434,7 @@ interface SearchMessageResult {
|
||||
senderName: string
|
||||
senderPlatformId: string
|
||||
senderAliases: string[]
|
||||
senderAvatar: string | null
|
||||
content: string
|
||||
timestamp: number
|
||||
type: number
|
||||
|
||||
@@ -52,7 +52,7 @@ const hintText = computed(() => {
|
||||
if (currentOwner.value) {
|
||||
return `当前:${getDisplayName(currentOwner.value)}`
|
||||
}
|
||||
return props.chatType === 'group' ? '选择你在群聊中的身份,便于数据分析' : '选择你在对话中的身份,便于数据分析'
|
||||
return '选择你是谁:1. 将会显示在聊天记录查看器中的右侧 2. AI对话中会标识你的身份'
|
||||
})
|
||||
|
||||
// 更新 owner
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { ref, computed } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { usePromptStore } from '@/stores/prompt'
|
||||
import { useSessionStore } from '@/stores/session'
|
||||
|
||||
// 工具调用记录
|
||||
export interface ToolCallRecord {
|
||||
@@ -82,6 +83,12 @@ const TOOL_DISPLAY_NAMES: Record<string, string> = {
|
||||
get_time_stats: '获取时间分布',
|
||||
}
|
||||
|
||||
/** Owner 信息类型 */
|
||||
interface OwnerInfo {
|
||||
platformId: string
|
||||
displayName: string
|
||||
}
|
||||
|
||||
export function useAIChat(
|
||||
sessionId: string,
|
||||
timeFilter?: { startTs: number; endTs: number },
|
||||
@@ -89,6 +96,7 @@ export function useAIChat(
|
||||
) {
|
||||
// 获取 chat store 中的提示词配置和全局设置
|
||||
const promptStore = usePromptStore()
|
||||
const sessionStore = useSessionStore()
|
||||
const { activeGroupPreset, activePrivatePreset, aiGlobalSettings } = storeToRefs(promptStore)
|
||||
|
||||
// 获取当前聊天类型对应的提示词配置
|
||||
@@ -108,6 +116,9 @@ export function useAIChat(
|
||||
const isAIThinking = ref(false)
|
||||
const currentConversationId = ref<string | null>(null)
|
||||
|
||||
// Owner 信息(用于告诉 AI 当前用户是谁)
|
||||
const ownerInfo = ref<OwnerInfo | undefined>(undefined)
|
||||
|
||||
// 工具调用状态
|
||||
const currentToolStatus = ref<ToolStatus | null>(null)
|
||||
const toolsUsedInCurrentRound = ref<string[]>([])
|
||||
@@ -115,6 +126,34 @@ export function useAIChat(
|
||||
// Token 使用量(当前会话累计)
|
||||
const sessionTokenUsage = ref<TokenUsage>({ promptTokens: 0, completionTokens: 0, totalTokens: 0 })
|
||||
|
||||
// 初始化:获取 Owner 信息
|
||||
async function initOwnerInfo() {
|
||||
const ownerId = sessionStore.currentSession?.ownerId
|
||||
if (!ownerId) {
|
||||
ownerInfo.value = undefined
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取成员列表,找到 owner 的显示名称
|
||||
const members = await window.chatApi.getMembers(sessionId)
|
||||
const ownerMember = members.find((m) => m.platformId === ownerId)
|
||||
if (ownerMember) {
|
||||
ownerInfo.value = {
|
||||
platformId: ownerId,
|
||||
displayName: ownerMember.groupNickname || ownerMember.accountName || ownerId,
|
||||
}
|
||||
console.log('[AI] Owner 信息已加载:', ownerInfo.value)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[AI] 获取 Owner 信息失败:', error)
|
||||
ownerInfo.value = undefined
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化时加载 Owner 信息
|
||||
initOwnerInfo()
|
||||
|
||||
// 中止控制
|
||||
let isAborted = false
|
||||
// 当前请求 ID,用于区分不同请求的响应
|
||||
@@ -248,15 +287,20 @@ export function useAIChat(
|
||||
|
||||
try {
|
||||
// 调用 Agent API
|
||||
// 注意:ownerInfo 需要深拷贝为普通对象,否则 IPC 克隆会失败
|
||||
const context = {
|
||||
sessionId,
|
||||
timeFilter: timeFilter ? { startTs: timeFilter.startTs, endTs: timeFilter.endTs } : undefined,
|
||||
maxMessagesLimit: aiGlobalSettings.value.maxMessagesPerRequest,
|
||||
ownerInfo: ownerInfo.value
|
||||
? { platformId: ownerInfo.value.platformId, displayName: ownerInfo.value.displayName }
|
||||
: undefined,
|
||||
}
|
||||
|
||||
console.log('[AI] 构建 context:', {
|
||||
sessionId,
|
||||
maxMessagesLimit: context.maxMessagesLimit,
|
||||
ownerInfo: context.ownerInfo,
|
||||
aiGlobalSettings: aiGlobalSettings.value,
|
||||
})
|
||||
|
||||
@@ -275,16 +319,12 @@ export function useAIChat(
|
||||
content: msg.content,
|
||||
}))
|
||||
|
||||
console.log(
|
||||
'[AI] 调用 Agent API...',
|
||||
console.log('[AI] 调用 Agent API...', {
|
||||
context,
|
||||
'historyLength:',
|
||||
historyMessages.length,
|
||||
'chatType:',
|
||||
historyLength: historyMessages.length,
|
||||
chatType,
|
||||
'promptConfig:',
|
||||
currentPromptConfig.value
|
||||
)
|
||||
promptConfig: currentPromptConfig.value,
|
||||
})
|
||||
|
||||
// 获取 requestId 和 promise(传递历史消息、聊天类型和提示词配置)
|
||||
const { requestId: agentReqId, promise: agentPromise } = window.agentApi.runStream(
|
||||
@@ -379,7 +419,11 @@ export function useAIChat(
|
||||
},
|
||||
historyMessages,
|
||||
chatType,
|
||||
currentPromptConfig.value
|
||||
// 确保传递纯对象,避免 IPC 克隆失败
|
||||
{
|
||||
roleDefinition: currentPromptConfig.value.roleDefinition,
|
||||
responseRules: currentPromptConfig.value.responseRules,
|
||||
}
|
||||
)
|
||||
|
||||
// 存储 Agent 请求 ID(用于中止)
|
||||
|
||||
+21
-4
@@ -251,13 +251,19 @@ export function getOriginalBuiltinPreset(presetId: string): PromptPreset | undef
|
||||
|
||||
// ==================== 锁定部分预览(仅用于前端展示) ====================
|
||||
|
||||
/** Owner 信息(用于前端预览) */
|
||||
export interface OwnerInfoPreview {
|
||||
displayName: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取锁定部分的提示词预览
|
||||
* 注意:实际执行时由主进程 agent.ts 生成,包含动态日期
|
||||
*
|
||||
* @param chatType 聊天类型
|
||||
* @param ownerInfo Owner 信息(可选,用于预览时显示)
|
||||
*/
|
||||
export function getLockedPromptSectionPreview(chatType: 'group' | 'private'): string {
|
||||
export function getLockedPromptSectionPreview(chatType: 'group' | 'private', ownerInfo?: OwnerInfoPreview): string {
|
||||
const now = new Date()
|
||||
const currentDate = now.toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
@@ -269,6 +275,15 @@ export function getLockedPromptSectionPreview(chatType: 'group' | 'private'): st
|
||||
const isPrivate = chatType === 'private'
|
||||
const chatTypeDesc = isPrivate ? '私聊记录' : '群聊记录'
|
||||
|
||||
// Owner 说明(当用户设置了"我是谁"时)
|
||||
const ownerNote = ownerInfo
|
||||
? `当前用户身份:
|
||||
- 用户在${isPrivate ? '对话' : '群聊'}中的身份是「${ownerInfo.displayName}」
|
||||
- 当用户提到"我"、"我的"时,指的就是「${ownerInfo.displayName}」
|
||||
- 查询"我"的发言时,使用 sender_id 参数筛选该成员
|
||||
`
|
||||
: ''
|
||||
|
||||
const memberNote = isPrivate
|
||||
? `成员查询策略:
|
||||
- 私聊只有两个人,可以直接获取成员列表
|
||||
@@ -280,7 +295,7 @@ export function getLockedPromptSectionPreview(chatType: 'group' | 'private'): st
|
||||
- 找到成员后,使用其 id 字段作为 search_messages 的 sender_id 参数来获取该成员的发言`
|
||||
|
||||
return `当前日期是 ${currentDate}。
|
||||
|
||||
${ownerNote}
|
||||
你可以使用以下工具来获取${chatTypeDesc}数据:
|
||||
|
||||
1. search_messages - 根据关键词搜索聊天记录,支持时间筛选和发送者筛选
|
||||
@@ -308,13 +323,15 @@ ${memberNote}
|
||||
* @param roleDefinition 角色定义
|
||||
* @param responseRules 回答要求
|
||||
* @param chatType 聊天类型
|
||||
* @param ownerInfo Owner 信息(可选)
|
||||
*/
|
||||
export function buildPromptPreview(
|
||||
roleDefinition: string,
|
||||
responseRules: string,
|
||||
chatType: 'group' | 'private'
|
||||
chatType: 'group' | 'private',
|
||||
ownerInfo?: OwnerInfoPreview
|
||||
): string {
|
||||
const lockedSection = getLockedPromptSectionPreview(chatType)
|
||||
const lockedSection = getLockedPromptSectionPreview(chatType, ownerInfo)
|
||||
|
||||
return `${roleDefinition}
|
||||
|
||||
|
||||
Reference in new issue
Block a user