feat: harden video analysis and runtime resilience
This commit is contained in:
1 parent
5ada543a09
commit
bb16991f2c
55 files changed
+5023
-437
No files matched your search
@@ -88,6 +88,43 @@ class SessionRepository:
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def get_latest_video_recordings_map(self) -> dict[str, dict[str, Any]]:
|
||||
"""Return the newest recording row for every session in one query.
|
||||
|
||||
The task-list endpoint renders every session at once. Calling
|
||||
``get_video_recording_for_session`` in that loop used to open a new
|
||||
SQLite connection for every row, which made a refresh progressively
|
||||
slower as history grew.
|
||||
"""
|
||||
try:
|
||||
with db_session(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name='video_recordings'"
|
||||
)
|
||||
if cursor.fetchone() is None:
|
||||
return {}
|
||||
|
||||
rows = cursor.execute(
|
||||
"SELECT * FROM video_recordings WHERE session_id IS NOT NULL "
|
||||
"ORDER BY start_time DESC"
|
||||
).fetchall()
|
||||
latest: dict[str, dict[str, Any]] = {}
|
||||
for row in rows:
|
||||
recording = dict(row)
|
||||
session_id = str(recording.get("session_id") or "")
|
||||
if not session_id or session_id in latest:
|
||||
continue
|
||||
if "status" not in recording:
|
||||
recording["status"] = (
|
||||
"ready" if recording.get("end_time") is not None else "recording"
|
||||
)
|
||||
recording.setdefault("error", None)
|
||||
latest[session_id] = recording
|
||||
return latest
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
def mark_recording_failed_if_pending(self, session_id: str, error: str) -> bool:
|
||||
"""Close a recording lifecycle when its worker exits before finalization."""
|
||||
try:
|
||||
@@ -114,6 +151,40 @@ class SessionRepository:
|
||||
rows = cursor.fetchall()
|
||||
return [r[0] for r in rows if r and r[0]]
|
||||
|
||||
def get_llm_traces_for_profiles_map(
|
||||
self, session_ids: list[str] | None = None, limit: int = 3
|
||||
) -> dict[str, list[str]]:
|
||||
"""Return bounded LLM payloads for only the sessions needing fallback."""
|
||||
if session_ids is not None and not session_ids:
|
||||
return {}
|
||||
|
||||
session_filter = ""
|
||||
params: list[Any] = []
|
||||
if session_ids is not None:
|
||||
placeholders = ",".join("?" for _ in session_ids)
|
||||
session_filter = f" AND session_id IN ({placeholders})"
|
||||
params.extend(session_ids)
|
||||
params.append(limit)
|
||||
|
||||
with db_session(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"SELECT session_id, payload FROM ("
|
||||
" SELECT session_id, payload, ROW_NUMBER() OVER ("
|
||||
" PARTITION BY session_id ORDER BY timestamp ASC"
|
||||
" ) AS row_number FROM traces WHERE type = 'llm_call'"
|
||||
f"{session_filter}"
|
||||
") WHERE row_number <= ?",
|
||||
params,
|
||||
)
|
||||
result: dict[str, list[str]] = {}
|
||||
for row in cursor.fetchall():
|
||||
session_id = str(row[0] or "")
|
||||
payload = row[1]
|
||||
if session_id and payload:
|
||||
result.setdefault(session_id, []).append(payload)
|
||||
return result
|
||||
|
||||
def get_agent_trace_names(self, session_id: str) -> list[str]:
|
||||
try:
|
||||
with db_session(self.db_path) as conn:
|
||||
@@ -127,6 +198,23 @@ class SessionRepository:
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
def get_agent_trace_names_map(self) -> dict[str, list[str]]:
|
||||
"""Return distinct agent/LLM trace names grouped by session."""
|
||||
with db_session(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"SELECT session_id, name FROM traces "
|
||||
"WHERE type IN ('agent', 'llm_call') AND session_id IS NOT NULL "
|
||||
"GROUP BY session_id, name"
|
||||
)
|
||||
result: dict[str, list[str]] = {}
|
||||
for row in cursor.fetchall():
|
||||
session_id = str(row[0] or "")
|
||||
name = row[1]
|
||||
if session_id and name:
|
||||
result.setdefault(session_id, []).append(name)
|
||||
return result
|
||||
|
||||
def get_session_by_id(self, session_id: str) -> dict[str, Any] | None:
|
||||
try:
|
||||
with db_session(self.db_path) as conn:
|
||||
|
||||
@@ -163,7 +163,12 @@ class StepRepository:
|
||||
co = (len(resp_text) // 4) + (resp_images * 258)
|
||||
return int(pr), int(co), int(pr + co)
|
||||
|
||||
def _clean_tool_payload(self, payload_raw: Any, trace_type: str | None = None) -> Any:
|
||||
def _clean_tool_payload(
|
||||
self,
|
||||
payload_raw: Any,
|
||||
trace_type: str | None = None,
|
||||
trace_name: str | None = None,
|
||||
) -> Any:
|
||||
if not payload_raw:
|
||||
return None
|
||||
payload_obj = payload_raw
|
||||
@@ -204,8 +209,18 @@ class StepRepository:
|
||||
)
|
||||
result_payload: dict[str, Any] = {"args": cleaned_args}
|
||||
|
||||
# Extract structured action execution results
|
||||
# Extract structured action execution results. Video analysis keeps its
|
||||
# result because the task stream needs to distinguish cached, partial,
|
||||
# recovering, waiting, completed, and terminal outcomes. Other tools
|
||||
# retain the historical compact payload to avoid sending large blobs.
|
||||
res = payload_obj.get("result")
|
||||
is_video_analysis = str(trace_name or "").lower() in {
|
||||
"video_analysis",
|
||||
"video_analyzer",
|
||||
"video_analyzer_pure",
|
||||
"spawn_sub_agent",
|
||||
"analyze_audio_only",
|
||||
}
|
||||
if isinstance(res, dict):
|
||||
if res.get("post_image_name"):
|
||||
result_payload["post_image_name"] = res["post_image_name"]
|
||||
@@ -213,8 +228,12 @@ class StepRepository:
|
||||
result_payload["pre_image_name"] = res["pre_image_name"]
|
||||
if res.get("outcome"):
|
||||
result_payload["outcome"] = res["outcome"]
|
||||
if is_video_analysis:
|
||||
result_payload["result"] = self._clean_value(res)
|
||||
elif res:
|
||||
res_str = str(res)
|
||||
if is_video_analysis:
|
||||
result_payload["result"] = res_str
|
||||
match = re.search(r"([a-f0-9]{64})", res_str)
|
||||
if match:
|
||||
result_payload["post_image_name"] = match.group(1)
|
||||
@@ -259,7 +278,7 @@ class StepRepository:
|
||||
return trace_dict
|
||||
|
||||
trace_dict["payload"] = self._clean_tool_payload(
|
||||
trace_dict.get("payload"), trace_dict.get("type")
|
||||
trace_dict.get("payload"), trace_dict.get("type"), trace_dict.get("name")
|
||||
)
|
||||
return trace_dict
|
||||
|
||||
|
||||
Reference in new issue
Block a user