activegraph 1.0.2__tar.gz → 1.0.2.post1__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.2 → activegraph-1.0.2.post1}/PKG-INFO +1 -1
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/__init__.py +1 -1
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/behaviors/decorators.py +12 -0
- activegraph-1.0.2.post1/activegraph/runtime/_live.py +173 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/runtime/runtime.py +24 -90
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph.egg-info/PKG-INFO +1 -1
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph.egg-info/SOURCES.txt +1 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/pyproject.toml +1 -1
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_llm_default_model.py +243 -17
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/README.md +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/__main__.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/behaviors/__init__.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/behaviors/base.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/cli/__init__.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/cli/main.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/cli/quickstart.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/core/__init__.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/core/clock.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/core/event.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/core/graph.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/core/ids.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/core/patch.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/core/view.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/errors.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/frame.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/llm/__init__.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/llm/anthropic.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/llm/cache.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/llm/errors.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/llm/openai.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/llm/parsing.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/llm/prompt.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/llm/provider.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/llm/recorded.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/llm/types.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/observability/__init__.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/observability/logging.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/observability/metrics.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/observability/migration.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/observability/prometheus.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/observability/status.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/packs/__init__.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/packs/diligence/__init__.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/packs/diligence/behaviors.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/packs/diligence/fixtures/__init__.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/packs/diligence/fixtures/companies.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/packs/diligence/object_types.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/packs/diligence/prompts/document_researcher.md +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/packs/diligence/prompts/memo_synthesizer.md +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/packs/diligence/prompts/question_generator.md +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/packs/diligence/prompts/risk_identifier.md +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/packs/diligence/settings.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/packs/diligence/tools.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/packs/loader.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/packs/scaffold.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/policy.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/runtime/__init__.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/runtime/behavior_graph.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/runtime/budget.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/runtime/config_errors.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/runtime/diff.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/runtime/errors.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/runtime/exec_errors.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/runtime/patterns.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/runtime/queue.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/runtime/registration_errors.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/runtime/registry.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/runtime/scheduler.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/runtime/view_builder.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/store/__init__.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/store/base.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/store/conformance.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/store/errors.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/store/memory.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/store/postgres.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/store/serde.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/store/sqlite.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/store/url.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/tools/__init__.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/tools/base.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/tools/cache.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/tools/context.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/tools/decorators.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/tools/errors.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/tools/graph_query.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/tools/recorded.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/tools/web_fetch.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/trace/__init__.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/trace/causal.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph/trace/printer.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph.egg-info/dependency_links.txt +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph.egg-info/entry_points.txt +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph.egg-info/requires.txt +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/activegraph.egg-info/top_level.txt +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/setup.cfg +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_activate_after.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_causal_cross_tool.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_cli.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_clock.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_diff.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_diligence_pack.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_diligence_with_tools.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_doc_links.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_doc_site_reachable.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_errors_format.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_event.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_fork.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_graph.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_ids.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_llm_anthropic.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_llm_behavior.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_llm_budget.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_llm_causal.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_llm_claim_extraction.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_llm_determinism.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_llm_failure.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_llm_openai.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_llm_prompt.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_llm_provider_fixtures.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_llm_replay.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_llm_tool_loop.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_llm_trace.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_llm_trace_snapshot.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_llm_types.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_migration.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_observability_logging.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_observability_metrics.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_operate_example.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_pack_scaffold.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_packs.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_patch.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_pattern_matcher.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_pattern_parser.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_pattern_subscriptions.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_persistence.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_postgres_store.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_quickstart.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_quickstart_snapshot.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_replay.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_replay_trace_snapshot.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_requeue_unfired.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_resume_example.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_runtime.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_runtime_status.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_serde.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_store_conformance.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_store_url.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_tool_replay.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_tool_trace_snapshot.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_tools.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_trace.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_tutorial_snippets.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_v1_0_1_patches.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_version_sync.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_view.py +0 -0
- {activegraph-1.0.2 → activegraph-1.0.2.post1}/tests/test_wheel_completeness.py +0 -0
|
@@ -85,6 +85,12 @@ def register(behavior_obj: Union[Behavior, RelationBehavior]) -> None:
|
|
|
85
85
|
f"decorators to construct one."
|
|
86
86
|
)
|
|
87
87
|
_REGISTRY.append(behavior_obj)
|
|
88
|
+
# v1.0.2.post1 #1 (b): if any Runtime is already alive, validate the
|
|
89
|
+
# new behavior against each one's configured provider so cross-
|
|
90
|
+
# provider model mismatches fire at the @llm_behavior / register()
|
|
91
|
+
# line rather than at first run_goal.
|
|
92
|
+
from activegraph.runtime._live import validate_behavior_against_live_runtimes
|
|
93
|
+
validate_behavior_against_live_runtimes(behavior_obj)
|
|
88
94
|
|
|
89
95
|
|
|
90
96
|
def behavior(
|
|
@@ -248,6 +254,12 @@ def llm_behavior(
|
|
|
248
254
|
max_tool_turns=max_tool_turns,
|
|
249
255
|
)
|
|
250
256
|
_REGISTRY.append(b)
|
|
257
|
+
# v1.0.2.post1 #1 (b): validate against any live Runtime's
|
|
258
|
+
# provider. Mirrors the eager check in register() so decorated
|
|
259
|
+
# behaviors with cross-provider model names fire at the
|
|
260
|
+
# @llm_behavior line instead of at first run_goal.
|
|
261
|
+
from activegraph.runtime._live import validate_behavior_against_live_runtimes
|
|
262
|
+
validate_behavior_against_live_runtimes(b)
|
|
251
263
|
return b
|
|
252
264
|
|
|
253
265
|
return wrap
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""Live-Runtime tracking + single-behavior cross-provider validation.
|
|
2
|
+
|
|
3
|
+
CONTRACT v1.0.2 #1 (b). The validation fires at *both* binding
|
|
4
|
+
moments: ``Runtime(graph, llm_provider=...)`` construction (against
|
|
5
|
+
the existing registry) and ``register()`` / ``@llm_behavior``
|
|
6
|
+
decoration (against any live Runtime via the WeakSet below).
|
|
7
|
+
|
|
8
|
+
The WeakSet is module-level so the registration decorators can
|
|
9
|
+
look it up without importing ``Runtime`` at module load. Runtimes
|
|
10
|
+
self-register inside ``__init__`` after wiring; the WeakSet
|
|
11
|
+
auto-cleans GC'd Runtimes — no explicit ``Runtime.close()`` is
|
|
12
|
+
required.
|
|
13
|
+
|
|
14
|
+
The single-behavior validator here is intentionally narrow: it
|
|
15
|
+
only does the cross-provider mismatch check (recognized name
|
|
16
|
+
belonging to a different shipped provider). It does *not* stamp
|
|
17
|
+
provider defaults onto ``model=None`` behaviors — that side
|
|
18
|
+
effect lives in ``_resolve_and_validate_llm_models`` in
|
|
19
|
+
``runtime.py``, which still runs at first ``_ensure_registry``.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import weakref
|
|
25
|
+
from typing import TYPE_CHECKING, Any
|
|
26
|
+
|
|
27
|
+
from activegraph.behaviors.base import LLMBehavior
|
|
28
|
+
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
from activegraph.runtime.runtime import Runtime
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# WeakSet so abandoned Runtimes don't leak validation calls. The
|
|
34
|
+
# fork/replay paths construct fresh Runtimes that self-register; the
|
|
35
|
+
# parent stays in the set until GC'd.
|
|
36
|
+
_LIVE_RUNTIMES: "weakref.WeakSet[Runtime]" = weakref.WeakSet()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def track_runtime(rt: "Runtime") -> None:
|
|
40
|
+
"""Register a live Runtime for cross-provider validation. Called
|
|
41
|
+
by ``Runtime.__init__`` after the provider and graph are wired."""
|
|
42
|
+
_LIVE_RUNTIMES.add(rt)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def live_runtimes() -> list["Runtime"]:
|
|
46
|
+
"""Snapshot of currently-alive Runtimes (a list of strong refs that
|
|
47
|
+
the caller releases at end-of-call). Used by ``register()`` and the
|
|
48
|
+
decorators to validate a new behavior against each live provider."""
|
|
49
|
+
return list(_LIVE_RUNTIMES)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _clear_for_test() -> None:
|
|
53
|
+
"""Empty the live-Runtime set. Test-only — used by the conftest
|
|
54
|
+
fixture that isolates the global registry between tests. Production
|
|
55
|
+
code shouldn't call this: the WeakSet cleans itself on GC."""
|
|
56
|
+
_LIVE_RUNTIMES.clear()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def validate_behavior_against_live_runtimes(behavior: Any) -> None:
|
|
60
|
+
"""Validate a freshly-registered behavior against every live
|
|
61
|
+
Runtime's provider. Raises :class:`InvalidRuntimeConfiguration` on
|
|
62
|
+
the first cross-provider mismatch.
|
|
63
|
+
|
|
64
|
+
No-op when ``behavior`` isn't an :class:`LLMBehavior`, when no
|
|
65
|
+
Runtime is live, or when the behavior has no explicit model (model
|
|
66
|
+
resolution against the provider's default happens at Runtime
|
|
67
|
+
construction or first-run, not here).
|
|
68
|
+
"""
|
|
69
|
+
if not isinstance(behavior, LLMBehavior):
|
|
70
|
+
return
|
|
71
|
+
if behavior.model is None:
|
|
72
|
+
return
|
|
73
|
+
for rt in live_runtimes():
|
|
74
|
+
provider = getattr(rt, "llm_provider", None)
|
|
75
|
+
if provider is None:
|
|
76
|
+
continue
|
|
77
|
+
_validate_one(behavior, provider)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _validate_one(behavior: LLMBehavior, provider: Any) -> None:
|
|
81
|
+
"""Cross-provider mismatch check for a single behavior against a
|
|
82
|
+
single provider. Pure: no side effects, no model-default stamping.
|
|
83
|
+
|
|
84
|
+
Permissive by default per v1.0.2 #1 (b): names no shipped provider
|
|
85
|
+
recognizes pass silently. Only recognized cross-provider mismatches
|
|
86
|
+
raise. The runtime-side ``_resolve_and_validate_llm_models``
|
|
87
|
+
delegates to this for its per-behavior pass so the check lives in
|
|
88
|
+
one place.
|
|
89
|
+
"""
|
|
90
|
+
from activegraph.runtime.config_errors import InvalidRuntimeConfiguration
|
|
91
|
+
|
|
92
|
+
model = behavior.model
|
|
93
|
+
if model is None:
|
|
94
|
+
return
|
|
95
|
+
recognizes = getattr(provider, "recognizes_model", None)
|
|
96
|
+
if recognizes is None or recognizes(model):
|
|
97
|
+
return
|
|
98
|
+
claimed_by = _which_shipped_provider_claims(model, exclude=type(provider))
|
|
99
|
+
if claimed_by is None:
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
provider_class = type(provider).__name__
|
|
103
|
+
provider_default = getattr(provider, "default_model", None) or "claude-sonnet-4-5"
|
|
104
|
+
claimed_by_default = getattr(claimed_by, "default_model", "")
|
|
105
|
+
provider_default_hint = (
|
|
106
|
+
f"or remove the model= argument to use {provider_class}'s "
|
|
107
|
+
f"default ({provider_default!r})"
|
|
108
|
+
if provider_default
|
|
109
|
+
else f"or set a {provider_class}-compatible model name"
|
|
110
|
+
)
|
|
111
|
+
raise InvalidRuntimeConfiguration(
|
|
112
|
+
(
|
|
113
|
+
f"@llm_behavior(name={behavior.name!r}, model={model!r}) "
|
|
114
|
+
f"names a {claimed_by.__name__}-family model, but the "
|
|
115
|
+
f"runtime is configured with {provider_class}"
|
|
116
|
+
),
|
|
117
|
+
what_failed=(
|
|
118
|
+
f"The behavior {behavior.name!r} pinned model={model!r}. "
|
|
119
|
+
f"That name belongs to {claimed_by.__name__}'s model "
|
|
120
|
+
f"family, but this Runtime was constructed with a "
|
|
121
|
+
f"{provider_class} instance. Sending the name to the "
|
|
122
|
+
f"wrong provider produces an HTTP 404 (or equivalent "
|
|
123
|
+
f"'unknown model' response) at first LLM call, with no "
|
|
124
|
+
f"hint that the mismatch is the cause."
|
|
125
|
+
),
|
|
126
|
+
why=(
|
|
127
|
+
"v1.0.2 #1 validates explicit model names at both binding "
|
|
128
|
+
"moments (Runtime construction and register()/decoration) "
|
|
129
|
+
"against each shipped provider's recognizes_model() "
|
|
130
|
+
"method. The configured provider doesn't claim this name, "
|
|
131
|
+
"but another shipped provider does — that's a "
|
|
132
|
+
"configuration mismatch worth surfacing before the first "
|
|
133
|
+
"network call rather than after."
|
|
134
|
+
),
|
|
135
|
+
how_to_fix=(
|
|
136
|
+
f"Either swap the provider — Runtime(graph, "
|
|
137
|
+
f"llm_provider={claimed_by.__name__}()) — "
|
|
138
|
+
f"{provider_default_hint}, or pass an explicit name "
|
|
139
|
+
f"from {provider_class}'s model families."
|
|
140
|
+
),
|
|
141
|
+
context={
|
|
142
|
+
"behavior": behavior.name,
|
|
143
|
+
"model": model,
|
|
144
|
+
"configured_provider": provider_class,
|
|
145
|
+
"claimed_by_provider": claimed_by.__name__,
|
|
146
|
+
"claimed_by_default_model": claimed_by_default,
|
|
147
|
+
},
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _which_shipped_provider_claims(name: str, *, exclude: type) -> Any:
|
|
152
|
+
"""Return the first shipped provider class that recognizes `name`,
|
|
153
|
+
excluding `exclude`. Returns None when no other shipped provider
|
|
154
|
+
claims the name (permissive default per v1.0.2 #1 (b))."""
|
|
155
|
+
from activegraph.llm.anthropic import AnthropicProvider
|
|
156
|
+
from activegraph.llm.openai import OpenAIProvider
|
|
157
|
+
|
|
158
|
+
candidates = [AnthropicProvider, OpenAIProvider]
|
|
159
|
+
for cls in candidates:
|
|
160
|
+
if cls is exclude:
|
|
161
|
+
continue
|
|
162
|
+
recognizes = getattr(cls, "recognizes_model", None)
|
|
163
|
+
if recognizes is None:
|
|
164
|
+
continue
|
|
165
|
+
try:
|
|
166
|
+
inst = cls()
|
|
167
|
+
except Exception:
|
|
168
|
+
# Defensive: if a provider's no-arg constructor ever requires
|
|
169
|
+
# real credentials, skip it for the validation lookup.
|
|
170
|
+
continue
|
|
171
|
+
if inst.recognizes_model(name):
|
|
172
|
+
return cls
|
|
173
|
+
return None
|
|
@@ -303,6 +303,23 @@ class Runtime:
|
|
|
303
303
|
self._pack_behaviors: list = []
|
|
304
304
|
self._pack_tools: list = []
|
|
305
305
|
|
|
306
|
+
# ---- v1.0.2.post1 #1 (b): eager cross-provider validation ----
|
|
307
|
+
# First run the bulk validation pass against whatever's already
|
|
308
|
+
# in the registry; only if it passes do we add this Runtime to
|
|
309
|
+
# the live-set. Failed constructions stay out of the WeakSet so
|
|
310
|
+
# they can't influence subsequent register() / @llm_behavior
|
|
311
|
+
# validation calls. The lazy path inside _ensure_registry()
|
|
312
|
+
# stays as a defensive double-check.
|
|
313
|
+
if self.llm_provider is not None:
|
|
314
|
+
source = (
|
|
315
|
+
self._explicit_behaviors
|
|
316
|
+
if self._explicit_behaviors is not None
|
|
317
|
+
else get_registry()
|
|
318
|
+
)
|
|
319
|
+
_resolve_and_validate_llm_models(source, self.llm_provider)
|
|
320
|
+
from activegraph.runtime._live import track_runtime
|
|
321
|
+
track_runtime(self)
|
|
322
|
+
|
|
306
323
|
# ---------- public surface ----------
|
|
307
324
|
|
|
308
325
|
@property
|
|
@@ -2190,11 +2207,12 @@ def _resolve_and_validate_llm_models(source, provider) -> None:
|
|
|
2190
2207
|
raise InvalidRuntimeConfiguration for explicit names that belong to
|
|
2191
2208
|
a different shipped provider. CONTRACT v1.0.2 #1.
|
|
2192
2209
|
|
|
2193
|
-
The
|
|
2194
|
-
|
|
2195
|
-
|
|
2210
|
+
The cross-provider mismatch check delegates to
|
|
2211
|
+
:func:`activegraph.runtime._live._validate_one` so the same check
|
|
2212
|
+
fires from both binding moments (Runtime construction here,
|
|
2213
|
+
register()/decoration in ``_live.validate_behavior_against_live_runtimes``).
|
|
2196
2214
|
"""
|
|
2197
|
-
from activegraph.runtime.
|
|
2215
|
+
from activegraph.runtime._live import _validate_one
|
|
2198
2216
|
|
|
2199
2217
|
# Custom providers that pre-date v1.0.2 don't declare `default_model`.
|
|
2200
2218
|
# Fall back to the v1.0.1 hardcoded default — keeps every pre-v1.0.2
|
|
@@ -2202,8 +2220,6 @@ def _resolve_and_validate_llm_models(source, provider) -> None:
|
|
|
2202
2220
|
# byte-identically without code changes. The runtime only swaps in a
|
|
2203
2221
|
# different default when the provider opts in by declaring one.
|
|
2204
2222
|
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
2223
|
|
|
2208
2224
|
for b in source:
|
|
2209
2225
|
if not isinstance(b, LLMBehavior):
|
|
@@ -2214,90 +2230,8 @@ def _resolve_and_validate_llm_models(source, provider) -> None:
|
|
|
2214
2230
|
# declare one).
|
|
2215
2231
|
b.model = provider_default
|
|
2216
2232
|
continue
|
|
2217
|
-
|
|
2218
|
-
|
|
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
|
|
2233
|
+
# v1.0.2 #1 (b): cross-provider mismatch check.
|
|
2234
|
+
_validate_one(b, provider)
|
|
2301
2235
|
|
|
2302
2236
|
|
|
2303
2237
|
def _budget_reason(name: Optional[str]) -> str:
|
|
@@ -55,6 +55,7 @@ activegraph/packs/diligence/prompts/memo_synthesizer.md
|
|
|
55
55
|
activegraph/packs/diligence/prompts/question_generator.md
|
|
56
56
|
activegraph/packs/diligence/prompts/risk_identifier.md
|
|
57
57
|
activegraph/runtime/__init__.py
|
|
58
|
+
activegraph/runtime/_live.py
|
|
58
59
|
activegraph/runtime/behavior_graph.py
|
|
59
60
|
activegraph/runtime/budget.py
|
|
60
61
|
activegraph/runtime/config_errors.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "activegraph"
|
|
7
|
-
version = "1.0.2"
|
|
7
|
+
version = "1.0.2.post1"
|
|
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"
|