| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248 |
- """obagent is the only visualization backend for find_agent_v2."""
- from __future__ import annotations
- import os
- from contextlib import contextmanager
- from dataclasses import dataclass
- from typing import Any, Iterator
- OBAGENT_PROJECT = "find_agent_v2"
- OBAGENT_AGENT = "find_agent_v2"
- OBAGENT_ROUND_ANCHOR = {"in": "run", "on": ["graph"]}
- DEFAULT_ENDPOINT = "http://ob.aiddit.com"
- MODULE_TITLES = {
- "supervisor": "自主编排",
- "search": "候选搜索",
- "evidence": "证据补全",
- "evaluator": "评估与分池",
- "report": "结果报告",
- }
- GRAPH_SPEC = {
- "nodes": [
- {"key": key, "title": title,
- "module_key": f"{OBAGENT_PROJECT}.{OBAGENT_AGENT}.{key}"}
- for key, title in MODULE_TITLES.items()
- if key != "report"
- ]
- }
- GRAPH_MODULE_KEYS = {
- key: f"{OBAGENT_PROJECT}.{OBAGENT_AGENT}.{key}"
- for key in ("supervisor", "search", "evidence", "evaluator")
- }
- GRAPH_TITLES = {
- key: MODULE_TITLES[key]
- for key in ("supervisor", "search", "evidence", "evaluator")
- }
- def graph_spec_for(app) -> dict[str, Any]:
- """Build the visualization spec from the compiled LangGraph itself."""
- try:
- from obagent_sdk.integrations.langgraph import graph_spec
- return graph_spec(
- app,
- module_keys=GRAPH_MODULE_KEYS,
- titles=GRAPH_TITLES,
- )
- except Exception:
- return GRAPH_SPEC
- _configured = False
- @dataclass(frozen=True)
- class InputSlot:
- title: str
- value: str
- key: str
- source: str
- optional: bool = True
- class NullRunHandle:
- run_uid: str | None = None
- def finish(self, final_output: Any = None) -> None:
- del final_output
- class NullModuleHandle:
- def declare(self, *, fallback: str, **_kwargs) -> str:
- return fallback
- def record_react(self, **_kwargs) -> None:
- return None
- def set_output(self, _output: Any, *, ok: bool = True) -> None:
- del ok
- class NullObserver:
- """Test-only observer. Production construction uses :class:`ObagentObserver`."""
- @contextmanager
- def run(self, **_kwargs) -> Iterator[NullRunHandle]:
- yield NullRunHandle()
- @contextmanager
- def round(self, **_kwargs) -> Iterator[NullModuleHandle]:
- yield NullModuleHandle()
- @contextmanager
- def node(self, **_kwargs) -> Iterator[NullModuleHandle]:
- yield NullModuleHandle()
- def console_endpoint() -> str:
- return (os.getenv("OBAGENT_ENDPOINT") or DEFAULT_ENDPOINT).strip().rstrip("/")
- def run_url(run_uid: str) -> str:
- return f"{console_endpoint()}/#client_uid={run_uid}"
- def ensure_configured() -> None:
- global _configured
- if _configured:
- return
- from obagent_sdk import configure
- kwargs: dict[str, Any] = {
- "endpoint": console_endpoint(),
- "project": OBAGENT_PROJECT,
- "timeout": float(os.getenv("OBAGENT_TIMEOUT", "120")),
- "timeout_per_op": float(os.getenv("OBAGENT_TIMEOUT_PER_OP", "1.0")),
- }
- api_key = os.getenv("OBAGENT_API_KEY", "").strip()
- if api_key:
- kwargs["api_key"] = api_key
- wal_dir = os.getenv("OBAGENT_WAL_DIR", "").strip()
- if wal_dir:
- kwargs["wal_dir"] = wal_dir
- enabled = os.getenv("OBAGENT_ENABLED", "1").strip().lower()
- if enabled in {"0", "false", "no", "off"}:
- kwargs["enabled"] = False
- configure(**kwargs)
- _configured = True
- def _blocks(slots: tuple[InputSlot, ...]):
- from obagent_sdk.observe import InputBlock
- return [
- InputBlock(
- slot.title,
- slot.value,
- key=slot.key,
- source=slot.source,
- optional=slot.optional,
- )
- for slot in slots
- ]
- class _ModuleHandle:
- def __init__(self, ctx) -> None:
- self.ctx = ctx
- def declare(
- self,
- *,
- fallback: str,
- system_prompt: str,
- slots: tuple[InputSlot, ...],
- tools: tuple,
- model: str,
- refs: dict[str, str] | None = None,
- ) -> str:
- del fallback, refs
- return self.ctx.declare(
- system_prompt=system_prompt,
- blocks=_blocks(slots),
- tools=list(tools),
- model=model,
- )
- def record_react(self, *, output: dict[str, Any], ok: bool) -> None:
- # One stable code stage per agent module; the payload holds the complete
- # custom ReAct message chain because this project does not use LangChain hooks.
- self.ctx.record_stage(
- "react",
- fn=_react_stage_identity,
- title="ReAct 运行",
- output=output,
- ok=ok,
- )
- def set_output(self, output: Any, *, ok: bool = True) -> None:
- self.ctx.set_output(output, ok=ok)
- def _react_stage_identity(messages: list[dict[str, Any]]) -> list[dict[str, Any]]:
- """Stable source identity for the custom ReAct runtime stage."""
- return messages
- class ObagentObserver:
- @contextmanager
- def run(
- self,
- *,
- run_id: str,
- demand_word: str,
- model: str,
- models_by_role: dict[str, str],
- ):
- ensure_configured()
- from obagent_sdk import observe
- with observe.run(
- agent=OBAGENT_AGENT,
- objective=f"寻找视频 · {demand_word} · run_id={run_id}",
- project=OBAGENT_PROJECT,
- model_name=model,
- payload={"run_id": run_id, "demand_word": demand_word,
- "models_by_role": models_by_role},
- tags={"engine": "staged-react", "version": "v2"},
- meta={"run_name": f"寻找 Agent v2 · {demand_word}",
- "run_id": run_id, "models_by_role": models_by_role},
- round_anchor=OBAGENT_ROUND_ANCHOR,
- ) as handle:
- yield handle
- @contextmanager
- def round(self, *, round_index: int, spec: dict[str, Any] | None = None):
- from obagent_sdk import observe
- with observe.module(
- "graph",
- kind="workflow",
- title=f"寻找 Agent · 第 {round_index} 轮",
- module_key="graph",
- spec=spec or GRAPH_SPEC,
- ) as ctx:
- from obagent_sdk.observe import InputBlock
- ctx.declare(blocks=[InputBlock(
- "当前轮次", str(round_index), key="round_index",
- source="FindAgentV2.begin_round", optional=False,
- )])
- yield _ModuleHandle(ctx)
- @contextmanager
- def node(self, *, node: str, branch_key: str = ""):
- from obagent_sdk import observe
- with observe.module(
- node,
- kind="agent",
- title=MODULE_TITLES[node],
- module_key=node,
- branch_key=branch_key,
- ) as ctx:
- yield _ModuleHandle(ctx)
|