context-compiler-example-integrations 0.1.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.
- context_compiler_example_integrations/README.md +71 -0
- context_compiler_example_integrations/__init__.py +1 -0
- context_compiler_example_integrations/examples/__init__.py +12 -0
- context_compiler_example_integrations/examples/_shared/__init__.py +1 -0
- context_compiler_example_integrations/examples/_shared/litellm_request.py +61 -0
- context_compiler_example_integrations/examples/_shared/provider_mode.py +91 -0
- context_compiler_example_integrations/examples/checkpoint_continuation/README.md +77 -0
- context_compiler_example_integrations/examples/checkpoint_continuation/example.py +169 -0
- context_compiler_example_integrations/examples/checkpoint_continuation/fastapi/README.md +85 -0
- context_compiler_example_integrations/examples/checkpoint_continuation/fastapi/app.py +205 -0
- context_compiler_example_integrations/examples/execution_authorization/README.md +31 -0
- context_compiler_example_integrations/examples/execution_authorization/expense_approval/README.md +77 -0
- context_compiler_example_integrations/examples/execution_authorization/expense_approval/__init__.py +1 -0
- context_compiler_example_integrations/examples/execution_authorization/expense_approval/example.py +168 -0
- context_compiler_example_integrations/examples/execution_authorization/expense_approval/fastapi/README.md +97 -0
- context_compiler_example_integrations/examples/execution_authorization/expense_approval/fastapi/__init__.py +1 -0
- context_compiler_example_integrations/examples/execution_authorization/expense_approval/fastapi/app.py +312 -0
- context_compiler_example_integrations/examples/execution_authorization/expense_approval/fastapi/model_approval.py +98 -0
- context_compiler_example_integrations/examples/gateway_middleware/README.md +49 -0
- context_compiler_example_integrations/examples/gateway_middleware/customer_support_routing/README.md +40 -0
- context_compiler_example_integrations/examples/gateway_middleware/customer_support_routing/__init__.py +1 -0
- context_compiler_example_integrations/examples/gateway_middleware/customer_support_routing/example.py +220 -0
- context_compiler_example_integrations/examples/prompt_construction/README.md +27 -0
- context_compiler_example_integrations/examples/prompt_construction/litellm/README.md +197 -0
- context_compiler_example_integrations/examples/prompt_construction/litellm/basic.py +392 -0
- context_compiler_example_integrations/examples/prompt_construction/litellm/confirmation_helper.py +91 -0
- context_compiler_example_integrations/examples/prompt_construction/litellm/with_directive_drafter.py +524 -0
- context_compiler_example_integrations/examples/prompt_construction/writing_assistant/README.md +92 -0
- context_compiler_example_integrations/examples/prompt_construction/writing_assistant/__init__.py +1 -0
- context_compiler_example_integrations/examples/prompt_construction/writing_assistant/example.py +205 -0
- context_compiler_example_integrations/examples/retrieval_filtering/README.md +65 -0
- context_compiler_example_integrations/examples/retrieval_filtering/chromadb_hr_policy_lookup/README.md +120 -0
- context_compiler_example_integrations/examples/retrieval_filtering/chromadb_hr_policy_lookup/__init__.py +1 -0
- context_compiler_example_integrations/examples/retrieval_filtering/chromadb_hr_policy_lookup/example.py +320 -0
- context_compiler_example_integrations/examples/retrieval_filtering/hr_policy_lookup/README.md +123 -0
- context_compiler_example_integrations/examples/retrieval_filtering/hr_policy_lookup/__init__.py +1 -0
- context_compiler_example_integrations/examples/retrieval_filtering/hr_policy_lookup/example.py +300 -0
- context_compiler_example_integrations/examples/schema_selection/README.md +41 -0
- context_compiler_example_integrations/examples/schema_selection/litellm_response_format/response_format.py +218 -0
- context_compiler_example_integrations/examples/schema_selection/ollama_structured_output/README.md +60 -0
- context_compiler_example_integrations/examples/schema_selection/ollama_structured_output/example.py +171 -0
- context_compiler_example_integrations/examples/schema_selection/refund_intake/README.md +95 -0
- context_compiler_example_integrations/examples/schema_selection/refund_intake/__init__.py +1 -0
- context_compiler_example_integrations/examples/schema_selection/refund_intake/example.py +180 -0
- context_compiler_example_integrations/examples/tool_gating/README.md +55 -0
- context_compiler_example_integrations/examples/tool_gating/calendar_admin/README.md +34 -0
- context_compiler_example_integrations/examples/tool_gating/calendar_admin/__init__.py +1 -0
- context_compiler_example_integrations/examples/tool_gating/calendar_admin/example.py +195 -0
- context_compiler_example_integrations/examples/tool_gating/mcp_calendar_admin/README.md +93 -0
- context_compiler_example_integrations/examples/tool_gating/mcp_calendar_admin/__init__.py +1 -0
- context_compiler_example_integrations/examples/tool_gating/mcp_calendar_admin/example.py +250 -0
- context_compiler_example_integrations/examples/tool_gating/mcp_calendar_admin/live_model.py +369 -0
- context_compiler_example_integrations/reference_integrations/__init__.py +12 -0
- context_compiler_example_integrations/reference_integrations/litellm_proxy/README.md +251 -0
- context_compiler_example_integrations/reference_integrations/litellm_proxy/_checkpoint_support.py +146 -0
- context_compiler_example_integrations/reference_integrations/litellm_proxy/config.example.yaml +23 -0
- context_compiler_example_integrations/reference_integrations/litellm_proxy/context_compiler_precall_hook.py +163 -0
- context_compiler_example_integrations/reference_integrations/litellm_proxy/context_compiler_precall_hook_with_directive_drafter.py +289 -0
- context_compiler_example_integrations/reference_integrations/openwebui_pipe/README.md +233 -0
- context_compiler_example_integrations/reference_integrations/openwebui_pipe/open_webui_pipe.py +750 -0
- context_compiler_example_integrations/reference_integrations/openwebui_pipe/open_webui_pipe_with_directive_drafter.py +1045 -0
- context_compiler_example_integrations-0.1.0.dist-info/METADATA +110 -0
- context_compiler_example_integrations-0.1.0.dist-info/RECORD +65 -0
- context_compiler_example_integrations-0.1.0.dist-info/WHEEL +4 -0
- context_compiler_example_integrations-0.1.0.dist-info/licenses/LICENSE +173 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Context Compiler Example Integrations for Python
|
|
2
|
+
|
|
3
|
+
These examples show how authoritative state changes application behavior at runtime.
|
|
4
|
+
|
|
5
|
+
Each example demonstrates a single enforcement point where premise and policy influence what a host allows, routes, retrieves, builds, or executes.
|
|
6
|
+
|
|
7
|
+
- The core authority contract is provided by [`context-compiler`](https://github.com/rlippmann/context-compiler).
|
|
8
|
+
- Directive recognition can optionally be added with [`context-compiler-directive-drafter`](https://github.com/rlippmann/context-compiler-directive-drafter).
|
|
9
|
+
- These examples focus on where authoritative state changes application behavior.
|
|
10
|
+
|
|
11
|
+
*Prompt reinjection* influences ***model behavior***.
|
|
12
|
+
|
|
13
|
+
*Context Compiler* influences ***runtime behavior***.
|
|
14
|
+
|
|
15
|
+
## Install options
|
|
16
|
+
|
|
17
|
+
Base installation keeps this package discovery-first:
|
|
18
|
+
|
|
19
|
+
```shell
|
|
20
|
+
pip install "context-compiler-example-integrations"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
That installs the shared core dependency only:
|
|
24
|
+
|
|
25
|
+
- `context-compiler>=0.8.0`
|
|
26
|
+
|
|
27
|
+
Add extras only for the examples you want to inspect locally:
|
|
28
|
+
|
|
29
|
+
- `pip install "context-compiler-example-integrations[drafter]"` for examples that use `context-compiler-directive-drafter`
|
|
30
|
+
- `pip install "context-compiler-example-integrations[retrieval]"` for ChromaDB retrieval filtering examples
|
|
31
|
+
- `pip install "context-compiler-example-integrations[fastapi]"` for FastAPI variants
|
|
32
|
+
- `pip install "context-compiler-example-integrations[litellm]"` for LiteLLM-oriented examples and reference integrations
|
|
33
|
+
- `pip install "context-compiler-example-integrations[all]"` to install all package-managed optional dependencies
|
|
34
|
+
|
|
35
|
+
Open WebUI is not installed by this package. The Open WebUI reference
|
|
36
|
+
integration assumes Open WebUI is already installed and configured as the host
|
|
37
|
+
runtime.
|
|
38
|
+
|
|
39
|
+
## Generic examples
|
|
40
|
+
|
|
41
|
+
- [Checkpoint continuation](examples/checkpoint_continuation/README.md): persisted confirmation and resume flows change host behavior across turns or requests
|
|
42
|
+
- [Execution authorization](examples/execution_authorization/README.md): protected host actions execute only when authoritative state allows them
|
|
43
|
+
- [Gateway middleware](examples/gateway_middleware/README.md): the host allows, blocks, or routes requests before downstream work runs
|
|
44
|
+
- [Prompt construction](examples/prompt_construction/README.md): the host builds different request or prompt payloads from authoritative state
|
|
45
|
+
- [Retrieval filtering](examples/retrieval_filtering/README.md): the host changes which documents are eligible or relevant before returning results
|
|
46
|
+
- [Schema selection](examples/schema_selection/README.md): the host picks different workflow or response schemas from authoritative state
|
|
47
|
+
- [Tool gating](examples/tool_gating/README.md): the host changes which tools are visible or executable at runtime
|
|
48
|
+
|
|
49
|
+
## Reference integrations
|
|
50
|
+
|
|
51
|
+
Python also includes reference integrations for runtime-specific behavior after
|
|
52
|
+
the generic examples.
|
|
53
|
+
|
|
54
|
+
Open a reference integration when you want to see the same kind of runtime
|
|
55
|
+
behavior on a specific host or framework surface.
|
|
56
|
+
|
|
57
|
+
Start with the generic example first, then use the Python reference
|
|
58
|
+
integrations to inspect a runtime-specific path:
|
|
59
|
+
|
|
60
|
+
- [python/reference_integrations/litellm_proxy/README.md](reference_integrations/litellm_proxy/README.md)
|
|
61
|
+
- [python/reference_integrations/openwebui_pipe/README.md](reference_integrations/openwebui_pipe/README.md)
|
|
62
|
+
|
|
63
|
+
## Run an example
|
|
64
|
+
|
|
65
|
+
To explore or run an example, use a repository checkout:
|
|
66
|
+
|
|
67
|
+
1. Clone
|
|
68
|
+
[`context-compiler-example-integrations`](https://github.com/rlippmann/context-compiler-example-integrations).
|
|
69
|
+
2. Choose a generic example or a reference integration.
|
|
70
|
+
3. Open that example's README.
|
|
71
|
+
4. Follow the example-specific setup, runtime, and validation instructions.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Package root for installed example integrations."""
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Example modules for installed and editable package use."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
_PACKAGE_DIR = Path(__file__).resolve().parent
|
|
8
|
+
_SOURCE_DIR = _PACKAGE_DIR.parents[1] / "python" / "examples"
|
|
9
|
+
|
|
10
|
+
__path__ = [str(_PACKAGE_DIR)]
|
|
11
|
+
if _SOURCE_DIR.is_dir():
|
|
12
|
+
__path__.append(str(_SOURCE_DIR))
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Shared helpers for Python examples."""
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Tiny shared helper for LiteLLM live-validation request kwargs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Literal, TypedDict
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ProviderConfigLike(TypedDict):
|
|
9
|
+
mode: Literal["openai", "ollama", "openai_compatible"]
|
|
10
|
+
source: str
|
|
11
|
+
base_url: str
|
|
12
|
+
model: str
|
|
13
|
+
api_key: str | None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class LiteLLMProviderKwargs(TypedDict, total=False):
|
|
17
|
+
model: str
|
|
18
|
+
api_base: str
|
|
19
|
+
api_key: str
|
|
20
|
+
temperature: float
|
|
21
|
+
drop_params: bool
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def build_litellm_provider_kwargs(
|
|
25
|
+
config: ProviderConfigLike,
|
|
26
|
+
) -> LiteLLMProviderKwargs:
|
|
27
|
+
"""Return LiteLLM-safe provider/model kwargs for live validations."""
|
|
28
|
+
|
|
29
|
+
model = _normalize_litellm_model_id(config)
|
|
30
|
+
base_url = _config_value(config, "base_url")
|
|
31
|
+
if base_url is None:
|
|
32
|
+
raise RuntimeError("Provider config missing base_url.")
|
|
33
|
+
|
|
34
|
+
kwargs: LiteLLMProviderKwargs = {
|
|
35
|
+
"model": model,
|
|
36
|
+
"api_base": base_url,
|
|
37
|
+
"drop_params": True,
|
|
38
|
+
}
|
|
39
|
+
api_key = _config_value(config, "api_key")
|
|
40
|
+
if api_key:
|
|
41
|
+
kwargs["api_key"] = api_key
|
|
42
|
+
kwargs["temperature"] = 0
|
|
43
|
+
return kwargs
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _normalize_litellm_model_id(config: ProviderConfigLike) -> str:
|
|
47
|
+
mode = _config_value(config, "mode")
|
|
48
|
+
model = _config_value(config, "model")
|
|
49
|
+
if model is None:
|
|
50
|
+
raise RuntimeError("Provider config missing model.")
|
|
51
|
+
if mode == "ollama" and "/" not in model:
|
|
52
|
+
return f"ollama/{model}"
|
|
53
|
+
return model
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _config_value(config: object, key: str) -> str | None:
|
|
57
|
+
if isinstance(config, dict):
|
|
58
|
+
value = config.get(key)
|
|
59
|
+
else:
|
|
60
|
+
value = getattr(config, key, None)
|
|
61
|
+
return value if isinstance(value, str) or value is None else str(value)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Shared provider mode resolution for installed example modules."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Literal, cast
|
|
7
|
+
|
|
8
|
+
_ALLOWED_PROVIDER_VALUES = ("openai", "ollama", "openai_compatible")
|
|
9
|
+
_STARTUP_LOGGED = False
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class ProviderConfig:
|
|
14
|
+
mode: Literal["openai", "ollama", "openai_compatible"]
|
|
15
|
+
source: Literal["default", "PROVIDER", "OPENAI_BASE_URL override"]
|
|
16
|
+
base_url: str
|
|
17
|
+
model: str
|
|
18
|
+
api_key: str | None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def resolve_provider_config(
|
|
22
|
+
default_model: str = "openai/gpt-4o-mini",
|
|
23
|
+
) -> ProviderConfig:
|
|
24
|
+
"""Resolve provider mode config from environment using strict contract."""
|
|
25
|
+
base_url = os.getenv("OPENAI_BASE_URL", "").strip() or None
|
|
26
|
+
provider = os.getenv("PROVIDER", "").strip().lower() or None
|
|
27
|
+
api_key = os.getenv("OPENAI_API_KEY", "").strip() or None
|
|
28
|
+
model = os.getenv("MODEL", "").strip() or default_model
|
|
29
|
+
|
|
30
|
+
if base_url:
|
|
31
|
+
return ProviderConfig(
|
|
32
|
+
mode="openai_compatible",
|
|
33
|
+
source="OPENAI_BASE_URL override",
|
|
34
|
+
base_url=base_url,
|
|
35
|
+
model=model,
|
|
36
|
+
api_key=api_key,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
if provider is not None and provider not in _ALLOWED_PROVIDER_VALUES:
|
|
40
|
+
allowed_values = ", ".join(_ALLOWED_PROVIDER_VALUES)
|
|
41
|
+
raise RuntimeError(
|
|
42
|
+
f"Invalid PROVIDER value '{provider}'. Allowed values: {allowed_values}"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
mode: Literal["openai", "ollama", "openai_compatible"]
|
|
46
|
+
source: Literal["default", "PROVIDER", "OPENAI_BASE_URL override"]
|
|
47
|
+
if provider is None:
|
|
48
|
+
mode = "openai"
|
|
49
|
+
source = "default"
|
|
50
|
+
else:
|
|
51
|
+
mode = cast(Literal["openai", "ollama", "openai_compatible"], provider)
|
|
52
|
+
source = "PROVIDER"
|
|
53
|
+
|
|
54
|
+
if mode == "openai":
|
|
55
|
+
if not api_key:
|
|
56
|
+
raise RuntimeError("OPENAI_API_KEY is required in openai mode.")
|
|
57
|
+
return ProviderConfig(
|
|
58
|
+
mode=mode,
|
|
59
|
+
source=source,
|
|
60
|
+
base_url="https://api.openai.com/v1",
|
|
61
|
+
model=model,
|
|
62
|
+
api_key=api_key,
|
|
63
|
+
)
|
|
64
|
+
if mode == "ollama":
|
|
65
|
+
return ProviderConfig(
|
|
66
|
+
mode=mode,
|
|
67
|
+
source=source,
|
|
68
|
+
base_url="http://localhost:11434",
|
|
69
|
+
model=model,
|
|
70
|
+
api_key=api_key,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
raise RuntimeError("OPENAI_BASE_URL is required when PROVIDER=openai_compatible.")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def print_startup_config(
|
|
77
|
+
config: ProviderConfig, logger: logging.Logger | None = None
|
|
78
|
+
) -> None:
|
|
79
|
+
"""Emit one startup line per process with resolved provider config."""
|
|
80
|
+
global _STARTUP_LOGGED
|
|
81
|
+
if _STARTUP_LOGGED:
|
|
82
|
+
return
|
|
83
|
+
target_logger = logger or logging.getLogger(__name__)
|
|
84
|
+
target_logger.info(
|
|
85
|
+
"litellm_config mode=%s base_url=%s model=%s source=%s",
|
|
86
|
+
config.mode,
|
|
87
|
+
config.base_url,
|
|
88
|
+
config.model,
|
|
89
|
+
config.source,
|
|
90
|
+
)
|
|
91
|
+
_STARTUP_LOGGED = True
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Checkpoint continuation
|
|
2
|
+
|
|
3
|
+
Restoring a saved checkpoint changes whether a fresh host process can resume
|
|
4
|
+
and apply a pending itinerary change. This example shows checkpoint
|
|
5
|
+
continuation in a generic Python travel-booking flow.
|
|
6
|
+
|
|
7
|
+
## Domain
|
|
8
|
+
|
|
9
|
+
The domain is a small travel-booking change flow.
|
|
10
|
+
|
|
11
|
+
The user requests a change from the current itinerary to a new itinerary.
|
|
12
|
+
That change requires confirmation before the host applies it.
|
|
13
|
+
|
|
14
|
+
## Runtime
|
|
15
|
+
|
|
16
|
+
This is a generic Python example.
|
|
17
|
+
|
|
18
|
+
It does not call an LLM.
|
|
19
|
+
|
|
20
|
+
It does not use directive drafter.
|
|
21
|
+
|
|
22
|
+
## What Context Compiler owns
|
|
23
|
+
|
|
24
|
+
Context Compiler owns:
|
|
25
|
+
|
|
26
|
+
- authoritative policy state
|
|
27
|
+
- the pending confirmation continuation state
|
|
28
|
+
- the checkpoint that captures both
|
|
29
|
+
|
|
30
|
+
In this example, the pending checkpoint state is what makes the resumed
|
|
31
|
+
confirmation meaningful.
|
|
32
|
+
|
|
33
|
+
Restoring authoritative state alone is not enough to resume the pending change.
|
|
34
|
+
|
|
35
|
+
## What the host owns
|
|
36
|
+
|
|
37
|
+
The host owns:
|
|
38
|
+
|
|
39
|
+
- the booking record
|
|
40
|
+
- checkpoint persistence
|
|
41
|
+
- request/process boundaries
|
|
42
|
+
- the runtime behavior that actually applies the itinerary change
|
|
43
|
+
|
|
44
|
+
The host reads authoritative Context Compiler state after confirmation and
|
|
45
|
+
decides whether to apply the booking change.
|
|
46
|
+
|
|
47
|
+
## Why this is not prompt reinjection
|
|
48
|
+
|
|
49
|
+
This example does not re-send hidden instructions to a model.
|
|
50
|
+
|
|
51
|
+
The observable behavior change is host-side: the booking record changes only
|
|
52
|
+
after a restored engine resumes the pending confirmation and authoritative
|
|
53
|
+
state changes.
|
|
54
|
+
|
|
55
|
+
## Example behavior
|
|
56
|
+
|
|
57
|
+
1. The host starts with a booking on `boston_trip`.
|
|
58
|
+
2. The user initiates a switch to `chicago_trip`.
|
|
59
|
+
3. Context Compiler enters a pending confirmation state.
|
|
60
|
+
4. The host exports and persists the checkpoint.
|
|
61
|
+
5. A fresh host process restores that checkpoint into a new engine.
|
|
62
|
+
6. If the user confirms, the host applies the itinerary change.
|
|
63
|
+
7. If the user rejects or sends unrelated text, the booking remains unchanged.
|
|
64
|
+
|
|
65
|
+
## Run
|
|
66
|
+
|
|
67
|
+
From the repository root:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
uv run python python/examples/checkpoint_continuation/example.py
|
|
71
|
+
uv run pytest python/tests/test_checkpoint_continuation_example.py
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## FastAPI variant
|
|
75
|
+
|
|
76
|
+
For a request-boundary example, see
|
|
77
|
+
[python/examples/checkpoint_continuation/fastapi/README.md](fastapi/README.md).
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""Minimal checkpoint-continuation example for a travel booking change."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Literal, TypedDict, cast
|
|
5
|
+
|
|
6
|
+
from context_compiler import POLICY_USE, State, create_engine, get_policy_items
|
|
7
|
+
from context_compiler.engine import Checkpoint, Engine, State as EngineState
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BookingRecord(TypedDict):
|
|
11
|
+
booking_id: str
|
|
12
|
+
active_itinerary: str
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BookingChangeRuntimeResult(TypedDict):
|
|
16
|
+
compiler_input: str
|
|
17
|
+
decision_kind: Literal["clarify", "update", "passthrough"]
|
|
18
|
+
prompt_to_user: str | None
|
|
19
|
+
checkpoint_pending: bool
|
|
20
|
+
active_itinerary: str
|
|
21
|
+
host_applied_change: bool
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class CheckpointStore:
|
|
26
|
+
"""Host-owned persistence for serialized engine checkpoints."""
|
|
27
|
+
|
|
28
|
+
saved_checkpoint: Checkpoint | None = None
|
|
29
|
+
|
|
30
|
+
def save(self, checkpoint: Checkpoint) -> None:
|
|
31
|
+
self.saved_checkpoint = checkpoint
|
|
32
|
+
|
|
33
|
+
def load(self) -> Checkpoint:
|
|
34
|
+
if self.saved_checkpoint is None:
|
|
35
|
+
raise ValueError("no checkpoint saved")
|
|
36
|
+
return self.saved_checkpoint
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class BookingHost:
|
|
41
|
+
"""Host-owned runtime behavior for the booking example."""
|
|
42
|
+
|
|
43
|
+
booking: BookingRecord
|
|
44
|
+
applied_changes: list[str] = field(default_factory=list)
|
|
45
|
+
|
|
46
|
+
def apply_selected_itinerary(self, state: State) -> bool:
|
|
47
|
+
selected_itinerary = select_itinerary_from_state(state)
|
|
48
|
+
if selected_itinerary is None:
|
|
49
|
+
return False
|
|
50
|
+
|
|
51
|
+
self.booking["active_itinerary"] = selected_itinerary
|
|
52
|
+
self.applied_changes.append(selected_itinerary)
|
|
53
|
+
return True
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def select_itinerary_from_state(state: State) -> str | None:
|
|
57
|
+
"""Select the host-visible itinerary from authoritative state."""
|
|
58
|
+
|
|
59
|
+
use_items = list(get_policy_items(state, POLICY_USE))
|
|
60
|
+
if not use_items:
|
|
61
|
+
return None
|
|
62
|
+
return use_items[0]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _decision_kind_name(
|
|
66
|
+
decision: object,
|
|
67
|
+
) -> Literal["clarify", "update", "passthrough"]:
|
|
68
|
+
if not isinstance(decision, dict):
|
|
69
|
+
raise ValueError("unexpected decision shape")
|
|
70
|
+
|
|
71
|
+
kind = decision.get("kind")
|
|
72
|
+
kind_name = getattr(kind, "value", None)
|
|
73
|
+
if kind_name not in {"clarify", "update", "passthrough"}:
|
|
74
|
+
raise ValueError(f"unexpected decision kind: {kind_name}")
|
|
75
|
+
return cast(Literal["clarify", "update", "passthrough"], kind_name)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def initiate_itinerary_change(
|
|
79
|
+
engine: Engine,
|
|
80
|
+
*,
|
|
81
|
+
current_itinerary: str,
|
|
82
|
+
requested_itinerary: str,
|
|
83
|
+
) -> BookingChangeRuntimeResult:
|
|
84
|
+
"""Ask Context Compiler to hold a travel change behind confirmation."""
|
|
85
|
+
|
|
86
|
+
compiler_input = f"use {requested_itinerary} instead of {current_itinerary}"
|
|
87
|
+
decision = engine.step(compiler_input)
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
"compiler_input": compiler_input,
|
|
91
|
+
"decision_kind": _decision_kind_name(decision),
|
|
92
|
+
"prompt_to_user": decision.get("prompt_to_user"),
|
|
93
|
+
"checkpoint_pending": engine.has_pending_clarification(),
|
|
94
|
+
"active_itinerary": select_itinerary_from_state(engine.state)
|
|
95
|
+
or current_itinerary,
|
|
96
|
+
"host_applied_change": False,
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def restore_engine_from_checkpoint(checkpoint: Checkpoint) -> Engine:
|
|
101
|
+
"""Restore both authoritative state and pending continuation state."""
|
|
102
|
+
|
|
103
|
+
engine = create_engine()
|
|
104
|
+
engine.import_checkpoint(checkpoint)
|
|
105
|
+
return engine
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def restore_engine_from_authoritative_state_only(
|
|
109
|
+
checkpoint: Checkpoint,
|
|
110
|
+
) -> Engine:
|
|
111
|
+
"""Restore only authoritative state, without pending continuation state."""
|
|
112
|
+
|
|
113
|
+
authoritative_state = cast(EngineState, checkpoint["authoritative_state"])
|
|
114
|
+
return create_engine(state=authoritative_state)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def continue_itinerary_change(
|
|
118
|
+
engine: Engine,
|
|
119
|
+
host: BookingHost,
|
|
120
|
+
user_input: str,
|
|
121
|
+
) -> BookingChangeRuntimeResult:
|
|
122
|
+
"""Resume a pending change and apply host behavior only after confirmation."""
|
|
123
|
+
|
|
124
|
+
decision = engine.step(user_input)
|
|
125
|
+
host_applied_change = False
|
|
126
|
+
if _decision_kind_name(decision) == "update":
|
|
127
|
+
host_applied_change = host.apply_selected_itinerary(engine.state)
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
"compiler_input": user_input,
|
|
131
|
+
"decision_kind": _decision_kind_name(decision),
|
|
132
|
+
"prompt_to_user": decision.get("prompt_to_user"),
|
|
133
|
+
"checkpoint_pending": engine.has_pending_clarification(),
|
|
134
|
+
"active_itinerary": host.booking["active_itinerary"],
|
|
135
|
+
"host_applied_change": host_applied_change,
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def run_demo() -> dict[str, BookingChangeRuntimeResult | Checkpoint]:
|
|
140
|
+
"""Run a deterministic checkpoint-continuation demonstration."""
|
|
141
|
+
|
|
142
|
+
initial_booking: BookingRecord = {
|
|
143
|
+
"booking_id": "booking-100",
|
|
144
|
+
"active_itinerary": "boston_trip",
|
|
145
|
+
}
|
|
146
|
+
first_host = BookingHost(booking=initial_booking.copy())
|
|
147
|
+
first_engine = create_engine()
|
|
148
|
+
checkpoint_store = CheckpointStore()
|
|
149
|
+
|
|
150
|
+
pending_result = initiate_itinerary_change(
|
|
151
|
+
first_engine,
|
|
152
|
+
current_itinerary=first_host.booking["active_itinerary"],
|
|
153
|
+
requested_itinerary="chicago_trip",
|
|
154
|
+
)
|
|
155
|
+
checkpoint_store.save(first_engine.export_checkpoint())
|
|
156
|
+
|
|
157
|
+
resumed_engine = restore_engine_from_checkpoint(checkpoint_store.load())
|
|
158
|
+
resumed_host = BookingHost(booking=first_host.booking.copy())
|
|
159
|
+
confirmed_result = continue_itinerary_change(resumed_engine, resumed_host, "yes")
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
"pending_result": pending_result,
|
|
163
|
+
"confirmed_result": confirmed_result,
|
|
164
|
+
"saved_checkpoint": checkpoint_store.load(),
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
if __name__ == "__main__":
|
|
169
|
+
print(run_demo())
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Checkpoint continuation with FastAPI
|
|
2
|
+
|
|
3
|
+
A saved checkpoint lets later HTTP requests resume or reject a pending
|
|
4
|
+
itinerary change instead of starting over. This example shows checkpoint
|
|
5
|
+
continuation across stateless HTTP request boundaries.
|
|
6
|
+
|
|
7
|
+
## Enforcement point
|
|
8
|
+
|
|
9
|
+
Checkpoint continuation
|
|
10
|
+
|
|
11
|
+
## Domain
|
|
12
|
+
|
|
13
|
+
The domain is a small travel-booking change flow.
|
|
14
|
+
|
|
15
|
+
The first request initiates a change from `boston_trip` to `chicago_trip`.
|
|
16
|
+
|
|
17
|
+
That change requires confirmation before the host applies it.
|
|
18
|
+
|
|
19
|
+
## Runtime
|
|
20
|
+
|
|
21
|
+
This is a small FastAPI example.
|
|
22
|
+
|
|
23
|
+
FastAPI is secondary to the enforcement point.
|
|
24
|
+
|
|
25
|
+
It exists to show that the host can persist a checkpoint between separate HTTP
|
|
26
|
+
requests and restore it later into a fresh engine.
|
|
27
|
+
|
|
28
|
+
## Ownership boundary
|
|
29
|
+
|
|
30
|
+
Context Compiler owns:
|
|
31
|
+
|
|
32
|
+
- authoritative policy state
|
|
33
|
+
- pending continuation state
|
|
34
|
+
- checkpoint export and import
|
|
35
|
+
|
|
36
|
+
The host owns:
|
|
37
|
+
|
|
38
|
+
- checkpoint storage
|
|
39
|
+
- request routing
|
|
40
|
+
- booking mutation
|
|
41
|
+
|
|
42
|
+
In this example, the host creates a fresh engine per request.
|
|
43
|
+
|
|
44
|
+
The second request resumes the flow only because the host restores the saved
|
|
45
|
+
checkpoint, not because the process remembered a conversation.
|
|
46
|
+
|
|
47
|
+
## Why checkpoint continuation differs from state restore
|
|
48
|
+
|
|
49
|
+
Checkpoint continuation includes pending confirmation state.
|
|
50
|
+
|
|
51
|
+
Authoritative-state-only restore does not.
|
|
52
|
+
|
|
53
|
+
That difference matters here:
|
|
54
|
+
|
|
55
|
+
- restoring the full checkpoint lets a later `yes` resume the pending trip
|
|
56
|
+
change
|
|
57
|
+
- restoring authoritative state alone does not resume that pending confirmation
|
|
58
|
+
|
|
59
|
+
## Why this is not prompt reinjection
|
|
60
|
+
|
|
61
|
+
This example does not re-send hidden instructions to a model.
|
|
62
|
+
|
|
63
|
+
The observable behavior change is host-side: the booking only changes after a
|
|
64
|
+
later request restores the checkpoint and confirmation succeeds.
|
|
65
|
+
|
|
66
|
+
## Endpoints
|
|
67
|
+
|
|
68
|
+
- `POST /change-trip`
|
|
69
|
+
- creates a pending confirmation
|
|
70
|
+
- persists a checkpoint in the host store
|
|
71
|
+
- `POST /confirm`
|
|
72
|
+
- restores the saved checkpoint into a fresh engine
|
|
73
|
+
- accepts `yes`, `no`, or unrelated text
|
|
74
|
+
- applies the booking change only after successful confirmation
|
|
75
|
+
- `GET /booking`
|
|
76
|
+
- returns the host-owned booking state
|
|
77
|
+
|
|
78
|
+
## Validate
|
|
79
|
+
|
|
80
|
+
From the repository root:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
uv run pytest python/tests/test_fastapi_checkpoint_continuation_example.py
|
|
84
|
+
./scripts/validate_python.sh
|
|
85
|
+
```
|