activegraph 1.0.1__tar.gz → 1.0.2__tar.gz
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.
- {activegraph-1.0.1 → activegraph-1.0.2}/PKG-INFO +1 -1
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/__init__.py +1 -1
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/behaviors/base.py +13 -2
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/behaviors/decorators.py +14 -1
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/llm/anthropic.py +8 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/llm/openai.py +20 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/llm/provider.py +34 -3
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/llm/recorded.py +26 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/runtime/runtime.py +120 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph.egg-info/PKG-INFO +1 -1
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph.egg-info/SOURCES.txt +1 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/pyproject.toml +1 -1
- activegraph-1.0.2/tests/test_llm_default_model.py +328 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/README.md +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/__main__.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/behaviors/__init__.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/cli/__init__.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/cli/main.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/cli/quickstart.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/core/__init__.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/core/clock.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/core/event.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/core/graph.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/core/ids.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/core/patch.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/core/view.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/errors.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/frame.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/llm/__init__.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/llm/cache.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/llm/errors.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/llm/parsing.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/llm/prompt.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/llm/types.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/observability/__init__.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/observability/logging.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/observability/metrics.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/observability/migration.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/observability/prometheus.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/observability/status.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/packs/__init__.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/packs/diligence/__init__.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/packs/diligence/behaviors.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/packs/diligence/fixtures/__init__.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/packs/diligence/fixtures/companies.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/packs/diligence/object_types.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/packs/diligence/prompts/document_researcher.md +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/packs/diligence/prompts/memo_synthesizer.md +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/packs/diligence/prompts/question_generator.md +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/packs/diligence/prompts/risk_identifier.md +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/packs/diligence/settings.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/packs/diligence/tools.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/packs/loader.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/packs/scaffold.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/policy.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/runtime/__init__.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/runtime/behavior_graph.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/runtime/budget.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/runtime/config_errors.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/runtime/diff.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/runtime/errors.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/runtime/exec_errors.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/runtime/patterns.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/runtime/queue.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/runtime/registration_errors.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/runtime/registry.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/runtime/scheduler.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/runtime/view_builder.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/store/__init__.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/store/base.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/store/conformance.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/store/errors.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/store/memory.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/store/postgres.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/store/serde.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/store/sqlite.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/store/url.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/tools/__init__.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/tools/base.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/tools/cache.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/tools/context.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/tools/decorators.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/tools/errors.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/tools/graph_query.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/tools/recorded.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/tools/web_fetch.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/trace/__init__.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/trace/causal.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph/trace/printer.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph.egg-info/dependency_links.txt +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph.egg-info/entry_points.txt +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph.egg-info/requires.txt +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/activegraph.egg-info/top_level.txt +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/setup.cfg +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_activate_after.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_causal_cross_tool.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_cli.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_clock.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_diff.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_diligence_pack.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_diligence_with_tools.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_doc_links.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_doc_site_reachable.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_errors_format.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_event.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_fork.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_graph.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_ids.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_llm_anthropic.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_llm_behavior.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_llm_budget.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_llm_causal.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_llm_claim_extraction.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_llm_determinism.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_llm_failure.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_llm_openai.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_llm_prompt.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_llm_provider_fixtures.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_llm_replay.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_llm_tool_loop.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_llm_trace.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_llm_trace_snapshot.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_llm_types.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_migration.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_observability_logging.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_observability_metrics.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_operate_example.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_pack_scaffold.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_packs.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_patch.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_pattern_matcher.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_pattern_parser.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_pattern_subscriptions.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_persistence.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_postgres_store.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_quickstart.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_quickstart_snapshot.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_replay.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_replay_trace_snapshot.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_requeue_unfired.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_resume_example.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_runtime.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_runtime_status.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_serde.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_store_conformance.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_store_url.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_tool_replay.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_tool_trace_snapshot.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_tools.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_trace.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_tutorial_snippets.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_v1_0_1_patches.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_version_sync.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_view.py +0 -0
- {activegraph-1.0.1 → activegraph-1.0.2}/tests/test_wheel_completeness.py +0 -0
|
@@ -93,7 +93,11 @@ class LLMBehavior(Behavior):
|
|
|
93
93
|
|
|
94
94
|
handler: Optional[Callable[..., None]] = None
|
|
95
95
|
description: str = ""
|
|
96
|
-
|
|
96
|
+
# v1.0.2 #1: model is optional. None = "use the configured provider's
|
|
97
|
+
# default_model"; the runtime resolves and stamps the concrete string
|
|
98
|
+
# at registration time. Existing call sites that pass an explicit
|
|
99
|
+
# string keep working unchanged.
|
|
100
|
+
model: Optional[str] = None
|
|
97
101
|
output_schema: Optional[type] = None
|
|
98
102
|
deterministic: bool = False
|
|
99
103
|
max_tokens: int = 4096
|
|
@@ -134,10 +138,17 @@ class LLMBehavior(Behavior):
|
|
|
134
138
|
if ar_expr:
|
|
135
139
|
around_id = _resolve_event_path(ar_expr, event)
|
|
136
140
|
depth = self.view_spec.get("depth")
|
|
141
|
+
# v1.0.2 #1: if model is still None, the runtime hasn't stamped
|
|
142
|
+
# a provider default yet — `build_prompt` is for inspection
|
|
143
|
+
# (CONTRACT v0.6 #20) and must work without a Runtime, so fall
|
|
144
|
+
# back to the v1.0.1 hardcoded default for inspection-time hash
|
|
145
|
+
# stability. Real provider calls always go through Runtime,
|
|
146
|
+
# which resolves the configured provider's default_model first.
|
|
147
|
+
inspection_model = self.model or "claude-sonnet-4-5"
|
|
137
148
|
return assemble_prompt(
|
|
138
149
|
behavior_name=self.name,
|
|
139
150
|
description=self.description,
|
|
140
|
-
model=
|
|
151
|
+
model=inspection_model,
|
|
141
152
|
output_schema=self.output_schema,
|
|
142
153
|
creates=self.creates,
|
|
143
154
|
view=view,
|
|
@@ -147,7 +147,7 @@ def llm_behavior(
|
|
|
147
147
|
on: Optional[list[str]] = None,
|
|
148
148
|
where: Optional[dict[str, Any]] = None,
|
|
149
149
|
description: str = "",
|
|
150
|
-
model: str =
|
|
150
|
+
model: Optional[str] = None,
|
|
151
151
|
output_schema: Optional[type] = None,
|
|
152
152
|
view: Optional[dict[str, Any]] = None,
|
|
153
153
|
creates: Optional[list[str]] = None,
|
|
@@ -177,6 +177,19 @@ def llm_behavior(
|
|
|
177
177
|
Keyword-only on purpose — `@llm_behavior` carries enough
|
|
178
178
|
parameters that positional binding would be a footgun.
|
|
179
179
|
|
|
180
|
+
``model=`` is optional (v1.0.2 #1). Omitted, the runtime resolves
|
|
181
|
+
it to the configured provider's ``default_model`` at registration
|
|
182
|
+
time — ``"claude-sonnet-4-5"`` for ``AnthropicProvider``,
|
|
183
|
+
``"gpt-4o-mini"`` for ``OpenAIProvider``. Passing an explicit
|
|
184
|
+
model string still works byte-identically; the runtime additionally
|
|
185
|
+
validates the name against the configured provider's
|
|
186
|
+
``recognizes_model()`` and raises
|
|
187
|
+
:class:`InvalidRuntimeConfiguration` at registration time if the
|
|
188
|
+
name belongs to a different shipped provider's family (e.g.
|
|
189
|
+
``model="gpt-4o-mini"`` on a runtime configured with
|
|
190
|
+
``AnthropicProvider``). Names no shipped provider recognizes
|
|
191
|
+
(custom or fine-tuned models) pass through silently.
|
|
192
|
+
|
|
180
193
|
`prompt_template=` is the only escape hatch from the
|
|
181
194
|
runtime-assembled prompt. It is a `str.format`-style template that
|
|
182
195
|
receives four placeholders:
|
|
@@ -62,6 +62,10 @@ def _pricing_for(model: str, pricing: Mapping[str, Mapping[str, str]]) -> tuple[
|
|
|
62
62
|
|
|
63
63
|
|
|
64
64
|
class AnthropicProvider(LLMProvider):
|
|
65
|
+
# v1.0.2 #1: provider-aware default model. @llm_behavior(model=None)
|
|
66
|
+
# resolves to this string at registration time.
|
|
67
|
+
default_model: str = "claude-sonnet-4-5"
|
|
68
|
+
|
|
65
69
|
def __init__(
|
|
66
70
|
self,
|
|
67
71
|
*,
|
|
@@ -205,6 +209,10 @@ class AnthropicProvider(LLMProvider):
|
|
|
205
209
|
result = client.messages.count_tokens(**kwargs)
|
|
206
210
|
return int(getattr(result, "input_tokens", 0) or 0)
|
|
207
211
|
|
|
212
|
+
def recognizes_model(self, name: str) -> bool:
|
|
213
|
+
"""True for the ``claude-*`` model family. v1.0.2 #1."""
|
|
214
|
+
return name.startswith("claude-")
|
|
215
|
+
|
|
208
216
|
|
|
209
217
|
# ---- helpers ---------------------------------------------------------------
|
|
210
218
|
|
|
@@ -82,6 +82,20 @@ def _pricing_for(
|
|
|
82
82
|
|
|
83
83
|
|
|
84
84
|
class OpenAIProvider(LLMProvider):
|
|
85
|
+
# v1.0.2 #1: provider-aware default model. @llm_behavior(model=None)
|
|
86
|
+
# resolves to this string at registration time. gpt-4o-mini is the
|
|
87
|
+
# cheap, fast member of the GPT-4o family — matches Anthropic's
|
|
88
|
+
# default-model shape (cheapest sensible default; override for prod).
|
|
89
|
+
default_model: str = "gpt-4o-mini"
|
|
90
|
+
|
|
91
|
+
# v1.0.2 #1: the model-name prefixes this provider recognizes.
|
|
92
|
+
# `gpt-` covers the GPT-3.5 / GPT-4 / GPT-4o families; the
|
|
93
|
+
# `o1-` / `o3-` / `o4-` prefixes cover the reasoning-model line
|
|
94
|
+
# (o1, o3, o4 and their dated variants). Names matching none of
|
|
95
|
+
# these pass through registration silently per v1.0.2 #1 (b)'s
|
|
96
|
+
# permissive-default rule.
|
|
97
|
+
_RECOGNIZED_PREFIXES: tuple[str, ...] = ("gpt-", "o1-", "o3-", "o4-")
|
|
98
|
+
|
|
85
99
|
def __init__(
|
|
86
100
|
self,
|
|
87
101
|
*,
|
|
@@ -267,6 +281,12 @@ class OpenAIProvider(LLMProvider):
|
|
|
267
281
|
total += 4
|
|
268
282
|
return max(1, total)
|
|
269
283
|
|
|
284
|
+
def recognizes_model(self, name: str) -> bool:
|
|
285
|
+
"""True for OpenAI model families (``gpt-*``, ``o1-*``, ``o3-*``,
|
|
286
|
+
``o4-*``). v1.0.2 #1.
|
|
287
|
+
"""
|
|
288
|
+
return any(name.startswith(p) for p in self._RECOGNIZED_PREFIXES)
|
|
289
|
+
|
|
270
290
|
def _heuristic_count(self, system: str, messages: list[LLMMessage]) -> int:
|
|
271
291
|
if not self._heuristic_warned:
|
|
272
292
|
_log.debug(
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
"""The `LLMProvider` Protocol every provider implements.
|
|
2
2
|
|
|
3
|
-
CONTRACT v0.6 #3, extended in v0.7
|
|
4
|
-
|
|
3
|
+
CONTRACT v0.6 #3, extended in v0.7, additively widened in v1.0.2 #1.
|
|
4
|
+
Narrow, explicit, keyword-only. Shipped reference implementations are
|
|
5
|
+
`AnthropicProvider` and `OpenAIProvider`. Tests use
|
|
5
6
|
`RecordedLLMProvider` + `RecordingLLMProvider`. The demo ships its
|
|
6
7
|
own scripted provider.
|
|
7
8
|
|
|
8
|
-
A provider does three things:
|
|
9
|
+
A provider does three things plus two declarations:
|
|
10
|
+
|
|
9
11
|
* `complete()`: run a single non-streaming completion. v0.7 adds
|
|
10
12
|
an optional `tools=` parameter; when non-empty, the model is
|
|
11
13
|
allowed to return tool_use blocks in the response.
|
|
@@ -14,6 +16,18 @@ A provider does three things:
|
|
|
14
16
|
prompt that's about to be sent. Used for pre-call budget gating
|
|
15
17
|
when `budget.max_cost_usd` is set; otherwise skipped (see
|
|
16
18
|
CONTRACT v0.6 #4 / decision 10).
|
|
19
|
+
* `default_model`: the model name to use when an `@llm_behavior`
|
|
20
|
+
didn't pin one (v1.0.2 #1).
|
|
21
|
+
* `recognizes_model(name)`: True when `name` belongs to a model
|
|
22
|
+
family this provider serves. Used by the runtime at
|
|
23
|
+
registration time to flag cross-provider mismatches before the
|
|
24
|
+
first network call (v1.0.2 #1).
|
|
25
|
+
|
|
26
|
+
`default_model` and `recognizes_model` are additive: the runtime
|
|
27
|
+
guards their use with `getattr(...)`, so custom providers that
|
|
28
|
+
pre-date v1.0.2 keep working — they just require an explicit
|
|
29
|
+
`model=` on every `@llm_behavior` and don't participate in
|
|
30
|
+
cross-provider validation.
|
|
17
31
|
|
|
18
32
|
No streaming, no multi-model orchestration — those are deferred to
|
|
19
33
|
v0.8+. Tool use IS in v0.7, but the loop is orchestrated by the
|
|
@@ -32,6 +46,11 @@ from activegraph.llm.types import LLMMessage, LLMResponse
|
|
|
32
46
|
|
|
33
47
|
@runtime_checkable
|
|
34
48
|
class LLMProvider(Protocol):
|
|
49
|
+
# v1.0.2 #1: declared as an attribute on the Protocol. Concrete
|
|
50
|
+
# providers set it as a class attribute; custom providers may
|
|
51
|
+
# omit it (the runtime falls back to getattr-with-default).
|
|
52
|
+
default_model: str
|
|
53
|
+
|
|
35
54
|
def complete(
|
|
36
55
|
self,
|
|
37
56
|
*,
|
|
@@ -64,3 +83,15 @@ class LLMProvider(Protocol):
|
|
|
64
83
|
model: str,
|
|
65
84
|
) -> int:
|
|
66
85
|
...
|
|
86
|
+
|
|
87
|
+
def recognizes_model(self, name: str) -> bool:
|
|
88
|
+
"""True when `name` belongs to a model family this provider serves.
|
|
89
|
+
|
|
90
|
+
v1.0.2 #1. Used at registration time to flag cross-provider
|
|
91
|
+
mismatches (e.g. ``model="claude-sonnet-4-5"`` on a runtime
|
|
92
|
+
configured with ``OpenAIProvider``). Permissive by default:
|
|
93
|
+
unknown names — fine-tuned models, internal deployment names,
|
|
94
|
+
experimental prefixes — should return False so the runtime
|
|
95
|
+
passes them through without a diagnostic.
|
|
96
|
+
"""
|
|
97
|
+
...
|
|
@@ -110,9 +110,21 @@ class RecordedLLMProvider(LLMProvider):
|
|
|
110
110
|
silent fallthrough to a real call.
|
|
111
111
|
"""
|
|
112
112
|
|
|
113
|
+
# v1.0.2 #1: fixtures already carry their own model name in the hash
|
|
114
|
+
# input, so default-model resolution only matters when an
|
|
115
|
+
# @llm_behavior omits model=. The recorded provider has no opinion
|
|
116
|
+
# about which family it's replaying; declare the historical default
|
|
117
|
+
# so existing fixtures stay reachable.
|
|
118
|
+
default_model: str = "claude-sonnet-4-5"
|
|
119
|
+
|
|
113
120
|
def __init__(self, fixtures_dir: str) -> None:
|
|
114
121
|
self._dir = fixtures_dir
|
|
115
122
|
|
|
123
|
+
def recognizes_model(self, name: str) -> bool:
|
|
124
|
+
# Fixture-backed: claim every name so cross-provider validation
|
|
125
|
+
# doesn't fire against recorded responses.
|
|
126
|
+
return True
|
|
127
|
+
|
|
116
128
|
# ---- LLMProvider methods ----
|
|
117
129
|
|
|
118
130
|
def complete(
|
|
@@ -207,6 +219,20 @@ class RecordingLLMProvider(LLMProvider):
|
|
|
207
219
|
self._dir = fixtures_dir
|
|
208
220
|
os.makedirs(self._dir, exist_ok=True)
|
|
209
221
|
|
|
222
|
+
# v1.0.2 #1: delegate default_model + recognizes_model to the inner
|
|
223
|
+
# provider so wrapping doesn't change the resolution surface. Falls
|
|
224
|
+
# back to the historical default if the inner provider pre-dates
|
|
225
|
+
# v1.0.2 and doesn't declare one.
|
|
226
|
+
@property
|
|
227
|
+
def default_model(self) -> str:
|
|
228
|
+
return getattr(self._inner, "default_model", "claude-sonnet-4-5")
|
|
229
|
+
|
|
230
|
+
def recognizes_model(self, name: str) -> bool:
|
|
231
|
+
recognizes = getattr(self._inner, "recognizes_model", None)
|
|
232
|
+
if recognizes is None:
|
|
233
|
+
return True
|
|
234
|
+
return bool(recognizes(name))
|
|
235
|
+
|
|
210
236
|
def complete(
|
|
211
237
|
self,
|
|
212
238
|
*,
|
|
@@ -376,6 +376,11 @@ class Runtime:
|
|
|
376
376
|
for b in source:
|
|
377
377
|
if isinstance(b, LLMBehavior):
|
|
378
378
|
raise MissingProviderError(behavior_name=b.name)
|
|
379
|
+
else:
|
|
380
|
+
# CONTRACT v1.0.2 #1: resolve the provider's default model for
|
|
381
|
+
# behaviors that didn't pin one, and validate explicit model
|
|
382
|
+
# names against cross-provider mismatches.
|
|
383
|
+
_resolve_and_validate_llm_models(source, self.llm_provider)
|
|
379
384
|
self.registry = Registry(source)
|
|
380
385
|
|
|
381
386
|
# v0.7: assemble the tool registry. Explicit tools= override the
|
|
@@ -2180,6 +2185,121 @@ _BUDGET_REASON_MAP = {
|
|
|
2180
2185
|
}
|
|
2181
2186
|
|
|
2182
2187
|
|
|
2188
|
+
def _resolve_and_validate_llm_models(source, provider) -> None:
|
|
2189
|
+
"""Stamp provider defaults onto LLMBehaviors with model=None, and
|
|
2190
|
+
raise InvalidRuntimeConfiguration for explicit names that belong to
|
|
2191
|
+
a different shipped provider. CONTRACT v1.0.2 #1.
|
|
2192
|
+
|
|
2193
|
+
The validation is permissive: a name no shipped provider recognizes
|
|
2194
|
+
(custom or fine-tuned model) passes silently — only *recognized*
|
|
2195
|
+
cross-provider mismatches fire the diagnostic.
|
|
2196
|
+
"""
|
|
2197
|
+
from activegraph.runtime.config_errors import InvalidRuntimeConfiguration
|
|
2198
|
+
|
|
2199
|
+
# Custom providers that pre-date v1.0.2 don't declare `default_model`.
|
|
2200
|
+
# Fall back to the v1.0.1 hardcoded default — keeps every pre-v1.0.2
|
|
2201
|
+
# call site (which silently inherited "claude-sonnet-4-5") working
|
|
2202
|
+
# byte-identically without code changes. The runtime only swaps in a
|
|
2203
|
+
# different default when the provider opts in by declaring one.
|
|
2204
|
+
provider_default = getattr(provider, "default_model", None) or "claude-sonnet-4-5"
|
|
2205
|
+
provider_recognizes = getattr(provider, "recognizes_model", None)
|
|
2206
|
+
provider_class = type(provider).__name__
|
|
2207
|
+
|
|
2208
|
+
for b in source:
|
|
2209
|
+
if not isinstance(b, LLMBehavior):
|
|
2210
|
+
continue
|
|
2211
|
+
if b.model is None:
|
|
2212
|
+
# v1.0.2 #1 (a): resolve to the provider's default_model
|
|
2213
|
+
# (or the v1.0.1 fallback for custom providers that don't
|
|
2214
|
+
# declare one).
|
|
2215
|
+
b.model = provider_default
|
|
2216
|
+
continue
|
|
2217
|
+
|
|
2218
|
+
# b.model is an explicit string. Cross-provider validation per
|
|
2219
|
+
# v1.0.2 #1 (b): if the configured provider doesn't recognize the
|
|
2220
|
+
# name, ask each *other* shipped provider class whether it does.
|
|
2221
|
+
# Exactly one other-provider claim ⇒ raise; otherwise pass through.
|
|
2222
|
+
if provider_recognizes is None or provider_recognizes(b.model):
|
|
2223
|
+
continue
|
|
2224
|
+
claimed_by = _which_shipped_provider_claims(b.model, exclude=type(provider))
|
|
2225
|
+
if claimed_by is None:
|
|
2226
|
+
continue
|
|
2227
|
+
# The model name belongs to a different shipped provider's family.
|
|
2228
|
+
# This is the v1.0.1-user-test finding the v1.0.2 patch addresses:
|
|
2229
|
+
# silently sending an Anthropic-family name to OpenAI 404s with no
|
|
2230
|
+
# helpful diagnostic. Fire at registration time, not call time.
|
|
2231
|
+
claimed_by_default = getattr(claimed_by, "default_model", "")
|
|
2232
|
+
provider_default_hint = (
|
|
2233
|
+
f"or remove the model= argument to use {provider_class}'s "
|
|
2234
|
+
f"default ({provider_default!r})"
|
|
2235
|
+
if provider_default
|
|
2236
|
+
else f"or set a {provider_class}-compatible model name"
|
|
2237
|
+
)
|
|
2238
|
+
raise InvalidRuntimeConfiguration(
|
|
2239
|
+
(
|
|
2240
|
+
f"@llm_behavior(name={b.name!r}, model={b.model!r}) "
|
|
2241
|
+
f"names a {claimed_by.__name__}-family model, but the "
|
|
2242
|
+
f"runtime is configured with {provider_class}"
|
|
2243
|
+
),
|
|
2244
|
+
what_failed=(
|
|
2245
|
+
f"The behavior {b.name!r} pinned model={b.model!r}. That "
|
|
2246
|
+
f"name belongs to {claimed_by.__name__}'s model family, "
|
|
2247
|
+
f"but this Runtime was constructed with a "
|
|
2248
|
+
f"{provider_class} instance. Sending the name to the "
|
|
2249
|
+
f"wrong provider produces an HTTP 404 (or equivalent "
|
|
2250
|
+
f"'unknown model' response) at first LLM call, with no "
|
|
2251
|
+
f"hint that the mismatch is the cause."
|
|
2252
|
+
),
|
|
2253
|
+
why=(
|
|
2254
|
+
"v1.0.2 #1 validates explicit model names at registration "
|
|
2255
|
+
"time against each shipped provider's recognizes_model() "
|
|
2256
|
+
"method. The configured provider doesn't claim this "
|
|
2257
|
+
"name, but another shipped provider does — that's a "
|
|
2258
|
+
"configuration mismatch worth surfacing before the "
|
|
2259
|
+
"first network call rather than after."
|
|
2260
|
+
),
|
|
2261
|
+
how_to_fix=(
|
|
2262
|
+
f"Either swap the provider — Runtime(graph, "
|
|
2263
|
+
f"llm_provider={claimed_by.__name__}()) — "
|
|
2264
|
+
f"{provider_default_hint}, or pass an explicit name "
|
|
2265
|
+
f"from {provider_class}'s model families."
|
|
2266
|
+
),
|
|
2267
|
+
context={
|
|
2268
|
+
"behavior": b.name,
|
|
2269
|
+
"model": b.model,
|
|
2270
|
+
"configured_provider": provider_class,
|
|
2271
|
+
"claimed_by_provider": claimed_by.__name__,
|
|
2272
|
+
"claimed_by_default_model": claimed_by_default,
|
|
2273
|
+
},
|
|
2274
|
+
)
|
|
2275
|
+
|
|
2276
|
+
|
|
2277
|
+
def _which_shipped_provider_claims(name: str, *, exclude: type):
|
|
2278
|
+
"""Return the first shipped provider class that recognizes `name`,
|
|
2279
|
+
excluding `exclude`. Returns None when no other shipped provider
|
|
2280
|
+
claims the name (permissive default per v1.0.2 #1 (b))."""
|
|
2281
|
+
from activegraph.llm.anthropic import AnthropicProvider
|
|
2282
|
+
from activegraph.llm.openai import OpenAIProvider
|
|
2283
|
+
|
|
2284
|
+
candidates = [AnthropicProvider, OpenAIProvider]
|
|
2285
|
+
for cls in candidates:
|
|
2286
|
+
if cls is exclude:
|
|
2287
|
+
continue
|
|
2288
|
+
recognizes = getattr(cls, "recognizes_model", None)
|
|
2289
|
+
if recognizes is None:
|
|
2290
|
+
continue
|
|
2291
|
+
# Instantiate cheaply: the providers' __init__ doesn't open the SDK.
|
|
2292
|
+
try:
|
|
2293
|
+
inst = cls()
|
|
2294
|
+
except Exception:
|
|
2295
|
+
# Defensive: if a provider's no-arg constructor ever requires
|
|
2296
|
+
# real credentials, skip it for the validation lookup.
|
|
2297
|
+
continue
|
|
2298
|
+
if inst.recognizes_model(name):
|
|
2299
|
+
return cls
|
|
2300
|
+
return None
|
|
2301
|
+
|
|
2302
|
+
|
|
2183
2303
|
def _budget_reason(name: Optional[str]) -> str:
|
|
2184
2304
|
if name is None:
|
|
2185
2305
|
return "budget.exhausted"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "activegraph"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.2"
|
|
8
8
|
description = "An event-sourced reactive graph runtime for long-running, auditable, agentic systems."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|