myagent-ai 1.6.4 → 1.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agents/__pycache__/base.cpython-312.pyc +0 -0
- package/agents/base.py +28 -2
- package/package.json +1 -1
- package/web/ui/chat.html +189 -7
|
Binary file
|
package/agents/base.py
CHANGED
|
@@ -145,6 +145,7 @@ class BaseAgent(ABC):
|
|
|
145
145
|
request_kwargs.update(kwargs)
|
|
146
146
|
|
|
147
147
|
full_text = ""
|
|
148
|
+
full_reasoning = "" # Track reasoning tokens (for o1/o3/DeepSeek-R1 etc.)
|
|
148
149
|
tool_calls_acc: Dict[int, Dict] = {} # index -> {id, name, arguments_str}
|
|
149
150
|
finish_reason = ""
|
|
150
151
|
|
|
@@ -166,6 +167,12 @@ class BaseAgent(ABC):
|
|
|
166
167
|
else:
|
|
167
168
|
await _write_sse({"type": "text_delta", "content": delta_text})
|
|
168
169
|
|
|
170
|
+
async def _emit_reasoning_delta(delta_text: str):
|
|
171
|
+
"""处理推理 token:发送 reasoning_delta SSE 事件"""
|
|
172
|
+
nonlocal full_reasoning
|
|
173
|
+
full_reasoning += delta_text
|
|
174
|
+
await _write_sse({"type": "reasoning_delta", "content": delta_text})
|
|
175
|
+
|
|
169
176
|
try:
|
|
170
177
|
if self.llm.provider in self.llm._OPENAI_COMPATIBLE_PROVIDERS or self.llm.provider == "zhipu":
|
|
171
178
|
# 使用异步客户端流式
|
|
@@ -190,6 +197,11 @@ class BaseAgent(ABC):
|
|
|
190
197
|
if delta.content:
|
|
191
198
|
await _emit_text_delta(delta.content)
|
|
192
199
|
|
|
200
|
+
# Handle reasoning_content (OpenAI o1/o3, DeepSeek-R1, Qwen-QwQ etc.)
|
|
201
|
+
reasoning_content = getattr(delta, 'reasoning_content', None) or getattr(delta, 'reasoning', None)
|
|
202
|
+
if reasoning_content:
|
|
203
|
+
await _emit_reasoning_delta(reasoning_content)
|
|
204
|
+
|
|
193
205
|
# Handle tool_call deltas (accumulate)
|
|
194
206
|
if hasattr(delta, 'tool_calls') and delta.tool_calls:
|
|
195
207
|
for tc_delta in delta.tool_calls:
|
|
@@ -252,8 +264,17 @@ class BaseAgent(ABC):
|
|
|
252
264
|
if event is None:
|
|
253
265
|
break
|
|
254
266
|
if event.type == "content_block_delta":
|
|
255
|
-
|
|
267
|
+
# Handle text content
|
|
268
|
+
if hasattr(event.delta, "text") and event.delta.text:
|
|
256
269
|
await _emit_text_delta(event.delta.text)
|
|
270
|
+
# Handle extended thinking (Anthropic reasoning)
|
|
271
|
+
if hasattr(event.delta, "thinking") and event.delta.thinking:
|
|
272
|
+
await _emit_reasoning_delta(event.delta.thinking)
|
|
273
|
+
# Handle thinking delta via type
|
|
274
|
+
if hasattr(event.delta, "type") and event.delta.type == "thinking_delta":
|
|
275
|
+
thinking_text = getattr(event.delta, "thinking", "")
|
|
276
|
+
if thinking_text:
|
|
277
|
+
await _emit_reasoning_delta(thinking_text)
|
|
257
278
|
elif event.type == "message_stop":
|
|
258
279
|
finish_reason = "stop"
|
|
259
280
|
|
|
@@ -292,9 +313,14 @@ class BaseAgent(ABC):
|
|
|
292
313
|
break
|
|
293
314
|
try:
|
|
294
315
|
data = json.loads(line.decode('utf-8') if isinstance(line, bytes) else line)
|
|
295
|
-
|
|
316
|
+
message = data.get("message", {})
|
|
317
|
+
content = message.get("content", "")
|
|
296
318
|
if content:
|
|
297
319
|
await _emit_text_delta(content)
|
|
320
|
+
# Handle Ollama thinking/reasoning (e.g., DeepSeek-R1 via Ollama)
|
|
321
|
+
thinking = message.get("thinking", "")
|
|
322
|
+
if thinking:
|
|
323
|
+
await _emit_reasoning_delta(thinking)
|
|
298
324
|
if data.get("done"):
|
|
299
325
|
finish_reason = "stop"
|
|
300
326
|
# Record usage from Ollama
|
package/package.json
CHANGED
package/web/ui/chat.html
CHANGED
|
@@ -3063,7 +3063,8 @@ async function loadSessions() {
|
|
|
3063
3063
|
} catch (e) {
|
|
3064
3064
|
// Offline - try cache
|
|
3065
3065
|
state.sessions = state.agentSessions[state.activeAgent] || [];
|
|
3066
|
-
|
|
3066
|
+
renderSessions();
|
|
3067
|
+
}
|
|
3067
3068
|
|
|
3068
3069
|
// Auto-select most recent session if none selected
|
|
3069
3070
|
if (!state.activeSessionId && state.sessions.length > 0) {
|
|
@@ -3350,6 +3351,17 @@ function renderMessages() {
|
|
|
3350
3351
|
<div class="thought-content">${renderMarkdown(msg.thought)}</div>
|
|
3351
3352
|
</details>`;
|
|
3352
3353
|
})() : '';
|
|
3354
|
+
const reasoningHtml = msg.reasoning ? (() => {
|
|
3355
|
+
const isStreaming = !!msg.streaming;
|
|
3356
|
+
return `<details class="thought-block ${isStreaming ? 'streaming' : ''}" ${isStreaming ? 'open' : ''}>
|
|
3357
|
+
<summary>
|
|
3358
|
+
<span class="thought-icon">💡</span>
|
|
3359
|
+
<span class="thought-label">模型推理过程</span>
|
|
3360
|
+
${isStreaming ? '<span class="thought-badge">推理中...</span>' : '<span class="thought-badge">已完成</span>'}
|
|
3361
|
+
</summary>
|
|
3362
|
+
<div class="thought-content">${renderMarkdown(msg.reasoning)}</div>
|
|
3363
|
+
</details>`;
|
|
3364
|
+
})() : '';
|
|
3353
3365
|
const actionBtns = (!isUser && msg.content) ? `
|
|
3354
3366
|
<div class="msg-actions">
|
|
3355
3367
|
<button class="msg-action-btn" onclick="copyMessage(${i})" title="复制">
|
|
@@ -3375,6 +3387,7 @@ function renderMessages() {
|
|
|
3375
3387
|
<div class="message-row ${msg.role}">
|
|
3376
3388
|
<div class="message-avatar">${avatar}</div>
|
|
3377
3389
|
<div style="flex:1;min-width:0">
|
|
3390
|
+
${reasoningHtml}
|
|
3378
3391
|
${thoughtHtml}
|
|
3379
3392
|
${content || streamingIndicator ? `<div class="message-bubble">${content}${ttsIndicator}</div>` : ''}
|
|
3380
3393
|
${streamingIndicator}
|
|
@@ -3392,6 +3405,165 @@ function renderMessages() {
|
|
|
3392
3405
|
scrollToBottom();
|
|
3393
3406
|
}
|
|
3394
3407
|
|
|
3408
|
+
// ── Incremental Streaming Update (avoid full DOM rebuild on every token) ──
|
|
3409
|
+
function updateStreamingMessage(msgIdx) {
|
|
3410
|
+
// Only update the last streaming message bubble without rebuilding the entire DOM
|
|
3411
|
+
const msg = state.messages[msgIdx];
|
|
3412
|
+
if (!msg || msg.role !== 'assistant') return;
|
|
3413
|
+
|
|
3414
|
+
const container = document.getElementById('messagesInner');
|
|
3415
|
+
if (!container) return;
|
|
3416
|
+
|
|
3417
|
+
// Find or create the streaming message row
|
|
3418
|
+
const rows = container.querySelectorAll('.message-row.assistant');
|
|
3419
|
+
let targetRow = null;
|
|
3420
|
+
// Count assistant rows to find the right one
|
|
3421
|
+
let assistantCount = 0;
|
|
3422
|
+
for (const row of rows) {
|
|
3423
|
+
assistantCount++;
|
|
3424
|
+
if (assistantCount === msgIdx + 1) {
|
|
3425
|
+
targetRow = row;
|
|
3426
|
+
break;
|
|
3427
|
+
}
|
|
3428
|
+
}
|
|
3429
|
+
|
|
3430
|
+
// Fallback: if we can't find by count, use last assistant row
|
|
3431
|
+
if (!targetRow && rows.length > 0) {
|
|
3432
|
+
targetRow = rows[rows.length - 1];
|
|
3433
|
+
}
|
|
3434
|
+
|
|
3435
|
+
if (!targetRow) {
|
|
3436
|
+
// Message row doesn't exist yet, fall back to full render
|
|
3437
|
+
renderMessages();
|
|
3438
|
+
return;
|
|
3439
|
+
}
|
|
3440
|
+
|
|
3441
|
+
const contentArea = targetRow.querySelector(':scope > div');
|
|
3442
|
+
if (!contentArea) return;
|
|
3443
|
+
|
|
3444
|
+
// Update reasoning block (model inference/reasoning - e.g. o1, DeepSeek-R1)
|
|
3445
|
+
let reasoningBlock = contentArea.querySelector('.thought-block .thought-label');
|
|
3446
|
+
// Find reasoning block by looking for "模型推理过程" label
|
|
3447
|
+
let reasoningDetails = null;
|
|
3448
|
+
const allThoughtBlocks = contentArea.querySelectorAll('.thought-block');
|
|
3449
|
+
for (const tb of allThoughtBlocks) {
|
|
3450
|
+
const label = tb.querySelector('.thought-label');
|
|
3451
|
+
if (label && label.textContent.includes('模型推理过程')) {
|
|
3452
|
+
reasoningDetails = tb;
|
|
3453
|
+
break;
|
|
3454
|
+
}
|
|
3455
|
+
}
|
|
3456
|
+
if (msg.reasoning) {
|
|
3457
|
+
const reasoningHtml = `<details class="thought-block ${msg.streaming ? 'streaming' : ''}" ${msg.streaming ? 'open' : ''}>
|
|
3458
|
+
<summary>
|
|
3459
|
+
<span class="thought-icon">💡</span>
|
|
3460
|
+
<span class="thought-label">模型推理过程</span>
|
|
3461
|
+
${msg.streaming ? '<span class="thought-badge">推理中...</span>' : '<span class="thought-badge">已完成</span>'}
|
|
3462
|
+
</summary>
|
|
3463
|
+
<div class="thought-content">${renderMarkdown(msg.reasoning)}</div>
|
|
3464
|
+
</details>`;
|
|
3465
|
+
if (reasoningDetails) {
|
|
3466
|
+
reasoningDetails.outerHTML = reasoningHtml;
|
|
3467
|
+
} else {
|
|
3468
|
+
contentArea.insertAdjacentHTML('afterbegin', reasoningHtml);
|
|
3469
|
+
}
|
|
3470
|
+
}
|
|
3471
|
+
|
|
3472
|
+
// Update thought block (agent thinking)
|
|
3473
|
+
let thoughtBlock = null;
|
|
3474
|
+
const updatedBlocks = contentArea.querySelectorAll('.thought-block');
|
|
3475
|
+
for (const tb of updatedBlocks) {
|
|
3476
|
+
const label = tb.querySelector('.thought-label');
|
|
3477
|
+
if (label && label.textContent.includes('Agent 思考过程')) {
|
|
3478
|
+
thoughtBlock = tb;
|
|
3479
|
+
break;
|
|
3480
|
+
}
|
|
3481
|
+
}
|
|
3482
|
+
if (msg.thought) {
|
|
3483
|
+
const thoughtHtml = `<details class="thought-block ${msg.streaming ? 'streaming' : ''}" ${msg.streaming ? 'open' : ''}>
|
|
3484
|
+
<summary>
|
|
3485
|
+
<span class="thought-icon">🧠</span>
|
|
3486
|
+
<span class="thought-label">Agent 思考过程</span>
|
|
3487
|
+
${msg.streaming ? '<span class="thought-badge">思考中...</span>' : '<span class="thought-badge">已完成</span>'}
|
|
3488
|
+
</summary>
|
|
3489
|
+
<div class="thought-content">${renderMarkdown(msg.thought)}</div>
|
|
3490
|
+
</details>`;
|
|
3491
|
+
if (thoughtBlock) {
|
|
3492
|
+
thoughtBlock.outerHTML = thoughtHtml;
|
|
3493
|
+
} else {
|
|
3494
|
+
contentArea.insertAdjacentHTML('afterbegin', thoughtHtml);
|
|
3495
|
+
}
|
|
3496
|
+
} else if (thoughtBlock && !msg.thought) {
|
|
3497
|
+
thoughtBlock.remove();
|
|
3498
|
+
}
|
|
3499
|
+
|
|
3500
|
+
// Update message bubble content
|
|
3501
|
+
const bubble = contentArea.querySelector('.message-bubble');
|
|
3502
|
+
const content = msg.content ? renderMarkdown(msg.content) : '';
|
|
3503
|
+
const streamingIndicator = msg.streaming && !msg.content && !msg.thought ? `
|
|
3504
|
+
<div class="streaming-indicator">
|
|
3505
|
+
<div class="spinner"></div>
|
|
3506
|
+
<div class="streaming-dots">
|
|
3507
|
+
<span class="dot"></span><span class="dot"></span><span class="dot"></span>
|
|
3508
|
+
</div>
|
|
3509
|
+
<span style="font-weight:500">Agent 正在思考...</span>
|
|
3510
|
+
</div>` : '';
|
|
3511
|
+
|
|
3512
|
+
if (bubble) {
|
|
3513
|
+
if (content) {
|
|
3514
|
+
bubble.innerHTML = content;
|
|
3515
|
+
bubble.style.display = '';
|
|
3516
|
+
} else {
|
|
3517
|
+
bubble.style.display = 'none';
|
|
3518
|
+
}
|
|
3519
|
+
} else if (content) {
|
|
3520
|
+
contentArea.insertAdjacentHTML('afterbegin', `<div class="message-bubble">${content}</div>`);
|
|
3521
|
+
}
|
|
3522
|
+
|
|
3523
|
+
// Update streaming indicator
|
|
3524
|
+
let indicator = contentArea.querySelector('.streaming-indicator');
|
|
3525
|
+
if (streamingIndicator) {
|
|
3526
|
+
if (!indicator) {
|
|
3527
|
+
contentArea.insertAdjacentHTML('beforeend', streamingIndicator);
|
|
3528
|
+
}
|
|
3529
|
+
} else if (indicator) {
|
|
3530
|
+
indicator.remove();
|
|
3531
|
+
}
|
|
3532
|
+
|
|
3533
|
+
// Update exec events
|
|
3534
|
+
const existingPanel = contentArea.querySelector('.exec-events-panel');
|
|
3535
|
+
if (msg.exec_events && msg.exec_events.length > 0) {
|
|
3536
|
+
const execHtml = renderExecEvents(msg.exec_events, msgIdx);
|
|
3537
|
+
if (existingPanel) {
|
|
3538
|
+
existingPanel.outerHTML = execHtml;
|
|
3539
|
+
} else {
|
|
3540
|
+
contentArea.insertAdjacentHTML('beforeend', execHtml);
|
|
3541
|
+
}
|
|
3542
|
+
}
|
|
3543
|
+
|
|
3544
|
+
// Auto-scroll
|
|
3545
|
+
scrollToBottom();
|
|
3546
|
+
}
|
|
3547
|
+
|
|
3548
|
+
// Throttle streaming updates (max ~20fps to avoid excessive reflows)
|
|
3549
|
+
const _streamThrottle = { last: 0, pending: false, idx: -1 };
|
|
3550
|
+
function throttledStreamUpdate(msgIdx) {
|
|
3551
|
+
_streamThrottle.idx = msgIdx;
|
|
3552
|
+
if (_streamThrottle.pending) return;
|
|
3553
|
+
const now = performance.now();
|
|
3554
|
+
if (now - _streamThrottle.last < 50) {
|
|
3555
|
+
_streamThrottle.pending = true;
|
|
3556
|
+
requestAnimationFrame(() => {
|
|
3557
|
+
_streamThrottle.pending = false;
|
|
3558
|
+
_streamThrottle.last = performance.now();
|
|
3559
|
+
updateStreamingMessage(_streamThrottle.idx);
|
|
3560
|
+
});
|
|
3561
|
+
return;
|
|
3562
|
+
}
|
|
3563
|
+
_streamThrottle.last = now;
|
|
3564
|
+
updateStreamingMessage(msgIdx);
|
|
3565
|
+
}
|
|
3566
|
+
|
|
3395
3567
|
// ── Execution Events Rendering ──
|
|
3396
3568
|
function renderExecEvents(events, msgIndex) {
|
|
3397
3569
|
// 过滤出有意义的事件(仅展示调用/结果对,跳过中间状态)
|
|
@@ -3788,6 +3960,8 @@ async function sendMessage() {
|
|
|
3788
3960
|
|
|
3789
3961
|
if (evt.type === 'session') {
|
|
3790
3962
|
sessionIdReceived = evt.session_id;
|
|
3963
|
+
// Sync the actual session ID (backend may prefix with agent_path)
|
|
3964
|
+
state.activeSessionId = evt.session_id;
|
|
3791
3965
|
} else if (evt.type === 'text') {
|
|
3792
3966
|
fullResponse = evt.content;
|
|
3793
3967
|
state.messages[msgIdx].content = evt.content;
|
|
@@ -3796,12 +3970,12 @@ async function sendMessage() {
|
|
|
3796
3970
|
// Incremental streaming token
|
|
3797
3971
|
fullResponse += evt.content;
|
|
3798
3972
|
state.messages[msgIdx].content = fullResponse;
|
|
3799
|
-
|
|
3973
|
+
throttledStreamUpdate(msgIdx);
|
|
3800
3974
|
} else if (evt.type === 'thought_delta') {
|
|
3801
3975
|
// Agent 思考过程增量文本(流式推送,单独显示)
|
|
3802
3976
|
fullThought += evt.content;
|
|
3803
3977
|
state.messages[msgIdx].thought = fullThought;
|
|
3804
|
-
|
|
3978
|
+
throttledStreamUpdate(msgIdx);
|
|
3805
3979
|
} else if (evt.type === 'thought') {
|
|
3806
3980
|
// Agent 思考过程文本
|
|
3807
3981
|
if (fullThought.trim() === '') {
|
|
@@ -3810,7 +3984,7 @@ async function sendMessage() {
|
|
|
3810
3984
|
fullThought += '\n\n' + evt.content;
|
|
3811
3985
|
}
|
|
3812
3986
|
state.messages[msgIdx].thought = fullThought;
|
|
3813
|
-
|
|
3987
|
+
throttledStreamUpdate(msgIdx);
|
|
3814
3988
|
} else if (evt.type === 'queue_start') {
|
|
3815
3989
|
// New message starting from queue
|
|
3816
3990
|
if (state.messages[msgIdx]) {
|
|
@@ -3828,13 +4002,13 @@ async function sendMessage() {
|
|
|
3828
4002
|
// Clear intermediate text from previous agent loop iterations
|
|
3829
4003
|
fullResponse = '';
|
|
3830
4004
|
state.messages[msgIdx].content = '';
|
|
3831
|
-
|
|
4005
|
+
throttledStreamUpdate(msgIdx);
|
|
3832
4006
|
} else if (evt.type === 'exec_event') {
|
|
3833
4007
|
// Real-time execution event (tool call, code exec, skill result, etc.)
|
|
3834
4008
|
execEventsReceived.push(evt.data);
|
|
3835
4009
|
// 立即更新消息的 exec_events 并渲染
|
|
3836
4010
|
state.messages[msgIdx].exec_events = [...execEventsReceived];
|
|
3837
|
-
|
|
4011
|
+
throttledStreamUpdate(msgIdx);
|
|
3838
4012
|
} else if (evt.type === 'task_plan_updated') {
|
|
3839
4013
|
// 任务计划已更新,刷新侧边栏任务列表
|
|
3840
4014
|
loadTaskPlan();
|
|
@@ -3844,6 +4018,15 @@ async function sendMessage() {
|
|
|
3844
4018
|
execEventsReceived = evt.exec_events;
|
|
3845
4019
|
state.messages[msgIdx].exec_events = [...execEventsReceived];
|
|
3846
4020
|
}
|
|
4021
|
+
} else if (evt.type === 'reasoning_delta') {
|
|
4022
|
+
// 模型推理过程增量文本(OpenAI o1/o3/DeepSeek-R1 等推理模型)
|
|
4023
|
+
if (!state.messages[msgIdx].reasoning) state.messages[msgIdx].reasoning = '';
|
|
4024
|
+
state.messages[msgIdx].reasoning += evt.content;
|
|
4025
|
+
throttledStreamUpdate(msgIdx);
|
|
4026
|
+
} else if (evt.type === 'reasoning') {
|
|
4027
|
+
// 模型推理完整文本
|
|
4028
|
+
state.messages[msgIdx].reasoning = evt.content;
|
|
4029
|
+
throttledStreamUpdate(msgIdx);
|
|
3847
4030
|
} else if (evt.type === 'error') {
|
|
3848
4031
|
fullResponse = '❌ ' + evt.error;
|
|
3849
4032
|
state.messages[msgIdx].content = fullResponse;
|
|
@@ -4208,7 +4391,6 @@ async function callChatInject(text, choice) {
|
|
|
4208
4391
|
toast(`注入失败: ${e.message}`, 'error');
|
|
4209
4392
|
}
|
|
4210
4393
|
}
|
|
4211
|
-
}
|
|
4212
4394
|
|
|
4213
4395
|
async function insertMessageToRunningSession(text) {
|
|
4214
4396
|
// 立即将消息插入到当前正在进行的会话中
|