From a66803651adc963ab0667ab969e8d2da17b482a8 Mon Sep 17 00:00:00 2001 From: chenmingxuan Date: Sat, 17 Oct 2026 14:55:45 +0800 Subject: [PATCH] fix: improve cross-device evidence, request diagnostics and prompt efficiency --- apps/admin_console/routers/sessions.py | 15 + apps/admin_console/services/report_export.py | 3 + .../services/request_diagnostics.py | 72 ++++ .../agent-stream/agent-stream.component.html | 3 +- .../agent-stream/agent-stream.component.ts | 5 +- .../floating-video-player.component.html | 25 +- .../floating-video-player.component.scss | 48 +++ .../floating-video-player.component.spec.ts | 118 ++++++ .../floating-video-player.component.ts | 70 +++- .../llm-request-status.component.html | 49 +++ .../llm-request-status.component.scss | 26 ++ .../llm-request-status.component.spec.ts | 81 +++++ .../llm-request-status.component.ts | 94 +++++ .../task-history.component.spec.ts | 123 ++++++- .../task-history/task-history.component.ts | 26 +- .../src/app/services/agent.service.ts | 7 +- artemis/agents/checker/checker.py | 58 ++- artemis/agents/flash/context_compressor.py | 15 +- artemis/agents/flash/flash_runner.md | 1 + artemis/agents/flash/runner.py | 132 +++++-- artemis/agents/flash/summarizer.py | 36 ++ artemis/agents/operator/operator.json | 2 +- artemis/agents/operator/operator.py | 73 +++- artemis/agents/operator/prompts.py | 25 ++ artemis/agents/planner/planner.py | 4 +- artemis/agents/summarizer/summarizer.py | 48 ++- artemis/agents/validator/execution_loop.py | 21 +- artemis/agents/validator/tool_declarations.py | 7 +- artemis/config/constants.py | 5 + artemis/data_engine/engine.py | 4 +- artemis/data_engine/history_reader.py | 4 +- artemis/data_engine/step_evidence.py | 34 ++ artemis/data_engine/storage.py | 3 + artemis/exploration/runtime.py | 5 +- artemis/llm/diagnostics.py | 215 +++++++++++ artemis/llm/router.py | 7 + artemis/llm/usage.py | 2 + artemis/memory/transcript.py | 39 +- artemis/plugins/models.py | 1 + artemis/plugins/prompt_budget.py | 56 +++ artemis/plugins/results.py | 56 +++ artemis/plugins/runtime.py | 95 ++++- artemis/sdk/builders/agent_config_builder.py | 2 + artemis/sdk/types/agent.py | 2 + artemis/services/llm.py | 132 ++++++- docs/automation-workspace.md | 12 + docs/plugins.md | 16 +- tests/unit/admin_console/test_automation.py | 6 +- .../agents/test_checker_structured_report.py | 93 +++++ tests/unit/agents/test_flash_acceptance.py | 33 ++ .../test_flash_cross_target_observation.py | 219 +++++++++++ tests/unit/agents/test_operator_transcript.py | 103 ++++++ tests/unit/agents/test_summarizer.py | 46 +++ tests/unit/test_openai_responses.py | 65 +++- tests/unit/test_plugin_evidence.py | 4 +- tests/unit/test_plugin_result_delivery.py | 212 +++++++++++ tests/unit/test_plugins.py | 90 ++++- tests/unit/test_prompt_efficiency.py | 134 +++++++ tests/unit/test_recording_config.py | 35 ++ tests/unit/test_request_diagnostics.py | 340 ++++++++++++++++++ 60 files changed, 3097 insertions(+), 160 deletions(-) create mode 100644 apps/admin_console/services/request_diagnostics.py create mode 100644 apps/showcase_ui/src/app/components/llm-request-status/llm-request-status.component.html create mode 100644 apps/showcase_ui/src/app/components/llm-request-status/llm-request-status.component.scss create mode 100644 apps/showcase_ui/src/app/components/llm-request-status/llm-request-status.component.spec.ts create mode 100644 apps/showcase_ui/src/app/components/llm-request-status/llm-request-status.component.ts create mode 100644 artemis/data_engine/step_evidence.py create mode 100644 artemis/llm/diagnostics.py create mode 100644 artemis/plugins/prompt_budget.py create mode 100644 artemis/plugins/results.py create mode 100644 tests/unit/agents/test_flash_cross_target_observation.py create mode 100644 tests/unit/test_plugin_result_delivery.py create mode 100644 tests/unit/test_prompt_efficiency.py create mode 100644 tests/unit/test_request_diagnostics.py diff --git a/apps/admin_console/routers/sessions.py b/apps/admin_console/routers/sessions.py index b116922..d53e4be 100644 --- a/apps/admin_console/routers/sessions.py +++ b/apps/admin_console/routers/sessions.py @@ -17,6 +17,7 @@ import json import time from uuid import UUID from fastapi import APIRouter, HTTPException +from fastapi.responses import Response from artemis.config import DB_PATH, TRACES_PATH from artemis.runtime import trace_store @@ -35,6 +36,20 @@ from apps.admin_console.services.exploration_listing import exploration_metadata router = APIRouter(tags=["sessions"]) +@router.get("/api/sessions/{session_id}/request-diagnostics") +def get_request_diagnostics(session_id: str, download: bool = False): + from apps.admin_console.services.request_diagnostics import request_diagnostics + + result = request_diagnostics(session_id, limit=None if download else 100) + if not download: + return result + return Response( + json.dumps(result, ensure_ascii=False, indent=2), + media_type="application/json", + headers={"Content-Disposition": 'attachment; filename="model-request-diagnostics.json"'}, + ) + + @router.get("/api/sessions") async def list_sessions(): # The body does blocking work (sqlite queries, filesystem scans, ffmpeg diff --git a/apps/admin_console/services/report_export.py b/apps/admin_console/services/report_export.py index 84aac11..552eee0 100644 --- a/apps/admin_console/services/report_export.py +++ b/apps/admin_console/services/report_export.py @@ -7,6 +7,7 @@ from pathlib import Path import re import threading import zipfile +from apps.admin_console.services.request_diagnostics import diagnostics_json from docx import Document from docx.enum.table import WD_CELL_VERTICAL_ALIGNMENT @@ -900,6 +901,7 @@ def export_session_media(session_id: str, kind: str) -> Path: else zipfile.ZIP_DEFLATED, ) if kind == "all": + archive.writestr(prefix + "模型请求诊断.json", diagnostics_json(session_id)) archive.writestr( prefix + "证据索引.json", json.dumps( @@ -980,6 +982,7 @@ def export_batch(batch_id: str, progress: Callable[[int, int, str], None] | None if source.suffix.lower() in {".mp4", ".mkv", ".webm"} else zipfile.ZIP_DEFLATED, ) + archive.writestr(f"{prefix}/模型请求诊断.json", diagnostics_json(report["id"])) archive.writestr( f"{prefix}/证据索引.json", json.dumps( diff --git a/apps/admin_console/services/request_diagnostics.py b/apps/admin_console/services/request_diagnostics.py new file mode 100644 index 0000000..9adeb03 --- /dev/null +++ b/apps/admin_console/services/request_diagnostics.py @@ -0,0 +1,72 @@ +"""Read/export model diagnostics from the task's existing trace database.""" + +from datetime import UTC, datetime +import json +import time + +from apps.admin_console.database.connection import db_session + + +def request_diagnostics(session_id: str, *, db_path=None, limit: int | None = None) -> dict: + with db_session(db_path) as conn: + session = conn.execute( + "SELECT status FROM sessions WHERE session_id = ?", (session_id,) + ).fetchone() + sql = ( + "SELECT payload FROM traces WHERE session_id = ? AND type = 'llm_diagnostic' " + "ORDER BY timestamp DESC" + ) + params: tuple = (session_id,) + if limit is not None: + sql += " LIMIT ?" + params += (limit,) + rows = conn.execute(sql, params).fetchall() + requests = [] + seen = set() + running = bool(session and session["status"] in {"running", "resuming"}) + for row in rows: + try: + value = json.loads(row["payload"]) + except (ValueError, TypeError): + continue + if not isinstance(value, dict): + continue + request_id = value.get("request_id") + value["active"] = ( + running + and request_id not in seen + and value.get("phase") + in { + "waiting_response", + "waiting_output", + "receiving_output", + "generating_tool_arguments", + "retry_wait", + } + ) + seen.add(request_id) + value["timestamps_utc"] = { + field: datetime.fromtimestamp(value[field], UTC).isoformat() + for field in ( + "started_at", + "headers_at", + "first_output_at", + "last_output_at", + "ended_at", + "retry_at", + ) + if isinstance(value.get(field), (int, float)) + } + requests.append(value) + return { + "schema_version": 1, + "session_id": session_id, + "session_status": session["status"] if session else "unknown", + "collected_at": time.time(), + "requests": requests, + "note": "时间戳为 Unix 秒;timestamps_utc 为 UTC。HTTP 200 不代表完整输出。usage=null 表示服务未报告用量。历史任务没有记录的字段无法补推。", + } + + +def diagnostics_json(session_id: str) -> str: + return json.dumps(request_diagnostics(session_id), ensure_ascii=False, indent=2) diff --git a/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.html b/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.html index 18a353d..f52ba46 100644 --- a/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.html +++ b/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.html @@ -509,6 +509,7 @@ limitations under the License.
+ @if (agentService.isSessionContentLoading()) {
progress_activity @@ -516,7 +517,7 @@ limitations under the License.
} - @if (!agentService.isSessionContentLoading() && phases().length === 0 && !showPlanningLoader() && startupWorkItems().length === 0) { + @if (!agentService.isSessionContentLoading() && phases().length === 0 && !showPlanningLoader() && !hasModelRequest() && startupWorkItems().length === 0) {
sensors

尚未选择执行记录

diff --git a/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.ts b/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.ts index 8eb4dfb..927cac1 100644 --- a/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.ts +++ b/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.ts @@ -2,6 +2,7 @@ import { ExplorationMessage, Demonstration } from '../../services/exploration.se import { mergeConversation } from '../../utils/exploration-timeline.util'; import { TaskIdCopyComponent } from '../task-id-copy/task-id-copy.component'; import { TaskHistoryComponent } from '../task-history/task-history.component'; +import { LlmRequestStatusComponent } from '../llm-request-status/llm-request-status.component'; import { AutomationPanelComponent } from '../automation-panel/automation-panel.component'; import { UiLabelPipe } from '../../utils/ui-label.pipe'; /** @@ -246,7 +247,7 @@ export type { MarkdownSegment, MarkdownLine, NoteMilestone, ParsedNote, StreamRe @Component({ selector: 'app-agent-stream', standalone: true, - imports: [TaskIdCopyComponent, UiLabelPipe, CommonModule, FormsModule, OverlayModule, TaskHistoryComponent, AutomationPanelComponent, TestResultBadgeComponent, TestResultCardComponent, TaskBatchFilterComponent], + imports: [LlmRequestStatusComponent, TaskIdCopyComponent, UiLabelPipe, CommonModule, FormsModule, OverlayModule, TaskHistoryComponent, AutomationPanelComponent, TestResultBadgeComponent, TestResultCardComponent, TaskBatchFilterComponent], templateUrl: './agent-stream.component.html', styleUrl: './agent-stream.component.scss', changeDetection: ChangeDetectionStrategy.OnPush @@ -396,6 +397,7 @@ export class AgentStreamComponent implements AfterViewInit { public expandedActionCards = signal>(new Set()); public collapsedActionCards = signal>(new Set()); public retryClock = signal(Date.now()); + public hasModelRequest = signal(false); // Performance caches for parameter and event extractions private actionParamsCache = new WeakMap(); @@ -470,6 +472,7 @@ export class AgentStreamComponent implements AfterViewInit { }); public showPlanningLoader = computed(() => { + if (this.hasModelRequest()) return false; const isRunning = this.agentService.agentStatus() === 'running'; const currentSessionId = this.agentService.currentSessionId(); const runningSessionId = this.agentService.runningSessionId(); diff --git a/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.html b/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.html index 24919be..d4af4ad 100644 --- a/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.html +++ b/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.html @@ -29,6 +29,7 @@ limitations under the License. [class.theater]="isTheaterMode()" [style.left.px]="!isTheaterMode() ? posX() : null" [style.top.px]="!isTheaterMode() ? posY() : null" + [style.--player-top]="posY() + 'px'" >
@@ -111,6 +112,9 @@ limitations under the License.
此来源暂无可播放录像,请查看步骤截图或日志。
} } + @if (!agentService.isVideoMinimized() && agentService.playerMode() === 'video' && missingRecordingLabels().length) { +
{{ missingRecordingLabels().join('、') }}步骤没有对应录像,可切换「步骤」查看已有截图。
+ } @if (!agentService.isVideoMinimized()) {
@@ -369,8 +373,14 @@ limitations under the License. @if (!videoLoadError()) {