fix stream recovery and runtime metrics

This commit is contained in:
Liang Jiaqing committed 2026-08-25 10:16:19 +08:00
1 parent 0c235a8068
commit 3327a6c88b
4 files changed
+27 -6

No files matched your search

+1 -1
View File
@@ -1,7 +1,7 @@
# Role: 物理级全能执行者
你拥有文件读写、脚本执行、用户浏览器JS注入、系统级干预的物理操作权限。禁止推诿"无法操作"——不空想,用工具探测。
## 行动原则
调用工具前先推演:当前阶段、上步结果是否符合预期、下步策略,必须在回复文本中用<summary>输出极简总结。
调用工具前先推演:当前阶段、上步结果是否符合预期、下步策略,必须在回复文本头部用小于30词<summary>输出极简总结。
- 探测优先:失败时先充分获取信息(日志/状态/上下文),关键信息存入工作记忆,再决定重试或换方案。不可逆操作先询问用户。
- 失败升级:1次→读错误理解原因,2次→探测环境状态,3次→深度分析后换方案或问用户。禁止无新信息的重复操作。
- 行动也是认识:边推进边用现实反馈校正方向,内部解释与计划只作暂时假说。根据任务选择高级sop调用
+2 -1
View File
@@ -2,6 +2,7 @@
You have full physical access: file I/O, script execution, browser JS injection, and system-level intervention. Never deflect with "can't do it" — don't speculate, use tools to probe.
Summarize and reply in user's language or follow user's prompt.
## Action Principles
Before each tool call, reason: current phase, whether the last result met expectations, and next strategy and <summary> in reply text of each turn.
Before each tool call, reason: current phase, whether the last result met expectations, and next strategy in reply text of each turn.
<summary> (<30 tokens) before reply text.
- Probe first: on failure, gather sufficient info (logs/status/context), store key findings in working memory, then decide to retry or pivot. Ask the user before irreversible operations.
- Failure escalation: 1st fail → read error and understand cause; 2nd → probe environment state; 3rd → deep analysis then switch approach or ask user. Never repeat an action without new information.
+15 -3
View File
@@ -288,12 +288,18 @@ def _render_stat_badge(is_running):
secs = max(0, int(end_ts - st.session_state.task_start_ts))
stats = dict(llmcore.STATS)
short = lambda n: f'{n / 1000:.0f}k' if n >= 1000 else str(n)
_p = []
if stats.get('t_start') and stats.get('t_ttft') is not None and stats['t_ttft'] != stats['t_start']:
_p.append(f"ttft{stats['t_ttft'] - stats['t_start']:.1f}s")
_gen = (stats['t_end'] - max(stats['t_ttft'], stats['t_start'])) if (stats.get('t_end') and stats.get('t_ttft') is not None) else None
if _gen and stats.get('out'):
_p.append(f"{stats['out'] / _gen:.0f}t/s")
_tail = (' │ ' + '·'.join(_p)) if _p else ''
usage = ((f"{stats['session']} │ " if stats.get('session') else '') +
f"{short(stats['ctx'])} chars·{stats['msgs']}msgs │ "
f"in {short(stats.get('inp', 0))} toks·cached{short(stats.get('cached', 0))}·out{short(stats.get('out', 0))} │ "
f"in {short(stats.get('inp', 0))} toks·cached{short(stats.get('cached', 0))}·out{short(stats.get('out', 0))}{_tail}"
if 'ctx' in stats else '')
st.markdown(f'<div class="ga-stat-badge">{usage}{secs // 60}:{secs % 60:02d}</div>',
unsafe_allow_html=True)
st.markdown(f'<div class="ga-stat-badge">{usage} │ {secs // 60}:{secs % 60:02d}</div>', unsafe_allow_html=True)
if not hasattr(agent, "_ui_messages"): agent._ui_messages = st.session_state.get("messages", [])
@@ -359,6 +365,7 @@ if prompt:
st.session_state.reply_ts = ""
st.session_state.current_prompt = ""
st.session_state.last_reply_time = int(time.time())
st.session_state.show_full_history = False
st.rerun()
def _slash_missing(name):
st.session_state.messages.extend([
@@ -379,6 +386,11 @@ if prompt:
target = sessions[idx][0] if 0 <= idx < len(sessions) else None
result = handle_frontend_command(agent, cmd)
history = extract_ui_messages(target) if target and result.startswith('✅') else None
if history:
for x in history:
if x['role'] == 'assistant' and len(x['content']) > 120_000:
m = re.search(r'\**LLM Running \(Turn \d+\) \.\.\.\**', x['content'][-120_000:])
x['content'] = x['content'][-120_000 + m.start():] if m else ''
tail = [{"role": "assistant", "content": result, "time": ts}]
if history: st.session_state.messages[:] = history + tail
else: st.session_state.messages.extend([{"role": "user", "content": cmd, "time": ts}] + tail)
+9 -1
View File
@@ -448,6 +448,8 @@ def _stream_with_retry(sess, url, headers, payload, parse_fn):
return None if ra and ra > cap else max(0.5, ra or min(30.0, 3.0 * (2 ** attempt)))
for attempt in range(sess.max_retries + 1):
streamed = False
STATS.update(t_start=time.time(), t_ttft=None)
if not sess.stream: STATS['t_ttft'] = STATS['t_start']
try:
with requests.post(url, headers=headers, json=payload, stream=sess.stream,
timeout=(sess.connect_timeout, sess.read_timeout), proxies=sess.proxies, verify=sess.verify) as r:
@@ -464,9 +466,15 @@ def _stream_with_retry(sess, url, headers, payload, parse_fn):
yield err; return [{"type": "text", "text": err}]
gen = parse_fn(r)
try:
while True: chunk = next(gen); streamed = True; yield chunk
while True:
if getattr(sess, 'should_stop', None) and sess.should_stop():
STATS['t_end'] = time.time(); return []
chunk = next(gen)
if chunk and STATS.get('t_ttft') is None: STATS['t_ttft'] = time.time()
streamed = True; yield chunk
except StopIteration as e:
if not e.value and not streamed: raise requests.ConnectionError("empty response")
STATS['t_end'] = time.time()
return e.value or []
except (requests.Timeout, requests.ConnectionError, requests.exceptions.ChunkedEncodingError) as e:
err = f"!!!Error: {type(e).__name__}: {e}" if str(e) else f"!!!Error: {type(e).__name__}"