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):
|
||||
name = "智能体助手"
|
||||
description = "基础的对话机器人,可以回答问题,可在配置中启用需要的工具。"
|
||||
capabilities = ["file_upload"] # 支持文件上传功能
|
||||
capabilities = ["file_upload", "files"] # 支持文件上传功能
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
@ -337,6 +337,10 @@ const supportsFiles = computed(() => {
|
||||
return capabilities.includes('files')
|
||||
})
|
||||
|
||||
const currentCapabilities = computed(() => {
|
||||
return currentAgent.value?.capabilities || []
|
||||
})
|
||||
|
||||
// AgentState 相关计算属性
|
||||
const currentAgentState = computed(() => {
|
||||
return currentChatId.value ? getThreadState(currentChatId.value)?.agentState || null : null
|
||||
@ -949,6 +953,13 @@ const handleSendMessage = async ({ image } = {}) => {
|
||||
threadState.streamAbortController = new AbortController()
|
||||
|
||||
try {
|
||||
console.log('[AgentStateDebug][before_send]', {
|
||||
threadId,
|
||||
currentAgentId: currentAgentId.value,
|
||||
capabilities: currentCapabilities.value,
|
||||
supportsTodo: supportsTodo.value,
|
||||
supportsFiles: supportsFiles.value
|
||||
})
|
||||
const response = await sendMessage({
|
||||
agentId: currentAgentId.value,
|
||||
threadId: threadId,
|
||||
@ -967,10 +978,20 @@ const handleSendMessage = async ({ image } = {}) => {
|
||||
}
|
||||
threadState.isStreaming = false
|
||||
} finally {
|
||||
console.log('[AgentStateDebug][send_finally_start]', {
|
||||
threadId,
|
||||
currentAgentId: currentAgentId.value,
|
||||
supportsTodo: supportsTodo.value,
|
||||
supportsFiles: supportsFiles.value
|
||||
})
|
||||
threadState.streamAbortController = null
|
||||
// 异步加载历史记录,保持当前消息显示直到历史记录加载完成
|
||||
fetchThreadMessages({ agentId: currentAgentId.value, threadId: threadId, delay: 500 }).finally(
|
||||
() => {
|
||||
console.log('[AgentStateDebug][history_refreshed_after_send]', {
|
||||
threadId,
|
||||
currentAgentId: currentAgentId.value
|
||||
})
|
||||
// 历史记录加载完成后,安全地清空当前进行中的对话
|
||||
resetOnGoingConv(threadId)
|
||||
scrollController.scrollToBottom()
|
||||
@ -1019,6 +1040,14 @@ const handleApprovalWithStream = async (approved) => {
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('[AgentStateDebug][before_resume_send]', {
|
||||
threadId,
|
||||
currentAgentId: currentAgentId.value,
|
||||
approved,
|
||||
capabilities: currentCapabilities.value,
|
||||
supportsTodo: supportsTodo.value,
|
||||
supportsFiles: supportsFiles.value
|
||||
})
|
||||
// 使用审批 composable 处理审批
|
||||
const response = await handleApproval(
|
||||
approved,
|
||||
@ -1044,6 +1073,12 @@ const handleApprovalWithStream = async (approved) => {
|
||||
}
|
||||
} finally {
|
||||
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) {
|
||||
threadState.isStreaming = false
|
||||
threadState.streamAbortController = null
|
||||
@ -1052,6 +1087,10 @@ const handleApprovalWithStream = async (approved) => {
|
||||
// 异步加载历史记录,保持当前消息显示直到历史记录加载完成
|
||||
fetchThreadMessages({ agentId: currentAgentId.value, threadId: threadId, delay: 500 }).finally(
|
||||
() => {
|
||||
console.log('[AgentStateDebug][history_refreshed_after_resume]', {
|
||||
threadId,
|
||||
currentAgentId: currentAgentId.value
|
||||
})
|
||||
// 历史记录加载完成后,安全地清空当前进行中的对话
|
||||
resetOnGoingConv(threadId)
|
||||
scrollController.scrollToBottom()
|
||||
@ -1110,14 +1149,15 @@ const handleAgentStateRefresh = async (threadId = null) => {
|
||||
if (!currentAgentId.value) return
|
||||
// 优先使用传入的 threadId,否则使用当前的 currentChatId
|
||||
let chatId = threadId || currentChatId.value
|
||||
console.log(
|
||||
'[handleAgentStateRefresh] input threadId:',
|
||||
threadId,
|
||||
'currentChatId:',
|
||||
currentChatId.value,
|
||||
'final chatId:',
|
||||
chatId
|
||||
)
|
||||
console.log('[AgentStateDebug][manual_refresh_start]', {
|
||||
inputThreadId: threadId,
|
||||
currentChatId: currentChatId.value,
|
||||
finalChatId: chatId,
|
||||
currentAgentId: currentAgentId.value,
|
||||
capabilities: currentCapabilities.value,
|
||||
supportsTodo: supportsTodo.value,
|
||||
supportsFiles: supportsFiles.value
|
||||
})
|
||||
if (!chatId) return
|
||||
await fetchAgentState(currentAgentId.value, chatId)
|
||||
}
|
||||
@ -1245,6 +1285,19 @@ onMounted(async () => {
|
||||
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(
|
||||
currentAgentId,
|
||||
async (newAgentId, oldAgentId) => {
|
||||
|
||||
@ -69,6 +69,7 @@ export function useAgentStreamHandler({
|
||||
supportsTodo,
|
||||
supportsFiles
|
||||
}) {
|
||||
const debugPrefix = '[AgentStateDebug]'
|
||||
/**
|
||||
* Process a single stream chunk based on its status
|
||||
* @param {Object} chunk - The parsed JSON chunk
|
||||
@ -110,17 +111,39 @@ export function useAgentStreamHandler({
|
||||
return true
|
||||
|
||||
case 'human_approval_required':
|
||||
console.log(`${debugPrefix}[approval_required]`, {
|
||||
threadId,
|
||||
currentAgentId: unref(currentAgentId)
|
||||
})
|
||||
// 使用审批 composable 处理审批请求
|
||||
return processApprovalInStream(chunk, threadId, unref(currentAgentId))
|
||||
|
||||
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) {
|
||||
console.log('[AgentState]', {
|
||||
console.log(`${debugPrefix}[agent_state_apply]`, {
|
||||
threadId,
|
||||
todos: chunk.agent_state?.todos || [],
|
||||
files: chunk.agent_state?.files || []
|
||||
})
|
||||
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
|
||||
|
||||
@ -128,6 +151,13 @@ export function useAgentStreamHandler({
|
||||
// 先标记流式结束,但保持消息显示直到历史记录加载完成
|
||||
if (threadState) {
|
||||
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) {
|
||||
console.log(
|
||||
`[AgentState|Final] ${new Date().toLocaleTimeString()}.${new Date().getMilliseconds()}`,
|
||||
@ -143,7 +173,11 @@ export function useAgentStreamHandler({
|
||||
|
||||
case 'interrupted':
|
||||
// 中断状态,刷新消息历史
|
||||
console.warn('[Interrupted] case')
|
||||
console.warn(`${debugPrefix}[interrupted]`, {
|
||||
threadId,
|
||||
message: chunkMessage,
|
||||
currentAgentId: unref(currentAgentId)
|
||||
})
|
||||
if (threadState) {
|
||||
threadState.isStreaming = false
|
||||
}
|
||||
@ -164,10 +198,27 @@ export function useAgentStreamHandler({
|
||||
* @param {Function} [onChunk] - Optional callback for each chunk (e.g. for logging)
|
||||
*/
|
||||
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) => {
|
||||
if (chunk?.status && chunk.status !== 'loading') {
|
||||
console.log(`${debugPrefix}[chunk_status]`, {
|
||||
threadId,
|
||||
status: chunk.status,
|
||||
requestId: chunk.request_id
|
||||
})
|
||||
}
|
||||
if (onChunk) onChunk(chunk)
|
||||
return handleStreamChunk(chunk, threadId)
|
||||
})
|
||||
console.log(`${debugPrefix}[stream_end]`, {
|
||||
threadId,
|
||||
currentAgentId: unref(currentAgentId)
|
||||
})
|
||||
}
|
||||
|
||||
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