Meteord commited on
Commit
218632a
·
verified ·
1 Parent(s): 2afc083

Sync from GitHub via hub-sync

Browse files
Files changed (2) hide show
  1. app/app.py +96 -2
  2. app/ckan_agent.py +31 -1
app/app.py CHANGED
@@ -356,7 +356,7 @@ def _retrieval_stream_prefix(prompt: str, endpoint: str) -> str:
356
  progress_refs = ", ".join(f"progress{index}" for index in range(1, 13))
357
  return "\n".join(
358
  [
359
- "root = Card([header, suggestion, progress, candidates, callout, followups])",
360
  'header = CardHeader("Finding dataset", "Searching CKAN and inspecting candidates")',
361
  f'context = TextContent({_json_arg_safe(f"Request: {prompt} | Endpoint: {endpoint}")}, "small")',
362
  f'progress = ListBlock([{progress_refs}], "number")',
@@ -378,6 +378,10 @@ def _agent_stream_suffix(result: AgentResult, progress_count: int) -> str:
378
  lines = []
379
  for step in range(progress_count + 1, 13):
380
  lines.append(f'progress{step} = ListItem("waiting", "")')
 
 
 
 
381
  columns = list(rows[0].keys())
382
  for index, column in enumerate(columns):
383
  lines.append(f'candidate_col{index + 1} = Col({_json_arg_safe(column)}, {_json_arg_safe([row.get(column) for row in rows])}, "string")')
@@ -400,6 +404,93 @@ def _agent_stream_suffix(result: AgentResult, progress_count: int) -> str:
400
  return "\n".join(lines)
401
 
402
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
403
  def _json_arg_safe(value: Any) -> str:
404
  return json.dumps(value, ensure_ascii=False, default=str)
405
 
@@ -456,7 +547,10 @@ async def _stream_retrieval_workflow_response(
456
  if item["type"] == "event":
457
  event: AgentEvent = item["event"]
458
  progress_count += 1
459
- trace_events.append({"name": event.type, "detail": event.detail})
 
 
 
460
  if progress_count <= 12:
461
  line = f"progress{progress_count} = ListItem({_json_arg_safe(event.type)}, {_json_arg_safe(event.detail)})"
462
  chunks.append(line)
 
356
  progress_refs = ", ".join(f"progress{index}" for index in range(1, 13))
357
  return "\n".join(
358
  [
359
+ "root = Card([header, suggestion, progress, tool_details, candidates, callout, followups])",
360
  'header = CardHeader("Finding dataset", "Searching CKAN and inspecting candidates")',
361
  f'context = TextContent({_json_arg_safe(f"Request: {prompt} | Endpoint: {endpoint}")}, "small")',
362
  f'progress = ListBlock([{progress_refs}], "number")',
 
378
  lines = []
379
  for step in range(progress_count + 1, 13):
380
  lines.append(f'progress{step} = ListItem("waiting", "")')
381
+ tool_rows = _agent_tool_detail_rows(result.events)
382
+ for index, column in enumerate(["step", "event", "tool", "detail", "payload"]):
383
+ lines.append(f'tool_col{index + 1} = Col({_json_arg_safe(column)}, {_json_arg_safe([row[column] for row in tool_rows])}, "string")')
384
+ lines.append('tool_details = Table([tool_col1, tool_col2, tool_col3, tool_col4, tool_col5])')
385
  columns = list(rows[0].keys())
386
  for index, column in enumerate(columns):
387
  lines.append(f'candidate_col{index + 1} = Col({_json_arg_safe(column)}, {_json_arg_safe([row.get(column) for row in rows])}, "string")')
 
404
  return "\n".join(lines)
405
 
406
 
407
+ def _agent_tool_detail_rows(events: list[AgentEvent]) -> list[dict[str, str]]:
408
+ rows = []
409
+ for index, event in enumerate(events, start=1):
410
+ if event.type not in {"model_action", "tool_call", "tool_result", "selection", "retry", "error"}:
411
+ continue
412
+ rows.append(
413
+ {
414
+ "step": str(index),
415
+ "event": event.type,
416
+ "tool": _agent_event_tool_name(event),
417
+ "detail": _shorten(event.detail, 140),
418
+ "payload": _shorten(_agent_event_payload(event), 220),
419
+ }
420
+ )
421
+ if not rows:
422
+ rows.append({"step": "-", "event": "waiting", "tool": "-", "detail": "No tool calls yet.", "payload": ""})
423
+ return rows[:16]
424
+
425
+
426
+ def _agent_event_tool_name(event: AgentEvent) -> str:
427
+ data = event.data or {}
428
+ action = data.get("action")
429
+ if isinstance(action, dict):
430
+ return str(action.get("action") or action.get("tool") or event.type)
431
+ tool_result = data.get("tool_result")
432
+ if isinstance(tool_result, dict):
433
+ return str(tool_result.get("tool") or event.type)
434
+ resource = data.get("resource")
435
+ if isinstance(resource, dict):
436
+ return str(resource.get("format") or event.type)
437
+ return event.type
438
+
439
+
440
+ def _agent_event_payload(event: AgentEvent) -> str:
441
+ data = event.data or {}
442
+ action = data.get("action")
443
+ if isinstance(action, dict):
444
+ args = action.get("args")
445
+ confidence = action.get("confidence")
446
+ source = action.get("source")
447
+ return json.dumps({"args": args, "confidence": confidence, "source": source}, ensure_ascii=False, default=str)
448
+ tool_result = data.get("tool_result")
449
+ if isinstance(tool_result, dict):
450
+ summary = {
451
+ "ok": tool_result.get("ok"),
452
+ "summary": tool_result.get("summary"),
453
+ "data": _compact_tool_result_data(tool_result.get("data")),
454
+ "error": tool_result.get("error") or "",
455
+ }
456
+ return json.dumps(summary, ensure_ascii=False, default=str)
457
+ resource = data.get("resource")
458
+ if isinstance(resource, dict):
459
+ return json.dumps(
460
+ {
461
+ "package": resource.get("package_title"),
462
+ "resource": resource.get("name"),
463
+ "format": resource.get("format"),
464
+ "url": resource.get("url"),
465
+ },
466
+ ensure_ascii=False,
467
+ default=str,
468
+ )
469
+ if data:
470
+ return json.dumps(data, ensure_ascii=False, default=str)
471
+ return ""
472
+
473
+
474
+ def _compact_tool_result_data(data: Any) -> Any:
475
+ if not isinstance(data, dict):
476
+ return data
477
+ compact: dict[str, Any] = {}
478
+ for key, value in data.items():
479
+ if isinstance(value, list):
480
+ compact[key] = value[:5]
481
+ compact[f"{key}_count"] = len(value)
482
+ else:
483
+ compact[key] = value
484
+ return compact
485
+
486
+
487
+ def _shorten(value: Any, max_chars: int) -> str:
488
+ text = str(value)
489
+ if len(text) <= max_chars:
490
+ return text
491
+ return text[: max_chars - 1].rstrip() + "…"
492
+
493
+
494
  def _json_arg_safe(value: Any) -> str:
495
  return json.dumps(value, ensure_ascii=False, default=str)
496
 
 
547
  if item["type"] == "event":
548
  event: AgentEvent = item["event"]
549
  progress_count += 1
550
+ trace_event = {"name": event.type, "detail": event.detail}
551
+ if event.data:
552
+ trace_event["data"] = event.data
553
+ trace_events.append(trace_event)
554
  if progress_count <= 12:
555
  line = f"progress{progress_count} = ListItem({_json_arg_safe(event.type)}, {_json_arg_safe(event.detail)})"
556
  chunks.append(line)
app/ckan_agent.py CHANGED
@@ -125,6 +125,7 @@ def run_ckan_agent(
125
  valid_action, error = validate_action(action, session)
126
  if error:
127
  _record_event(session, AgentEvent("retry", error, {"action": asdict(action)}), on_event)
 
128
  valid_action = fallback_action(session)
129
  _record_event(session, AgentEvent("model_action", f"{valid_action.action}: {valid_action.reason}", {"action": asdict(valid_action)}), on_event)
130
 
@@ -220,6 +221,8 @@ def validate_action(action: AgentAction, session: AgentSession) -> tuple[AgentAc
220
  rows = int(action.args.get("rows", 10) or 10)
221
  action.args["rows"] = max(1, min(rows, 25))
222
  if action.action in {"group_list", "organization_list"}:
 
 
223
  rows = int(action.args.get("rows", 10) or 10)
224
  action.args["rows"] = max(1, min(rows, 25))
225
  if action.action == "package_show":
@@ -431,7 +434,34 @@ def _append_tool_result(session: AgentSession, action: AgentAction, result: Tool
431
  session.messages.append(
432
  {
433
  "role": "user",
434
- "content": "Tool observation:\n" + json.dumps({"action": asdict(action), "result": asdict(result)}, ensure_ascii=False, default=str),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
435
  }
436
  )
437
 
 
125
  valid_action, error = validate_action(action, session)
126
  if error:
127
  _record_event(session, AgentEvent("retry", error, {"action": asdict(action)}), on_event)
128
+ _append_validation_error(session, action, error)
129
  valid_action = fallback_action(session)
130
  _record_event(session, AgentEvent("model_action", f"{valid_action.action}: {valid_action.reason}", {"action": asdict(valid_action)}), on_event)
131
 
 
221
  rows = int(action.args.get("rows", 10) or 10)
222
  action.args["rows"] = max(1, min(rows, 25))
223
  if action.action in {"group_list", "organization_list"}:
224
+ if action.action in session.catalog_tools_run:
225
+ return action, f"{action.action} already ran in this run; choose tag_search, package_search, package_show, or ask_clarification instead."
226
  rows = int(action.args.get("rows", 10) or 10)
227
  action.args["rows"] = max(1, min(rows, 25))
228
  if action.action == "package_show":
 
434
  session.messages.append(
435
  {
436
  "role": "user",
437
+ "content": "Tool observation:\n" + json.dumps(
438
+ {
439
+ "action": asdict(action),
440
+ "result": asdict(result),
441
+ "catalog_tools_already_run": sorted(session.catalog_tools_run),
442
+ "instruction": "Do not repeat catalog discovery tools that already ran. Choose the next useful tool.",
443
+ },
444
+ ensure_ascii=False,
445
+ default=str,
446
+ ),
447
+ }
448
+ )
449
+
450
+
451
+ def _append_validation_error(session: AgentSession, action: AgentAction, error: str) -> None:
452
+ session.messages.append(
453
+ {
454
+ "role": "user",
455
+ "content": "Rejected model action:\n" + json.dumps(
456
+ {
457
+ "action": asdict(action),
458
+ "error": error,
459
+ "catalog_tools_already_run": sorted(session.catalog_tools_run),
460
+ "instruction": "Choose a different valid action. Do not repeat rejected actions.",
461
+ },
462
+ ensure_ascii=False,
463
+ default=str,
464
+ ),
465
  }
466
  )
467