veille-supervisor 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. supervisor/__init__.py +3 -0
  2. supervisor/adapters/__init__.py +11 -0
  3. supervisor/adapters/generic.py +52 -0
  4. supervisor/adapters/langgraph/__init__.py +20 -0
  5. supervisor/adapters/langgraph/adapter.py +132 -0
  6. supervisor/adapters/langgraph/port.py +53 -0
  7. supervisor/adapters/langgraph/stub.py +26 -0
  8. supervisor/adapters/litellm/__init__.py +0 -0
  9. supervisor/adapters/litellm/mock.py +50 -0
  10. supervisor/adapters/openai_agents/__init__.py +3 -0
  11. supervisor/adapters/openai_agents/adapter.py +37 -0
  12. supervisor/adapters/openai_responses/__init__.py +3 -0
  13. supervisor/adapters/openai_responses/adapter.py +36 -0
  14. supervisor/adapters/ports.py +59 -0
  15. supervisor/adapters/providers/__init__.py +285 -0
  16. supervisor/analytics/__init__.py +19 -0
  17. supervisor/analytics/run_summary.py +288 -0
  18. supervisor/cli.py +323 -0
  19. supervisor/console/__init__.py +10 -0
  20. supervisor/console/config.py +70 -0
  21. supervisor/console/connections.py +80 -0
  22. supervisor/console/doctor.py +59 -0
  23. supervisor/console/explorer.py +213 -0
  24. supervisor/console/run_registry.py +180 -0
  25. supervisor/console/server.py +160 -0
  26. supervisor/context/__init__.py +20 -0
  27. supervisor/context/diversification.py +107 -0
  28. supervisor/context/engine.py +64 -0
  29. supervisor/contracts/__init__.py +27 -0
  30. supervisor/contracts/events.py +65 -0
  31. supervisor/contracts/plan.py +50 -0
  32. supervisor/contracts/policy.py +34 -0
  33. supervisor/contracts/task.py +36 -0
  34. supervisor/contracts/validation.py +23 -0
  35. supervisor/io.py +37 -0
  36. supervisor/memory/__init__.py +23 -0
  37. supervisor/memory/governor.py +99 -0
  38. supervisor/memory/scoring.py +55 -0
  39. supervisor/memory/store.py +111 -0
  40. supervisor/optimize/__init__.py +30 -0
  41. supervisor/optimize/cache.py +164 -0
  42. supervisor/optimize/dedup.py +66 -0
  43. supervisor/optimize/keys.py +48 -0
  44. supervisor/optimize/policy.py +96 -0
  45. supervisor/planning/__init__.py +7 -0
  46. supervisor/planning/planner.py +129 -0
  47. supervisor/policy/__init__.py +41 -0
  48. supervisor/policy/budgets.py +75 -0
  49. supervisor/policy/enforcement.py +108 -0
  50. supervisor/policy/engine.py +356 -0
  51. supervisor/routing/__init__.py +17 -0
  52. supervisor/routing/router.py +98 -0
  53. supervisor/sdk/__init__.py +8 -0
  54. supervisor/sdk/collector.py +73 -0
  55. supervisor/sdk/supervisor.py +785 -0
  56. supervisor/telemetry/__init__.py +17 -0
  57. supervisor/telemetry/exporter.py +150 -0
  58. veille_supervisor-0.3.0.dist-info/METADATA +167 -0
  59. veille_supervisor-0.3.0.dist-info/RECORD +61 -0
  60. veille_supervisor-0.3.0.dist-info/WHEEL +4 -0
  61. veille_supervisor-0.3.0.dist-info/entry_points.txt +2 -0
