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
context_compiler_example_integrations/examples/schema_selection/ollama_structured_output/example.py
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""Minimal host-side Ollama structured-output schema selection.
|
|
2
|
+
|
|
3
|
+
Flow:
|
|
4
|
+
Context Compiler state -> host selection logic -> Ollama `format` JSON Schema.
|
|
5
|
+
|
|
6
|
+
This example keeps model execution optional so tests can validate behavior without Ollama.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import urllib.error
|
|
12
|
+
import urllib.request
|
|
13
|
+
from collections.abc import Mapping
|
|
14
|
+
from typing import Any, TypedDict, cast
|
|
15
|
+
|
|
16
|
+
from context_compiler import (
|
|
17
|
+
POLICY_PROHIBIT,
|
|
18
|
+
POLICY_USE,
|
|
19
|
+
State,
|
|
20
|
+
create_engine,
|
|
21
|
+
get_clarify_prompt,
|
|
22
|
+
get_decision_state,
|
|
23
|
+
get_policy_items,
|
|
24
|
+
is_clarify,
|
|
25
|
+
)
|
|
26
|
+
from context_compiler.engine import Engine
|
|
27
|
+
|
|
28
|
+
PYTHON_SCRIPT_SCHEMA: dict[str, Any] = {
|
|
29
|
+
"type": "object",
|
|
30
|
+
"properties": {
|
|
31
|
+
"python_script": {
|
|
32
|
+
"type": "string",
|
|
33
|
+
"description": "A complete Python script.",
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"required": ["python_script"],
|
|
37
|
+
"additionalProperties": False,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
SHELL_COMMAND_SCHEMA: dict[str, Any] = {
|
|
41
|
+
"type": "object",
|
|
42
|
+
"properties": {
|
|
43
|
+
"shell_command": {
|
|
44
|
+
"type": "string",
|
|
45
|
+
"description": "A single shell command.",
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"required": ["shell_command"],
|
|
49
|
+
"additionalProperties": False,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# Small, explicit mapping from policy item -> Ollama `format` schema.
|
|
53
|
+
_SCHEMA_BY_ITEM: dict[str, dict[str, Any]] = {
|
|
54
|
+
"python_script": PYTHON_SCRIPT_SCHEMA,
|
|
55
|
+
"shell_command": SHELL_COMMAND_SCHEMA,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class TurnPlan(TypedDict):
|
|
60
|
+
decision_kind: str
|
|
61
|
+
clarify_prompt: str | None
|
|
62
|
+
selected_schema_item: str | None
|
|
63
|
+
format_schema: dict[str, Any] | None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def select_ollama_format_schema(
|
|
67
|
+
state: State,
|
|
68
|
+
) -> tuple[str | None, dict[str, Any] | None]:
|
|
69
|
+
"""Return (policy_item, schema) or (None, None) when no safe match exists.
|
|
70
|
+
|
|
71
|
+
Unknown/insufficient policy state intentionally selects no schema.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
use_items = set(get_policy_items(state, POLICY_USE))
|
|
75
|
+
prohibit_items = set(get_policy_items(state, POLICY_PROHIBIT))
|
|
76
|
+
|
|
77
|
+
for item, schema in _SCHEMA_BY_ITEM.items():
|
|
78
|
+
if item in use_items and item not in prohibit_items:
|
|
79
|
+
return item, schema
|
|
80
|
+
|
|
81
|
+
return None, None
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def plan_turn(user_input: str, engine: Engine) -> TurnPlan:
|
|
85
|
+
"""Run compiler step and decide whether to request Ollama structured output."""
|
|
86
|
+
|
|
87
|
+
decision = engine.step(user_input)
|
|
88
|
+
if is_clarify(decision):
|
|
89
|
+
return {
|
|
90
|
+
"decision_kind": "clarify",
|
|
91
|
+
"clarify_prompt": get_clarify_prompt(decision),
|
|
92
|
+
"selected_schema_item": None,
|
|
93
|
+
"format_schema": None,
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
decision_state = get_decision_state(decision)
|
|
97
|
+
compiled_state = decision_state if decision_state is not None else engine.state
|
|
98
|
+
selected_item, format_schema = select_ollama_format_schema(compiled_state)
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
"decision_kind": str(decision["kind"]),
|
|
102
|
+
"clarify_prompt": None,
|
|
103
|
+
"selected_schema_item": selected_item,
|
|
104
|
+
"format_schema": format_schema,
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def optional_ollama_call(
|
|
109
|
+
*,
|
|
110
|
+
user_input: str,
|
|
111
|
+
model: str,
|
|
112
|
+
format_schema: Mapping[str, Any] | None,
|
|
113
|
+
host: str | None = None,
|
|
114
|
+
) -> dict[str, Any]:
|
|
115
|
+
"""Optional smoke call to Ollama's /api/chat.
|
|
116
|
+
|
|
117
|
+
If `format_schema` is provided, it is passed through `format` exactly.
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
base_url = host or os.getenv("OLLAMA_BASE_URL") or "http://localhost:11434"
|
|
121
|
+
payload: dict[str, Any] = {
|
|
122
|
+
"model": model,
|
|
123
|
+
"messages": [{"role": "user", "content": user_input}],
|
|
124
|
+
"stream": False,
|
|
125
|
+
}
|
|
126
|
+
if format_schema is not None:
|
|
127
|
+
payload["format"] = dict(format_schema)
|
|
128
|
+
|
|
129
|
+
request = urllib.request.Request(
|
|
130
|
+
url=f"{base_url.rstrip('/')}/api/chat",
|
|
131
|
+
data=json.dumps(payload).encode("utf-8"),
|
|
132
|
+
headers={"Content-Type": "application/json"},
|
|
133
|
+
method="POST",
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
with urllib.request.urlopen(request, timeout=30) as response:
|
|
138
|
+
raw = response.read().decode("utf-8")
|
|
139
|
+
except urllib.error.URLError as exc:
|
|
140
|
+
raise RuntimeError(f"Ollama call failed: {exc}") from exc
|
|
141
|
+
|
|
142
|
+
decoded = cast(object, json.loads(raw))
|
|
143
|
+
if not isinstance(decoded, dict):
|
|
144
|
+
raise RuntimeError("Ollama response must be a JSON object")
|
|
145
|
+
return cast(dict[str, Any], decoded)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def main() -> None:
|
|
149
|
+
engine = create_engine()
|
|
150
|
+
|
|
151
|
+
# Demonstration setup.
|
|
152
|
+
engine.step("use python_script")
|
|
153
|
+
engine.step("prohibit shell_command")
|
|
154
|
+
|
|
155
|
+
plan = plan_turn("Write a helper script to parse CSV files.", engine)
|
|
156
|
+
print("decision_kind:", plan["decision_kind"])
|
|
157
|
+
print("selected_schema_item:", plan["selected_schema_item"])
|
|
158
|
+
print("format_schema_selected:", plan["format_schema"] is not None)
|
|
159
|
+
|
|
160
|
+
# Optional model execution path; disabled by default.
|
|
161
|
+
if os.getenv("RUN_OLLAMA_SMOKE") == "1":
|
|
162
|
+
response = optional_ollama_call(
|
|
163
|
+
user_input="Write a helper script to parse CSV files.",
|
|
164
|
+
model=os.getenv("OLLAMA_MODEL", "llama3.1"),
|
|
165
|
+
format_schema=plan["format_schema"],
|
|
166
|
+
)
|
|
167
|
+
print("ollama_response_keys:", sorted(response.keys()))
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
if __name__ == "__main__":
|
|
171
|
+
main()
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Refund intake
|
|
2
|
+
|
|
3
|
+
Authoritative state changes which intake workflow the host selects for the same
|
|
4
|
+
ambiguous order request. This example shows two host-side schema-selection
|
|
5
|
+
mechanisms in a customer order/support intake flow.
|
|
6
|
+
|
|
7
|
+
The enforcement point stays the same:
|
|
8
|
+
|
|
9
|
+
- same user request
|
|
10
|
+
- different authoritative state
|
|
11
|
+
- different host-selected schema
|
|
12
|
+
|
|
13
|
+
Context Compiler does not choose the workflow by prompt wording. The host reads
|
|
14
|
+
the saved authoritative state and selects the workflow/schema from that state.
|
|
15
|
+
For premise-driven selection, the host applies a small deterministic
|
|
16
|
+
order-intake rule:
|
|
17
|
+
|
|
18
|
+
`saved order facts -> intake category -> selected schema`
|
|
19
|
+
|
|
20
|
+
In this example, the host checks for a few explicit order facts in the saved
|
|
21
|
+
premise, such as `delivered physical item` plus `damaged on arrival`, or
|
|
22
|
+
`digital subscription` plus `login failure`.
|
|
23
|
+
|
|
24
|
+
## Enforcement point
|
|
25
|
+
|
|
26
|
+
Schema selection
|
|
27
|
+
|
|
28
|
+
## State mechanisms
|
|
29
|
+
|
|
30
|
+
- policy-driven schema selection via `use ...`
|
|
31
|
+
- premise-driven schema selection via factual order context
|
|
32
|
+
|
|
33
|
+
## What changes
|
|
34
|
+
|
|
35
|
+
Without matching authoritative state, no workflow is selected.
|
|
36
|
+
|
|
37
|
+
With policy state:
|
|
38
|
+
|
|
39
|
+
```text
|
|
40
|
+
use refund_intake
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
the refund workflow runs.
|
|
44
|
+
|
|
45
|
+
With a factual premise:
|
|
46
|
+
|
|
47
|
+
```text
|
|
48
|
+
set premise order A-100 is a delivered physical item reported as damaged on arrival
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
the same ambiguous user request, `I need help with order A-100.`, selects the
|
|
52
|
+
`refund_intake` schema.
|
|
53
|
+
|
|
54
|
+
The host maps that saved premise to the `damaged_physical_delivery` intake
|
|
55
|
+
category, which then maps to `refund_intake`.
|
|
56
|
+
|
|
57
|
+
With a different factual premise:
|
|
58
|
+
|
|
59
|
+
```text
|
|
60
|
+
set premise order A-100 is a digital subscription with an active login failure after purchase
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
the same ambiguous user request selects the `technical_support` schema.
|
|
64
|
+
|
|
65
|
+
The host maps that saved premise to the
|
|
66
|
+
`digital_subscription_login_failure` intake category, which then maps to
|
|
67
|
+
`technical_support`.
|
|
68
|
+
|
|
69
|
+
The premise is factual context about the order. It is not a workflow command
|
|
70
|
+
and it is not rewritten as `use refund_intake` or `use technical_support`.
|
|
71
|
+
This mapping is host-owned business logic, not model inference.
|
|
72
|
+
|
|
73
|
+
## Proof
|
|
74
|
+
|
|
75
|
+
The tests verify:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
assert refund_handler.called is True
|
|
79
|
+
assert technical_support_handler.called is False
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
They also verify:
|
|
83
|
+
|
|
84
|
+
- `use refund_intake` selects `refund_intake`
|
|
85
|
+
- `use technical_support` selects `technical_support`
|
|
86
|
+
- saved premise facts map to a named intake category before schema selection
|
|
87
|
+
- the same request plus different saved premise selects different schemas
|
|
88
|
+
- unrelated premise selects no schema
|
|
89
|
+
- adversarial user text does not override saved policy or saved premise
|
|
90
|
+
|
|
91
|
+
## Test
|
|
92
|
+
|
|
93
|
+
```shell
|
|
94
|
+
uv run pytest python/tests/test_refund_intake_example.py
|
|
95
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Refund intake schema-selection example."""
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""Minimal host-side schema selection for refund intake."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Literal, TypedDict
|
|
5
|
+
|
|
6
|
+
from context_compiler import (
|
|
7
|
+
POLICY_USE,
|
|
8
|
+
State,
|
|
9
|
+
create_engine,
|
|
10
|
+
get_policy_items,
|
|
11
|
+
get_premise_value,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
DAMAGED_ORDER_PREMISE = (
|
|
15
|
+
"order A-100 is a delivered physical item reported as damaged on arrival"
|
|
16
|
+
)
|
|
17
|
+
DIGITAL_LOGIN_FAILURE_PREMISE = (
|
|
18
|
+
"order A-100 is a digital subscription with an active login failure after purchase"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class IntakeRequest(TypedDict):
|
|
23
|
+
customer_id: str
|
|
24
|
+
message: str
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class RefundIntakeResult(TypedDict):
|
|
28
|
+
kind: Literal["refund"]
|
|
29
|
+
customer_id: str
|
|
30
|
+
reason: str
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class TechnicalSupportResult(TypedDict):
|
|
34
|
+
kind: Literal["technical_support"]
|
|
35
|
+
customer_id: str
|
|
36
|
+
issue: str
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
OrderIntakeContext = Literal[
|
|
40
|
+
"damaged_physical_delivery", "digital_subscription_login_failure"
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class IntakeRunResult(TypedDict):
|
|
45
|
+
selected_schema: str | None
|
|
46
|
+
refund_handler_called: bool
|
|
47
|
+
technical_support_handler_called: bool
|
|
48
|
+
result: RefundIntakeResult | TechnicalSupportResult | None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
_SCHEMA_BY_ORDER_INTAKE_CONTEXT: dict[OrderIntakeContext, str] = {
|
|
52
|
+
"damaged_physical_delivery": "refund_intake",
|
|
53
|
+
"digital_subscription_login_failure": "technical_support",
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class IntakeHandler:
|
|
59
|
+
name: str
|
|
60
|
+
called: bool = False
|
|
61
|
+
|
|
62
|
+
def handle(
|
|
63
|
+
self, request: IntakeRequest
|
|
64
|
+
) -> RefundIntakeResult | TechnicalSupportResult:
|
|
65
|
+
self.called = True
|
|
66
|
+
|
|
67
|
+
if self.name == "refund_intake":
|
|
68
|
+
return {
|
|
69
|
+
"kind": "refund",
|
|
70
|
+
"customer_id": request["customer_id"],
|
|
71
|
+
"reason": request["message"],
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if self.name == "technical_support":
|
|
75
|
+
return {
|
|
76
|
+
"kind": "technical_support",
|
|
77
|
+
"customer_id": request["customer_id"],
|
|
78
|
+
"issue": request["message"],
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
raise ValueError(f"unknown handler: {self.name}")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def classify_premise_as_order_intake_context(
|
|
85
|
+
premise: str | None,
|
|
86
|
+
) -> OrderIntakeContext | None:
|
|
87
|
+
"""Map saved order facts to a host-owned intake context."""
|
|
88
|
+
|
|
89
|
+
if premise is None:
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
normalized_premise = premise.casefold()
|
|
93
|
+
if (
|
|
94
|
+
"delivered physical item" in normalized_premise
|
|
95
|
+
and "damaged on arrival" in normalized_premise
|
|
96
|
+
):
|
|
97
|
+
return "damaged_physical_delivery"
|
|
98
|
+
|
|
99
|
+
if (
|
|
100
|
+
"digital subscription" in normalized_premise
|
|
101
|
+
and "login failure" in normalized_premise
|
|
102
|
+
):
|
|
103
|
+
return "digital_subscription_login_failure"
|
|
104
|
+
|
|
105
|
+
return None
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def select_schema_from_order_intake_context(
|
|
109
|
+
context: OrderIntakeContext | None,
|
|
110
|
+
) -> str | None:
|
|
111
|
+
"""Map a host-owned intake context to the selected schema."""
|
|
112
|
+
|
|
113
|
+
if context is None:
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
return _SCHEMA_BY_ORDER_INTAKE_CONTEXT[context]
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def select_schema_from_state(state: State) -> str | None:
|
|
120
|
+
"""Select a host-side workflow from authoritative state."""
|
|
121
|
+
|
|
122
|
+
use_items = set(get_policy_items(state, POLICY_USE))
|
|
123
|
+
premise = get_premise_value(state)
|
|
124
|
+
|
|
125
|
+
if "refund_intake" in use_items:
|
|
126
|
+
return "refund_intake"
|
|
127
|
+
|
|
128
|
+
if "technical_support" in use_items:
|
|
129
|
+
return "technical_support"
|
|
130
|
+
|
|
131
|
+
intake_context = classify_premise_as_order_intake_context(premise)
|
|
132
|
+
return select_schema_from_order_intake_context(intake_context)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def run_intake(
|
|
136
|
+
request: IntakeRequest,
|
|
137
|
+
*,
|
|
138
|
+
selected_schema: str | None,
|
|
139
|
+
refund_handler: IntakeHandler,
|
|
140
|
+
technical_support_handler: IntakeHandler,
|
|
141
|
+
) -> RefundIntakeResult | TechnicalSupportResult | None:
|
|
142
|
+
"""Dispatch to the selected host-side handler, if any."""
|
|
143
|
+
|
|
144
|
+
if selected_schema == "refund_intake":
|
|
145
|
+
return refund_handler.handle(request)
|
|
146
|
+
|
|
147
|
+
if selected_schema == "technical_support":
|
|
148
|
+
return technical_support_handler.handle(request)
|
|
149
|
+
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def run_demo() -> IntakeRunResult:
|
|
154
|
+
"""Run a small demonstration with refund_intake enabled."""
|
|
155
|
+
|
|
156
|
+
engine = create_engine()
|
|
157
|
+
engine.step("use refund_intake")
|
|
158
|
+
|
|
159
|
+
request: IntakeRequest = {
|
|
160
|
+
"customer_id": "customer-123",
|
|
161
|
+
"message": "I need a refund for order A-100.",
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
refund_handler = IntakeHandler("refund_intake")
|
|
165
|
+
technical_support_handler = IntakeHandler("technical_support")
|
|
166
|
+
|
|
167
|
+
selected_schema = select_schema_from_state(engine.state)
|
|
168
|
+
result = run_intake(
|
|
169
|
+
request,
|
|
170
|
+
selected_schema=selected_schema,
|
|
171
|
+
refund_handler=refund_handler,
|
|
172
|
+
technical_support_handler=technical_support_handler,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
"selected_schema": selected_schema,
|
|
177
|
+
"refund_handler_called": refund_handler.called,
|
|
178
|
+
"technical_support_handler_called": technical_support_handler.called,
|
|
179
|
+
"result": result,
|
|
180
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Tool gating
|
|
2
|
+
|
|
3
|
+
These examples show the host deciding which tools exist at runtime and which
|
|
4
|
+
tool calls can execute.
|
|
5
|
+
|
|
6
|
+
Context Compiler owns the authoritative policy state.
|
|
7
|
+
|
|
8
|
+
The host owns the tool registry and tool execution.
|
|
9
|
+
|
|
10
|
+
Adversarial wording does not expose hidden tools or authorize blocked tools.
|
|
11
|
+
|
|
12
|
+
## Examples
|
|
13
|
+
|
|
14
|
+
### `calendar_admin`
|
|
15
|
+
|
|
16
|
+
Exposes a host-owned `calendar_admin_create_event` tool only when state
|
|
17
|
+
contains:
|
|
18
|
+
|
|
19
|
+
```text
|
|
20
|
+
use calendar_admin
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The host hides and blocks the tool when state is absent or when state contains:
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
prohibit calendar_admin
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The tests cover visible-tool changes, execution blocking, adversarial text, and
|
|
30
|
+
contradiction / clarification behavior.
|
|
31
|
+
|
|
32
|
+
### `mcp_calendar_admin`
|
|
33
|
+
|
|
34
|
+
Uses MCP as the integration surface while keeping Context Compiler as the
|
|
35
|
+
authority source.
|
|
36
|
+
|
|
37
|
+
The host exposes the `calendar_admin_create_event` MCP tool only when state
|
|
38
|
+
contains:
|
|
39
|
+
|
|
40
|
+
```text
|
|
41
|
+
use calendar_admin
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
When state is absent or contains:
|
|
45
|
+
|
|
46
|
+
```text
|
|
47
|
+
prohibit calendar_admin
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
the MCP tool is absent from the exposed tool set and blocked if invoked
|
|
51
|
+
directly.
|
|
52
|
+
|
|
53
|
+
This example also includes an opt-in live-model comparison showing that a real
|
|
54
|
+
tool-calling model can only act through the MCP tool surface the host exposes
|
|
55
|
+
from authoritative Context Compiler state.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# `calendar_admin`
|
|
2
|
+
|
|
3
|
+
This example shows host-side tool gating with explicit authoritative Context
|
|
4
|
+
Compiler state.
|
|
5
|
+
|
|
6
|
+
The host owns the tool registry and tool execution.
|
|
7
|
+
|
|
8
|
+
Context Compiler owns the policy state that decides whether the host exposes
|
|
9
|
+
`calendar_admin_create_event`.
|
|
10
|
+
|
|
11
|
+
The host exposes and allows the calendar admin tool only when state contains:
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
use calendar_admin
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The host hides and blocks the calendar admin tool when state is absent or when
|
|
18
|
+
state contains:
|
|
19
|
+
|
|
20
|
+
```text
|
|
21
|
+
prohibit calendar_admin
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Adversarial request text does not enable the tool because the host never derives
|
|
25
|
+
authority from user wording or model output.
|
|
26
|
+
|
|
27
|
+
The tests cover:
|
|
28
|
+
|
|
29
|
+
- allowed exposure and execution
|
|
30
|
+
- absent-state hiding and blocking
|
|
31
|
+
- prohibited-state hiding and blocking
|
|
32
|
+
- adversarial text that tries to self-authorize
|
|
33
|
+
- runtime behavior changing only when authoritative state changes
|
|
34
|
+
- contradiction and clarification behavior for conflicting `use` and `prohibit`
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Calendar admin tool-gating example."""
|