Merge branch 'codex/feature-agent-files-debug'

This commit is contained in:
肖泽涛 2026-02-24 18:05:53 +08:00
commit f0850c5980
4 changed files with 201 additions and 11 deletions

View File

@ -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)

View File

@ -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) => {

View File

@ -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 {

View 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()