Merge branch 'codex/feature-agent-files-debug'
This commit is contained in:
commit
f0850c5980
@ -19,7 +19,7 @@ def _create_fs_backend(rt):
|
|||||||
class ChatbotAgent(BaseAgent):
|
class ChatbotAgent(BaseAgent):
|
||||||
name = "智能体助手"
|
name = "智能体助手"
|
||||||
description = "基础的对话机器人,可以回答问题,可在配置中启用需要的工具。"
|
description = "基础的对话机器人,可以回答问题,可在配置中启用需要的工具。"
|
||||||
capabilities = ["file_upload"] # 支持文件上传功能
|
capabilities = ["file_upload", "files"] # 支持文件上传功能
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|||||||
@ -337,6 +337,10 @@ const supportsFiles = computed(() => {
|
|||||||
return capabilities.includes('files')
|
return capabilities.includes('files')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const currentCapabilities = computed(() => {
|
||||||
|
return currentAgent.value?.capabilities || []
|
||||||
|
})
|
||||||
|
|
||||||
// AgentState 相关计算属性
|
// AgentState 相关计算属性
|
||||||
const currentAgentState = computed(() => {
|
const currentAgentState = computed(() => {
|
||||||
return currentChatId.value ? getThreadState(currentChatId.value)?.agentState || null : null
|
return currentChatId.value ? getThreadState(currentChatId.value)?.agentState || null : null
|
||||||
@ -949,6 +953,13 @@ const handleSendMessage = async ({ image } = {}) => {
|
|||||||
threadState.streamAbortController = new AbortController()
|
threadState.streamAbortController = new AbortController()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
console.log('[AgentStateDebug][before_send]', {
|
||||||
|
threadId,
|
||||||
|
currentAgentId: currentAgentId.value,
|
||||||
|
capabilities: currentCapabilities.value,
|
||||||
|
supportsTodo: supportsTodo.value,
|
||||||
|
supportsFiles: supportsFiles.value
|
||||||
|
})
|
||||||
const response = await sendMessage({
|
const response = await sendMessage({
|
||||||
agentId: currentAgentId.value,
|
agentId: currentAgentId.value,
|
||||||
threadId: threadId,
|
threadId: threadId,
|
||||||
@ -967,10 +978,20 @@ const handleSendMessage = async ({ image } = {}) => {
|
|||||||
}
|
}
|
||||||
threadState.isStreaming = false
|
threadState.isStreaming = false
|
||||||
} finally {
|
} finally {
|
||||||
|
console.log('[AgentStateDebug][send_finally_start]', {
|
||||||
|
threadId,
|
||||||
|
currentAgentId: currentAgentId.value,
|
||||||
|
supportsTodo: supportsTodo.value,
|
||||||
|
supportsFiles: supportsFiles.value
|
||||||
|
})
|
||||||
threadState.streamAbortController = null
|
threadState.streamAbortController = null
|
||||||
// 异步加载历史记录,保持当前消息显示直到历史记录加载完成
|
// 异步加载历史记录,保持当前消息显示直到历史记录加载完成
|
||||||
fetchThreadMessages({ agentId: currentAgentId.value, threadId: threadId, delay: 500 }).finally(
|
fetchThreadMessages({ agentId: currentAgentId.value, threadId: threadId, delay: 500 }).finally(
|
||||||
() => {
|
() => {
|
||||||
|
console.log('[AgentStateDebug][history_refreshed_after_send]', {
|
||||||
|
threadId,
|
||||||
|
currentAgentId: currentAgentId.value
|
||||||
|
})
|
||||||
// 历史记录加载完成后,安全地清空当前进行中的对话
|
// 历史记录加载完成后,安全地清空当前进行中的对话
|
||||||
resetOnGoingConv(threadId)
|
resetOnGoingConv(threadId)
|
||||||
scrollController.scrollToBottom()
|
scrollController.scrollToBottom()
|
||||||
@ -1019,6 +1040,14 @@ const handleApprovalWithStream = async (approved) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
console.log('[AgentStateDebug][before_resume_send]', {
|
||||||
|
threadId,
|
||||||
|
currentAgentId: currentAgentId.value,
|
||||||
|
approved,
|
||||||
|
capabilities: currentCapabilities.value,
|
||||||
|
supportsTodo: supportsTodo.value,
|
||||||
|
supportsFiles: supportsFiles.value
|
||||||
|
})
|
||||||
// 使用审批 composable 处理审批
|
// 使用审批 composable 处理审批
|
||||||
const response = await handleApproval(
|
const response = await handleApproval(
|
||||||
approved,
|
approved,
|
||||||
@ -1044,6 +1073,12 @@ const handleApprovalWithStream = async (approved) => {
|
|||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
console.log('🔄 [STREAM] Cleaning up streaming state')
|
console.log('🔄 [STREAM] Cleaning up streaming state')
|
||||||
|
console.log('[AgentStateDebug][resume_finally_start]', {
|
||||||
|
threadId,
|
||||||
|
currentAgentId: currentAgentId.value,
|
||||||
|
supportsTodo: supportsTodo.value,
|
||||||
|
supportsFiles: supportsFiles.value
|
||||||
|
})
|
||||||
if (threadState) {
|
if (threadState) {
|
||||||
threadState.isStreaming = false
|
threadState.isStreaming = false
|
||||||
threadState.streamAbortController = null
|
threadState.streamAbortController = null
|
||||||
@ -1052,6 +1087,10 @@ const handleApprovalWithStream = async (approved) => {
|
|||||||
// 异步加载历史记录,保持当前消息显示直到历史记录加载完成
|
// 异步加载历史记录,保持当前消息显示直到历史记录加载完成
|
||||||
fetchThreadMessages({ agentId: currentAgentId.value, threadId: threadId, delay: 500 }).finally(
|
fetchThreadMessages({ agentId: currentAgentId.value, threadId: threadId, delay: 500 }).finally(
|
||||||
() => {
|
() => {
|
||||||
|
console.log('[AgentStateDebug][history_refreshed_after_resume]', {
|
||||||
|
threadId,
|
||||||
|
currentAgentId: currentAgentId.value
|
||||||
|
})
|
||||||
// 历史记录加载完成后,安全地清空当前进行中的对话
|
// 历史记录加载完成后,安全地清空当前进行中的对话
|
||||||
resetOnGoingConv(threadId)
|
resetOnGoingConv(threadId)
|
||||||
scrollController.scrollToBottom()
|
scrollController.scrollToBottom()
|
||||||
@ -1110,14 +1149,15 @@ const handleAgentStateRefresh = async (threadId = null) => {
|
|||||||
if (!currentAgentId.value) return
|
if (!currentAgentId.value) return
|
||||||
// 优先使用传入的 threadId,否则使用当前的 currentChatId
|
// 优先使用传入的 threadId,否则使用当前的 currentChatId
|
||||||
let chatId = threadId || currentChatId.value
|
let chatId = threadId || currentChatId.value
|
||||||
console.log(
|
console.log('[AgentStateDebug][manual_refresh_start]', {
|
||||||
'[handleAgentStateRefresh] input threadId:',
|
inputThreadId: threadId,
|
||||||
threadId,
|
currentChatId: currentChatId.value,
|
||||||
'currentChatId:',
|
finalChatId: chatId,
|
||||||
currentChatId.value,
|
currentAgentId: currentAgentId.value,
|
||||||
'final chatId:',
|
capabilities: currentCapabilities.value,
|
||||||
chatId
|
supportsTodo: supportsTodo.value,
|
||||||
)
|
supportsFiles: supportsFiles.value
|
||||||
|
})
|
||||||
if (!chatId) return
|
if (!chatId) return
|
||||||
await fetchAgentState(currentAgentId.value, chatId)
|
await fetchAgentState(currentAgentId.value, chatId)
|
||||||
}
|
}
|
||||||
@ -1245,6 +1285,19 @@ onMounted(async () => {
|
|||||||
scrollController.enableAutoScroll()
|
scrollController.enableAutoScroll()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
[currentAgentId, currentCapabilities, supportsTodo, supportsFiles],
|
||||||
|
([agentId, capabilities, todo, files]) => {
|
||||||
|
console.log('[AgentStateDebug][capabilities_changed]', {
|
||||||
|
currentAgentId: agentId,
|
||||||
|
capabilities,
|
||||||
|
supportsTodo: todo,
|
||||||
|
supportsFiles: files
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
currentAgentId,
|
currentAgentId,
|
||||||
async (newAgentId, oldAgentId) => {
|
async (newAgentId, oldAgentId) => {
|
||||||
|
|||||||
@ -69,6 +69,7 @@ export function useAgentStreamHandler({
|
|||||||
supportsTodo,
|
supportsTodo,
|
||||||
supportsFiles
|
supportsFiles
|
||||||
}) {
|
}) {
|
||||||
|
const debugPrefix = '[AgentStateDebug]'
|
||||||
/**
|
/**
|
||||||
* Process a single stream chunk based on its status
|
* Process a single stream chunk based on its status
|
||||||
* @param {Object} chunk - The parsed JSON chunk
|
* @param {Object} chunk - The parsed JSON chunk
|
||||||
@ -110,17 +111,39 @@ export function useAgentStreamHandler({
|
|||||||
return true
|
return true
|
||||||
|
|
||||||
case 'human_approval_required':
|
case 'human_approval_required':
|
||||||
|
console.log(`${debugPrefix}[approval_required]`, {
|
||||||
|
threadId,
|
||||||
|
currentAgentId: unref(currentAgentId)
|
||||||
|
})
|
||||||
// 使用审批 composable 处理审批请求
|
// 使用审批 composable 处理审批请求
|
||||||
return processApprovalInStream(chunk, threadId, unref(currentAgentId))
|
return processApprovalInStream(chunk, threadId, unref(currentAgentId))
|
||||||
|
|
||||||
case 'agent_state':
|
case 'agent_state':
|
||||||
|
console.log(`${debugPrefix}[agent_state_chunk]`, {
|
||||||
|
threadId,
|
||||||
|
supportsTodo: unref(supportsTodo),
|
||||||
|
supportsFiles: unref(supportsFiles),
|
||||||
|
currentAgentId: unref(currentAgentId),
|
||||||
|
hasAgentState: !!chunk.agent_state,
|
||||||
|
todoCount: Array.isArray(chunk.agent_state?.todos) ? chunk.agent_state.todos.length : 0,
|
||||||
|
fileKeys: chunk.agent_state?.files ? Object.keys(chunk.agent_state.files) : []
|
||||||
|
})
|
||||||
if ((unref(supportsTodo) || unref(supportsFiles)) && chunk.agent_state) {
|
if ((unref(supportsTodo) || unref(supportsFiles)) && chunk.agent_state) {
|
||||||
console.log('[AgentState]', {
|
console.log(`${debugPrefix}[agent_state_apply]`, {
|
||||||
threadId,
|
threadId,
|
||||||
todos: chunk.agent_state?.todos || [],
|
todos: chunk.agent_state?.todos || [],
|
||||||
files: chunk.agent_state?.files || []
|
files: chunk.agent_state?.files || []
|
||||||
})
|
})
|
||||||
threadState.agentState = chunk.agent_state
|
threadState.agentState = chunk.agent_state
|
||||||
|
} else {
|
||||||
|
console.warn(`${debugPrefix}[agent_state_skip]`, {
|
||||||
|
reason: 'capability_gate_or_empty_state',
|
||||||
|
supportsTodo: unref(supportsTodo),
|
||||||
|
supportsFiles: unref(supportsFiles),
|
||||||
|
hasAgentState: !!chunk.agent_state,
|
||||||
|
currentAgentId: unref(currentAgentId),
|
||||||
|
threadId
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|
||||||
@ -128,6 +151,13 @@ export function useAgentStreamHandler({
|
|||||||
// 先标记流式结束,但保持消息显示直到历史记录加载完成
|
// 先标记流式结束,但保持消息显示直到历史记录加载完成
|
||||||
if (threadState) {
|
if (threadState) {
|
||||||
threadState.isStreaming = false
|
threadState.isStreaming = false
|
||||||
|
console.log(`${debugPrefix}[finished]`, {
|
||||||
|
threadId,
|
||||||
|
currentAgentId: unref(currentAgentId),
|
||||||
|
hasThreadAgentState: !!threadState.agentState,
|
||||||
|
supportsTodo: unref(supportsTodo),
|
||||||
|
supportsFiles: unref(supportsFiles)
|
||||||
|
})
|
||||||
if ((unref(supportsTodo) || unref(supportsFiles)) && threadState.agentState) {
|
if ((unref(supportsTodo) || unref(supportsFiles)) && threadState.agentState) {
|
||||||
console.log(
|
console.log(
|
||||||
`[AgentState|Final] ${new Date().toLocaleTimeString()}.${new Date().getMilliseconds()}`,
|
`[AgentState|Final] ${new Date().toLocaleTimeString()}.${new Date().getMilliseconds()}`,
|
||||||
@ -143,7 +173,11 @@ export function useAgentStreamHandler({
|
|||||||
|
|
||||||
case 'interrupted':
|
case 'interrupted':
|
||||||
// 中断状态,刷新消息历史
|
// 中断状态,刷新消息历史
|
||||||
console.warn('[Interrupted] case')
|
console.warn(`${debugPrefix}[interrupted]`, {
|
||||||
|
threadId,
|
||||||
|
message: chunkMessage,
|
||||||
|
currentAgentId: unref(currentAgentId)
|
||||||
|
})
|
||||||
if (threadState) {
|
if (threadState) {
|
||||||
threadState.isStreaming = false
|
threadState.isStreaming = false
|
||||||
}
|
}
|
||||||
@ -164,10 +198,27 @@ export function useAgentStreamHandler({
|
|||||||
* @param {Function} [onChunk] - Optional callback for each chunk (e.g. for logging)
|
* @param {Function} [onChunk] - Optional callback for each chunk (e.g. for logging)
|
||||||
*/
|
*/
|
||||||
const handleAgentResponse = async (response, threadId, onChunk = null) => {
|
const handleAgentResponse = async (response, threadId, onChunk = null) => {
|
||||||
|
console.log(`${debugPrefix}[stream_start]`, {
|
||||||
|
threadId,
|
||||||
|
currentAgentId: unref(currentAgentId),
|
||||||
|
supportsTodo: unref(supportsTodo),
|
||||||
|
supportsFiles: unref(supportsFiles)
|
||||||
|
})
|
||||||
await processStreamResponse(response, (chunk) => {
|
await processStreamResponse(response, (chunk) => {
|
||||||
|
if (chunk?.status && chunk.status !== 'loading') {
|
||||||
|
console.log(`${debugPrefix}[chunk_status]`, {
|
||||||
|
threadId,
|
||||||
|
status: chunk.status,
|
||||||
|
requestId: chunk.request_id
|
||||||
|
})
|
||||||
|
}
|
||||||
if (onChunk) onChunk(chunk)
|
if (onChunk) onChunk(chunk)
|
||||||
return handleStreamChunk(chunk, threadId)
|
return handleStreamChunk(chunk, threadId)
|
||||||
})
|
})
|
||||||
|
console.log(`${debugPrefix}[stream_end]`, {
|
||||||
|
threadId,
|
||||||
|
currentAgentId: unref(currentAgentId)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
86
web/src/utils/__tests__/messageProcessor.spec.js
Normal file
86
web/src/utils/__tests__/messageProcessor.spec.js
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import assert from 'node:assert/strict'
|
||||||
|
|
||||||
|
import { MessageProcessor } from '../messageProcessor.js'
|
||||||
|
|
||||||
|
const databases = [{ name: '财税库' }, { name: 'DifyKB' }, { name: 'LightGraphKB' }]
|
||||||
|
|
||||||
|
const run = () => {
|
||||||
|
const conv = {
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
type: 'ai',
|
||||||
|
tool_calls: [
|
||||||
|
{
|
||||||
|
name: '财税库',
|
||||||
|
tool_call_result: {
|
||||||
|
content: JSON.stringify([
|
||||||
|
{
|
||||||
|
content: 'A',
|
||||||
|
score: 0.9,
|
||||||
|
metadata: { source: 'doc-a', chunk_id: 'c1', file_id: 'f1', chunk_index: 1 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: 'A',
|
||||||
|
score: 0.8,
|
||||||
|
metadata: { source: 'doc-a', chunk_id: 'c1', file_id: 'f1', chunk_index: 1 },
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'LightGraphKB',
|
||||||
|
tool_call_result: {
|
||||||
|
content: JSON.stringify({
|
||||||
|
data: {
|
||||||
|
chunks: [
|
||||||
|
{
|
||||||
|
content: 'B',
|
||||||
|
score: 0.4,
|
||||||
|
metadata: { source: 'doc-b', chunk_id: 'c2', file_id: 'f2', chunk_index: 2 },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'not_kb_tool',
|
||||||
|
tool_call_result: {
|
||||||
|
content: JSON.stringify([{ content: 'X', score: 0.99, metadata: { chunk_id: 'cx' } }]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'DifyKB',
|
||||||
|
tool_call_result: { content: 'not-json' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
const chunks = MessageProcessor.extractKnowledgeChunksFromConversation(conv, databases)
|
||||||
|
|
||||||
|
// 1. Milvus/Dify 数组提取
|
||||||
|
assert.equal(chunks.some((c) => c.content === 'A' && c.kb_name === '财税库'), true)
|
||||||
|
|
||||||
|
// 2. LightRAG data.chunks 提取
|
||||||
|
assert.equal(chunks.some((c) => c.content === 'B' && c.kb_name === 'LightGraphKB'), true)
|
||||||
|
|
||||||
|
// 3. 非知识库工具忽略
|
||||||
|
assert.equal(chunks.some((c) => c.content === 'X'), false)
|
||||||
|
|
||||||
|
// 4. 非法 JSON 自动跳过
|
||||||
|
assert.equal(chunks.some((c) => c.kb_name === 'DifyKB'), false)
|
||||||
|
|
||||||
|
// 5. 去重生效(chunk_id=c1 仅一条)
|
||||||
|
assert.equal(chunks.filter((c) => c.metadata?.chunk_id === 'c1').length, 1)
|
||||||
|
|
||||||
|
// 6. 分数排序(A 0.9 在 B 0.4 前)
|
||||||
|
const idxA = chunks.findIndex((c) => c.content === 'A')
|
||||||
|
const idxB = chunks.findIndex((c) => c.content === 'B')
|
||||||
|
assert.equal(idxA < idxB, true)
|
||||||
|
|
||||||
|
console.log('messageProcessor extractKnowledgeChunksFromConversation: all assertions passed')
|
||||||
|
}
|
||||||
|
|
||||||
|
run()
|
||||||
Loading…
Reference in New Issue
Block a user