supervisor/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """AI Runtime Supervisor — control plane for production agent work."""
2
+
3
+ __version__ = "0.2.0"
@@ -0,0 +1,11 @@
1
+ """Framework and provider adapter ports."""
2
+
3
+ from supervisor.adapters.langgraph.port import LangGraphAdapter, LangGraphEventHook
4
+ from supervisor.adapters.litellm.mock import LiteLLMMockAdapter, MockCompletionResult
5
+
6
+ __all__ = [
7
+ "LangGraphAdapter",
8
+ "LangGraphEventHook",
9
+ "LiteLLMMockAdapter",
10
+ "MockCompletionResult",
11
+ ]
@@ -0,0 +1,52 @@
1
+ """Generic framework adapter base.
2
+
3
+ Wraps any Python agent/callable so it executes *through* Veille's runtime and
4
+ emits normalized events. OpenAI Agents SDK and OpenAI Responses API adapters
5
+ build on this; SDK-specific tracing hooks (handoffs, traces) are future
6
+ integration points layered on top of ``InstrumentedAgent``.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import uuid
12
+ from typing import Any, Protocol, runtime_checkable
13
+
14
+ from supervisor.sdk import Supervisor
15
+
16
+
17
+ @runtime_checkable
18
+ class FrameworkAgent(Protocol):
19
+ def run(self, input: Any, **kwargs: Any) -> Any: ...
20
+ def invoke(self, input: Any, **kwargs: Any) -> Any: ...
21
+
22
+
23
+ class InstrumentedAgent:
24
+ """Runs an agent callable under Veille's run lifecycle."""
25
+
26
+ def __init__(self, agent_fn: Any, supervisor: Supervisor) -> None:
27
+ self._agent_fn = agent_fn
28
+ self._supervisor = supervisor
29
+
30
+ def run(self, input: Any, **kwargs: Any) -> Any:
31
+ self._supervisor.start_run()
32
+ try:
33
+ return self._agent_fn(input, self._supervisor)
34
+ finally:
35
+ self._supervisor.finish_run("pass")
36
+
37
+ def invoke(self, input: Any, **kwargs: Any) -> Any:
38
+ return self.run(input, **kwargs)
39
+
40
+
41
+ class GenericFrameworkAdapter:
42
+ """Implements the framework-neutral :class:`FrameworkAdapter` port."""
43
+
44
+ def attach(self, target: Any, supervisor: Supervisor, **kwargs: Any) -> InstrumentedAgent:
45
+ return InstrumentedAgent(target, supervisor)
46
+
47
+ def extract_run_id(self, config: dict[str, Any] | None) -> str:
48
+ if config and config.get("run_id"):
49
+ return str(config["run_id"])
50
+ if config and config.get("configurable", {}).get("run_id"):
51
+ return str(config["configurable"]["run_id"])
52
+ return str(uuid.uuid4())
@@ -0,0 +1,20 @@
1
+ """LangGraph adapter package."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from supervisor.adapters.langgraph.adapter import (
6
+ InstrumentedGraph,
7
+ LangGraphCallbackHandler,
8
+ LangGraphInstrumentedAdapter,
9
+ )
10
+ from supervisor.adapters.langgraph.port import LangGraphAdapter, LangGraphEventHook
11
+ from supervisor.adapters.langgraph.stub import LangGraphAdapterStub
12
+
13
+ __all__ = [
14
+ "InstrumentedGraph",
15
+ "LangGraphCallbackHandler",
16
+ "LangGraphInstrumentedAdapter",
17
+ "LangGraphAdapter",
18
+ "LangGraphEventHook",
19
+ "LangGraphAdapterStub",
20
+ ]
@@ -0,0 +1,132 @@
1
+ """LangGraph adapter implementation (Phase 1).
2
+
3
+ Wraps a compiled LangGraph graph so that a supervised run is captured
4
+ automatically: run lifecycle via the wrapper, and model/tool/retry events via
5
+ a LangChain callback handler for graphs that use LangChain-native LLM/Tool
6
+ primitives. The cited-market-research demo uses the ``Supervisor`` helpers for
7
+ its manual mock calls, so the callback handler is dormant there but available
8
+ for any LangChain-based agent.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import Any
14
+ from uuid import uuid4
15
+
16
+ from langchain_core.callbacks import BaseCallbackHandler
17
+
18
+ from supervisor.contracts.events import EventType
19
+ from supervisor.sdk.supervisor import Supervisor
20
+
21
+
22
+ class LangGraphCallbackHandler(BaseCallbackHandler):
23
+ """Maps LangChain callback events to normalized supervisor ``RunEvent`` facts."""
24
+
25
+ def __init__(self, supervisor: Supervisor) -> None:
26
+ super().__init__()
27
+ self._supervisor = supervisor
28
+
29
+ def on_llm_start(
30
+ self, serialized: Any, prompts: Any, *, run_id: Any = None, **kwargs: Any
31
+ ) -> None:
32
+ invocation = kwargs.get("invocation_params") or {}
33
+ model = invocation.get("model") or invocation.get("model_name") or "unknown"
34
+ preview = ""
35
+ if isinstance(prompts, (list, tuple)) and prompts:
36
+ preview = str(prompts[0])[:120]
37
+ self._supervisor.collector.emit(
38
+ EventType.MODEL_REQUESTED,
39
+ model_name=str(model),
40
+ attributes={"prompt_preview": preview},
41
+ )
42
+
43
+ def on_llm_end(self, response: Any, *, run_id: Any = None, **kwargs: Any) -> None:
44
+ tokens_in = tokens_out = None
45
+ try:
46
+ usage = getattr(response, "llm_output", {}) or {}
47
+ token_usage = usage.get("token_usage", {}) or {}
48
+ tokens_in = token_usage.get("prompt_tokens")
49
+ tokens_out = token_usage.get("completion_tokens")
50
+ except Exception:
51
+ pass
52
+ self._supervisor.collector.emit(
53
+ EventType.MODEL_COMPLETED,
54
+ input_tokens=tokens_in,
55
+ output_tokens=tokens_out,
56
+ status="ok",
57
+ )
58
+
59
+ def on_tool_start(
60
+ self, serialized: Any, input_str: Any, *, run_id: Any = None, **kwargs: Any
61
+ ) -> None:
62
+ name = (
63
+ serialized.get("name")
64
+ if isinstance(serialized, dict)
65
+ else getattr(serialized, "name", None)
66
+ )
67
+ self._supervisor.collector.emit(
68
+ EventType.TOOL_REQUESTED,
69
+ tool_name=name,
70
+ attributes={"input": {"input": str(input_str)[:500]}},
71
+ )
72
+
73
+ def on_tool_end(self, output: Any, *, run_id: Any = None, **kwargs: Any) -> None:
74
+ self._supervisor.collector.emit(EventType.TOOL_COMPLETED, status="ok")
75
+
76
+ def on_tool_error(self, error: Any, *, run_id: Any = None, **kwargs: Any) -> None:
77
+ self._supervisor.collector.emit(
78
+ EventType.TOOL_COMPLETED,
79
+ status="error",
80
+ error_message=str(error)[:500],
81
+ )
82
+
83
+ def on_retry(self, retry_state: Any, *, run_id: Any = None, **kwargs: Any) -> None:
84
+ self._supervisor.collector.emit(EventType.RETRY_SCHEDULED)
85
+
86
+
87
+ class InstrumentedGraph:
88
+ """A compiled graph wrapped so supervisor run lifecycle is captured."""
89
+
90
+ def __init__(
91
+ self, graph: Any, supervisor: Supervisor, *, auto_run_lifecycle: bool = True
92
+ ) -> None:
93
+ self._graph = graph
94
+ self._supervisor = supervisor
95
+ self._handler = LangGraphCallbackHandler(supervisor)
96
+ self._auto = auto_run_lifecycle
97
+
98
+ def _merge_config(self, config: Any) -> dict[str, Any]:
99
+ base: dict[str, Any] = dict(config) if isinstance(config, dict) else {}
100
+ callbacks = list(base.get("callbacks") or [])
101
+ callbacks.append(self._handler)
102
+ base["callbacks"] = callbacks
103
+ return base
104
+
105
+ def invoke(self, input: Any, config: Any = None, **kwargs: Any) -> Any:
106
+ if self._auto:
107
+ self._supervisor.start_run()
108
+ try:
109
+ out = self._graph.invoke(input, config=self._merge_config(config), **kwargs)
110
+ except Exception:
111
+ if self._auto:
112
+ self._supervisor.finish_run("error")
113
+ raise
114
+ if self._auto:
115
+ self._supervisor.finish_run("ok")
116
+ return out
117
+
118
+
119
+ class LangGraphInstrumentedAdapter:
120
+ """Phase 1 LangGraph adapter: implements the port's ``attach`` contract."""
121
+
122
+ def attach(
123
+ self, graph: Any, supervisor: Supervisor, *, auto_run_lifecycle: bool = True
124
+ ) -> InstrumentedGraph:
125
+ return InstrumentedGraph(graph, supervisor, auto_run_lifecycle=auto_run_lifecycle)
126
+
127
+ def extract_run_id(self, config: dict[str, Any] | None) -> str:
128
+ if config and "configurable" in config:
129
+ run_id = config["configurable"].get("run_id")
130
+ if isinstance(run_id, str):
131
+ return run_id
132
+ return str(uuid4())
@@ -0,0 +1,53 @@
1
+ """LangGraph adapter port — Phase 1 will implement full instrumentation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Protocol, runtime_checkable
6
+
7
+ from supervisor.contracts.events import RunEvent
8
+
9
+
10
+ @runtime_checkable
11
+ class LangGraphEventHook(Protocol):
12
+ """Callback surface for LangGraph lifecycle events."""
13
+
14
+ def on_run_started(self, run_id: str, task_id: str, metadata: dict[str, Any]) -> None: ...
15
+
16
+ def on_run_finished(self, run_id: str, status: str, metadata: dict[str, Any]) -> None: ...
17
+
18
+ def on_agent_event(
19
+ self, run_id: str, agent_id: str, event_type: str, metadata: dict[str, Any]
20
+ ) -> None: ...
21
+
22
+ def on_tool_event(
23
+ self,
24
+ run_id: str,
25
+ tool_name: str,
26
+ event_type: str,
27
+ input_data: dict[str, Any],
28
+ metadata: dict[str, Any],
29
+ ) -> None: ...
30
+
31
+ def on_model_event(
32
+ self,
33
+ run_id: str,
34
+ model_name: str,
35
+ event_type: str,
36
+ token_usage: dict[str, int],
37
+ metadata: dict[str, Any],
38
+ ) -> None: ...
39
+
40
+ def flush(self) -> list[RunEvent]: ...
41
+
42
+
43
+ @runtime_checkable
44
+ class LangGraphAdapter(Protocol):
45
+ """Port for wrapping LangGraph agents with supervisor instrumentation."""
46
+
47
+ def attach(self, graph: Any, hook: LangGraphEventHook) -> Any:
48
+ """Return an instrumented graph. Phase 1 implementation."""
49
+ ...
50
+
51
+ def extract_run_id(self, config: dict[str, Any] | None) -> str:
52
+ """Extract or generate a stable run identifier."""
53
+ ...
@@ -0,0 +1,26 @@
1
+ """LangGraph adapter stub — returns graph unchanged until Phase 1."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+ from uuid import uuid4
7
+
8
+ from supervisor.adapters.langgraph.port import LangGraphEventHook
9
+
10
+
11
+ class LangGraphAdapterStub:
12
+ """Phase 0 stub: documents the adapter contract without modifying execution."""
13
+
14
+ def attach(self, graph: Any, hook: LangGraphEventHook) -> Any:
15
+ _ = hook
16
+ return graph
17
+
18
+ def extract_run_id(self, config: dict[str, Any] | None) -> str:
19
+ if config and "configurable" in config:
20
+ run_id = config["configurable"].get("run_id")
21
+ if isinstance(run_id, str):
22
+ return run_id
23
+ return str(uuid4())
24
+
25
+
26
+ # LangGraphAdapterStub implements the LangGraphAdapter protocol.
File without changes
@@ -0,0 +1,50 @@
1
+ """LiteLLM mock adapter — deterministic costs without API calls."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+
7
+
8
+ @dataclass(frozen=True)
9
+ class MockCompletionResult:
10
+ model: str
11
+ content: str
12
+ input_tokens: int
13
+ output_tokens: int
14
+ cost_usd: float
15
+ latency_ms: float
16
+
17
+
18
+ class LiteLLMMockAdapter:
19
+ """Returns deterministic token counts and costs for demo and tests."""
20
+
21
+ DEFAULT_PRICING: dict[str, tuple[float, float]] = {
22
+ "mock-research": (0.000001, 0.000002),
23
+ "mock-synthesis": (0.000002, 0.000004),
24
+ "mock-review": (0.000003, 0.000005),
25
+ }
26
+
27
+ def __init__(self, use_mock: bool = True) -> None:
28
+ self.use_mock = use_mock
29
+
30
+ def complete(
31
+ self, model: str, prompt: str, max_output_tokens: int = 512
32
+ ) -> MockCompletionResult:
33
+ if not self.use_mock:
34
+ raise RuntimeError(
35
+ "Real LiteLLM calls are opt-in. Set USE_MOCK_MODELS=true or install litellm extra."
36
+ )
37
+
38
+ input_tokens = max(50, len(prompt) // 4)
39
+ output_tokens = min(max_output_tokens, max(80, len(prompt) // 8))
40
+ input_rate, output_rate = self.DEFAULT_PRICING.get(model, (0.000001, 0.000002))
41
+ cost = (input_tokens * input_rate) + (output_tokens * output_rate)
42
+
43
+ return MockCompletionResult(
44
+ model=model,
45
+ content=f"[mock response from {model}]",
46
+ input_tokens=input_tokens,
47
+ output_tokens=output_tokens,
48
+ cost_usd=round(cost, 6),
49
+ latency_ms=120.0,
50
+ )
@@ -0,0 +1,3 @@
1
+ from supervisor.adapters.openai_agents.adapter import OpenAIAgentsAdapter
2
+
3
+ __all__ = ["OpenAIAgentsAdapter"]
@@ -0,0 +1,37 @@
1
+ """OpenAI Agents SDK adapter.
2
+
3
+ Executes OpenAI Agents SDK agents through Veille's runtime. The base
4
+ ``GenericFrameworkAdapter`` runs the agent and emits normalized events; SDK-specific
5
+ tracing (handoffs, guardrails, session traces) is layered on here when the
6
+ ``openai-agents`` package is installed. Until then, any Python agent callable is
7
+ run through Veille unchanged.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import Any
13
+
14
+ from supervisor.adapters.generic import GenericFrameworkAdapter, InstrumentedAgent
15
+ from supervisor.sdk import Supervisor
16
+
17
+
18
+ class OpenAIAgentsAdapter(GenericFrameworkAdapter):
19
+ """Adapter for OpenAI Agents SDK agents."""
20
+
21
+ name = "openai_agents"
22
+
23
+ def attach(self, target: Any, supervisor: Supervisor, **kwargs: Any) -> InstrumentedAgent:
24
+ # If the real SDK is present, prefer its Runner so tracing integrates;
25
+ # otherwise fall back to the generic instrumented callable.
26
+ try: # pragma: no cover - optional dependency
27
+ from agents import Runner # type: ignore
28
+
29
+ if hasattr(target, "run") or hasattr(target, "invoke"):
30
+
31
+ def _run(input: Any, sup: Supervisor) -> Any:
32
+ return Runner.run(target, input)
33
+
34
+ return InstrumentedAgent(_run, supervisor)
35
+ except Exception: # noqa: BLE001
36
+ pass
37
+ return super().attach(target, supervisor)
@@ -0,0 +1,3 @@
1
+ from supervisor.adapters.openai_responses.adapter import OpenAIResponsesAdapter
2
+
3
+ __all__ = ["OpenAIResponsesAdapter"]
@@ -0,0 +1,36 @@
1
+ """OpenAI Responses API adapter.
2
+
3
+ Runs agents built on the OpenAI Responses API through Veille's runtime. Shares
4
+ the generic instrumented execution; Responses-API tracing (response objects,
5
+ tool calls) is integrated here when the SDK is available.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any
11
+
12
+ from supervisor.adapters.generic import GenericFrameworkAdapter, InstrumentedAgent
13
+ from supervisor.sdk import Supervisor
14
+
15
+
16
+ class OpenAIResponsesAdapter(GenericFrameworkAdapter):
17
+ """Adapter for OpenAI Responses API agents."""
18
+
19
+ name = "openai_responses"
20
+
21
+ def attach(self, target: Any, supervisor: Supervisor, **kwargs: Any) -> InstrumentedAgent:
22
+ # When the real SDK is present, route through the Responses client so the
23
+ # native trace is captured; otherwise run the generic instrumented callable.
24
+ try: # pragma: no cover - optional dependency
25
+ from openai import OpenAI # type: ignore
26
+
27
+ if hasattr(target, "run") or hasattr(target, "invoke"):
28
+
29
+ def _run(input: Any, sup: Supervisor) -> Any:
30
+ client = OpenAI()
31
+ return client.responses.create(model="gpt-4o", input=input)
32
+
33
+ return InstrumentedAgent(_run, supervisor)
34
+ except Exception: # noqa: BLE001
35
+ pass
36
+ return super().attach(target, supervisor)
@@ -0,0 +1,59 @@
1
+ """Framework-neutral adapter ports.
2
+
3
+ Generalizes the LangGraph-specific port pair into provider-agnostic contracts so
4
+ every future agent framework (CrewAI, AutoGen, Semantic Kernel, Mastra, PydanticAI,
5
+ custom Python agents) implements the same interface. Adapters turn framework-native
6
+ lifecycle into normalized ``RunEvent``s through the supervisor's collector.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from typing import Any, Protocol, runtime_checkable
12
+
13
+ from supervisor.contracts.events import RunEvent
14
+
15
+
16
+ @runtime_checkable
17
+ class RunEventHook(Protocol):
18
+ """Sink for normalized runtime events emitted by any framework adapter."""
19
+
20
+ def on_run_started(self, run_id: str, task_id: str, metadata: dict[str, Any]) -> None: ...
21
+
22
+ def on_run_finished(self, run_id: str, status: str, metadata: dict[str, Any]) -> None: ...
23
+
24
+ def on_agent_event(
25
+ self, run_id: str, agent_id: str, event_type: str, metadata: dict[str, Any]
26
+ ) -> None: ...
27
+
28
+ def on_tool_event(
29
+ self,
30
+ run_id: str,
31
+ tool_name: str,
32
+ event_type: str,
33
+ input_data: dict[str, Any],
34
+ metadata: dict[str, Any],
35
+ ) -> None: ...
36
+
37
+ def on_model_event(
38
+ self,
39
+ run_id: str,
40
+ model_name: str,
41
+ event_type: str,
42
+ token_usage: dict[str, int],
43
+ metadata: dict[str, Any],
44
+ ) -> None: ...
45
+
46
+ def flush(self) -> list[RunEvent]: ...
47
+
48
+
49
+ @runtime_checkable
50
+ class FrameworkAdapter(Protocol):
51
+ """Port for wrapping any agent framework with supervisor instrumentation."""
52
+
53
+ def attach(self, graph: Any, hook: RunEventHook) -> Any:
54
+ """Return an instrumented graph/agent bound to the hook."""
55
+ ...
56
+
57
+ def extract_run_id(self, config: dict[str, Any] | None) -> str:
58
+ """Extract or generate a stable run identifier."""
59
+ ...