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.
Files changed (65) hide show
  1. context_compiler_example_integrations/README.md +71 -0
  2. context_compiler_example_integrations/__init__.py +1 -0
  3. context_compiler_example_integrations/examples/__init__.py +12 -0
  4. context_compiler_example_integrations/examples/_shared/__init__.py +1 -0
  5. context_compiler_example_integrations/examples/_shared/litellm_request.py +61 -0
  6. context_compiler_example_integrations/examples/_shared/provider_mode.py +91 -0
  7. context_compiler_example_integrations/examples/checkpoint_continuation/README.md +77 -0
  8. context_compiler_example_integrations/examples/checkpoint_continuation/example.py +169 -0
  9. context_compiler_example_integrations/examples/checkpoint_continuation/fastapi/README.md +85 -0
  10. context_compiler_example_integrations/examples/checkpoint_continuation/fastapi/app.py +205 -0
  11. context_compiler_example_integrations/examples/execution_authorization/README.md +31 -0
  12. context_compiler_example_integrations/examples/execution_authorization/expense_approval/README.md +77 -0
  13. context_compiler_example_integrations/examples/execution_authorization/expense_approval/__init__.py +1 -0
  14. context_compiler_example_integrations/examples/execution_authorization/expense_approval/example.py +168 -0
  15. context_compiler_example_integrations/examples/execution_authorization/expense_approval/fastapi/README.md +97 -0
  16. context_compiler_example_integrations/examples/execution_authorization/expense_approval/fastapi/__init__.py +1 -0
  17. context_compiler_example_integrations/examples/execution_authorization/expense_approval/fastapi/app.py +312 -0
  18. context_compiler_example_integrations/examples/execution_authorization/expense_approval/fastapi/model_approval.py +98 -0
  19. context_compiler_example_integrations/examples/gateway_middleware/README.md +49 -0
  20. context_compiler_example_integrations/examples/gateway_middleware/customer_support_routing/README.md +40 -0
  21. context_compiler_example_integrations/examples/gateway_middleware/customer_support_routing/__init__.py +1 -0
  22. context_compiler_example_integrations/examples/gateway_middleware/customer_support_routing/example.py +220 -0
  23. context_compiler_example_integrations/examples/prompt_construction/README.md +27 -0
  24. context_compiler_example_integrations/examples/prompt_construction/litellm/README.md +197 -0
  25. context_compiler_example_integrations/examples/prompt_construction/litellm/basic.py +392 -0
  26. context_compiler_example_integrations/examples/prompt_construction/litellm/confirmation_helper.py +91 -0
  27. context_compiler_example_integrations/examples/prompt_construction/litellm/with_directive_drafter.py +524 -0
  28. context_compiler_example_integrations/examples/prompt_construction/writing_assistant/README.md +92 -0
  29. context_compiler_example_integrations/examples/prompt_construction/writing_assistant/__init__.py +1 -0
  30. context_compiler_example_integrations/examples/prompt_construction/writing_assistant/example.py +205 -0
  31. context_compiler_example_integrations/examples/retrieval_filtering/README.md +65 -0
  32. context_compiler_example_integrations/examples/retrieval_filtering/chromadb_hr_policy_lookup/README.md +120 -0
  33. context_compiler_example_integrations/examples/retrieval_filtering/chromadb_hr_policy_lookup/__init__.py +1 -0
  34. context_compiler_example_integrations/examples/retrieval_filtering/chromadb_hr_policy_lookup/example.py +320 -0
  35. context_compiler_example_integrations/examples/retrieval_filtering/hr_policy_lookup/README.md +123 -0
  36. context_compiler_example_integrations/examples/retrieval_filtering/hr_policy_lookup/__init__.py +1 -0
  37. context_compiler_example_integrations/examples/retrieval_filtering/hr_policy_lookup/example.py +300 -0
  38. context_compiler_example_integrations/examples/schema_selection/README.md +41 -0
  39. context_compiler_example_integrations/examples/schema_selection/litellm_response_format/response_format.py +218 -0
  40. context_compiler_example_integrations/examples/schema_selection/ollama_structured_output/README.md +60 -0
  41. context_compiler_example_integrations/examples/schema_selection/ollama_structured_output/example.py +171 -0
  42. context_compiler_example_integrations/examples/schema_selection/refund_intake/README.md +95 -0
  43. context_compiler_example_integrations/examples/schema_selection/refund_intake/__init__.py +1 -0
  44. context_compiler_example_integrations/examples/schema_selection/refund_intake/example.py +180 -0
  45. context_compiler_example_integrations/examples/tool_gating/README.md +55 -0
  46. context_compiler_example_integrations/examples/tool_gating/calendar_admin/README.md +34 -0
  47. context_compiler_example_integrations/examples/tool_gating/calendar_admin/__init__.py +1 -0
  48. context_compiler_example_integrations/examples/tool_gating/calendar_admin/example.py +195 -0
  49. context_compiler_example_integrations/examples/tool_gating/mcp_calendar_admin/README.md +93 -0
  50. context_compiler_example_integrations/examples/tool_gating/mcp_calendar_admin/__init__.py +1 -0
  51. context_compiler_example_integrations/examples/tool_gating/mcp_calendar_admin/example.py +250 -0
  52. context_compiler_example_integrations/examples/tool_gating/mcp_calendar_admin/live_model.py +369 -0
  53. context_compiler_example_integrations/reference_integrations/__init__.py +12 -0
  54. context_compiler_example_integrations/reference_integrations/litellm_proxy/README.md +251 -0
  55. context_compiler_example_integrations/reference_integrations/litellm_proxy/_checkpoint_support.py +146 -0
  56. context_compiler_example_integrations/reference_integrations/litellm_proxy/config.example.yaml +23 -0
  57. context_compiler_example_integrations/reference_integrations/litellm_proxy/context_compiler_precall_hook.py +163 -0
  58. context_compiler_example_integrations/reference_integrations/litellm_proxy/context_compiler_precall_hook_with_directive_drafter.py +289 -0
  59. context_compiler_example_integrations/reference_integrations/openwebui_pipe/README.md +233 -0
  60. context_compiler_example_integrations/reference_integrations/openwebui_pipe/open_webui_pipe.py +750 -0
  61. context_compiler_example_integrations/reference_integrations/openwebui_pipe/open_webui_pipe_with_directive_drafter.py +1045 -0
  62. context_compiler_example_integrations-0.1.0.dist-info/METADATA +110 -0
  63. context_compiler_example_integrations-0.1.0.dist-info/RECORD +65 -0
  64. context_compiler_example_integrations-0.1.0.dist-info/WHEEL +4 -0
  65. context_compiler_example_integrations-0.1.0.dist-info/licenses/LICENSE +173 -0
@@ -0,0 +1,369 @@
1
+ """Opt-in live-model tool-gating comparison for MCP calendar admin."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from collections.abc import Callable, Mapping
7
+ from dataclasses import dataclass
8
+ from importlib import import_module
9
+ from pathlib import Path
10
+ from typing import Literal, TypedDict, cast
11
+
12
+ from context_compiler import State, create_engine, get_decision_state, is_clarify
13
+
14
+ from context_compiler_example_integrations.examples._shared.litellm_request import (
15
+ build_litellm_provider_kwargs,
16
+ )
17
+ from context_compiler_example_integrations.examples.tool_gating.mcp_calendar_admin.example import (
18
+ CalendarAdminMcpHost,
19
+ McpToolCall,
20
+ )
21
+ from context_compiler_example_integrations.examples._shared.provider_mode import (
22
+ print_startup_config,
23
+ resolve_provider_config,
24
+ )
25
+
26
+
27
+ class SideEffectRecord(TypedDict):
28
+ tool_name: str
29
+ calendar_id: str
30
+ event_title: str
31
+ authorization_source: Literal["context_compiler_state"]
32
+
33
+
34
+ class LiveModelResult(TypedDict):
35
+ decision_kind: Literal["clarify", "update", "passthrough"] | None
36
+ prompt_to_user: str | None
37
+ exposed_tool_names: list[str]
38
+ hidden_tool_names: list[str]
39
+ protected_tool_exposed: bool
40
+ model_selected_tool_name: str | None
41
+ executed: bool
42
+ blocked_reason: str | None
43
+ tool_result: str | None
44
+ execution_log: list[str]
45
+ side_effect_path: str
46
+ side_effect_count: int
47
+
48
+
49
+ class _LiteLLMCallKwargs(TypedDict, total=False):
50
+ model: str
51
+ messages: list[dict[str, str]]
52
+ tools: list[dict[str, object]]
53
+ tool_choice: str
54
+ temperature: float
55
+ drop_params: bool
56
+ api_base: str
57
+ api_key: str
58
+
59
+
60
+ @dataclass(frozen=True)
61
+ class _SelectedToolCall:
62
+ name: str | None
63
+ arguments: dict[str, str]
64
+
65
+
66
+ @dataclass
67
+ class CalendarAdminSideEffectStore:
68
+ """Host-owned append-only artifact for successful protected execution."""
69
+
70
+ artifact_path: Path
71
+
72
+ def append(self, *, tool_call: McpToolCall) -> None:
73
+ record: SideEffectRecord = {
74
+ "tool_name": tool_call["tool_name"],
75
+ "calendar_id": tool_call["arguments"]["calendar_id"],
76
+ "event_title": tool_call["arguments"]["event_title"],
77
+ "authorization_source": "context_compiler_state",
78
+ }
79
+ self.artifact_path.parent.mkdir(parents=True, exist_ok=True)
80
+ with self.artifact_path.open("a", encoding="utf-8") as artifact:
81
+ artifact.write(json.dumps(record, sort_keys=True) + "\n")
82
+
83
+ def count(self) -> int:
84
+ if not self.artifact_path.exists():
85
+ return 0
86
+ with self.artifact_path.open(encoding="utf-8") as artifact:
87
+ return sum(1 for _ in artifact)
88
+
89
+
90
+ def _get_litellm_completion() -> Callable[..., object]:
91
+ litellm_module = import_module("litellm")
92
+ return cast(Callable[..., object], litellm_module.completion)
93
+
94
+
95
+ def _state_for_request(authoritative_state: State | None) -> State | None:
96
+ return authoritative_state
97
+
98
+
99
+ def _decision_kind_name(
100
+ decision: object,
101
+ ) -> Literal["clarify", "update", "passthrough"]:
102
+ if not isinstance(decision, dict):
103
+ raise ValueError("unexpected decision shape")
104
+
105
+ kind = decision.get("kind")
106
+ kind_name = getattr(kind, "value", None)
107
+ if kind_name not in {"clarify", "update", "passthrough"}:
108
+ raise ValueError(f"unexpected decision kind: {kind_name}")
109
+
110
+ return kind_name
111
+
112
+
113
+ def _exposed_tool_names(
114
+ host: CalendarAdminMcpHost, state: State
115
+ ) -> tuple[list[str], list[str]]:
116
+ exposed_tools = host.exposed_mcp_tools(state)
117
+ return (
118
+ [tool["name"] for tool in exposed_tools["tools"]],
119
+ exposed_tools["hidden_tool_names"],
120
+ )
121
+
122
+
123
+ def _build_openai_tools(
124
+ host: CalendarAdminMcpHost, state: State
125
+ ) -> list[dict[str, object]]:
126
+ exposed_tools = host.exposed_mcp_tools(state)["tools"]
127
+ tools: list[dict[str, object]] = []
128
+ for tool in exposed_tools:
129
+ if tool["name"] == "calendar_view_events":
130
+ input_schema: dict[str, object] = {
131
+ "type": "object",
132
+ "properties": {
133
+ "calendar_id": {
134
+ "type": "string",
135
+ "description": "Calendar identifier to inspect.",
136
+ }
137
+ },
138
+ "required": ["calendar_id"],
139
+ "additionalProperties": False,
140
+ }
141
+ elif tool["name"] == "calendar_admin_create_event":
142
+ input_schema = {
143
+ "type": "object",
144
+ "properties": {
145
+ "calendar_id": {
146
+ "type": "string",
147
+ "description": "Administrative calendar identifier.",
148
+ },
149
+ "event_title": {
150
+ "type": "string",
151
+ "description": "Administrative event title to create.",
152
+ },
153
+ },
154
+ "required": ["calendar_id", "event_title"],
155
+ "additionalProperties": False,
156
+ }
157
+ else:
158
+ continue
159
+
160
+ tools.append(
161
+ {
162
+ "type": "function",
163
+ "function": {
164
+ "name": tool["name"],
165
+ "description": tool["description"],
166
+ "parameters": input_schema,
167
+ },
168
+ }
169
+ )
170
+ return tools
171
+
172
+
173
+ def _build_messages(user_intent: str) -> list[dict[str, str]]:
174
+ return [
175
+ {
176
+ "role": "system",
177
+ "content": (
178
+ "You are a careful assistant.\n"
179
+ "Use a tool if one is available to complete the user's request.\n"
180
+ "Do not invent unavailable tools."
181
+ ),
182
+ },
183
+ {"role": "user", "content": user_intent},
184
+ ]
185
+
186
+
187
+ def _mapping_value(obj: object, key: str) -> object | None:
188
+ if isinstance(obj, Mapping):
189
+ return obj.get(key)
190
+ return getattr(obj, key, None)
191
+
192
+
193
+ def _extract_selected_tool_call(response: object) -> _SelectedToolCall:
194
+ choices = _mapping_value(response, "choices")
195
+ if not isinstance(choices, list) or not choices:
196
+ choices = getattr(response, "choices", None)
197
+ if not isinstance(choices, list) or not choices:
198
+ return _SelectedToolCall(name=None, arguments={})
199
+
200
+ message = _mapping_value(choices[0], "message")
201
+ if message is None:
202
+ return _SelectedToolCall(name=None, arguments={})
203
+
204
+ tool_calls = _mapping_value(message, "tool_calls")
205
+ if not isinstance(tool_calls, list) or not tool_calls:
206
+ return _SelectedToolCall(name=None, arguments={})
207
+
208
+ first_tool_call = tool_calls[0]
209
+ function_payload = _mapping_value(first_tool_call, "function")
210
+ if function_payload is None:
211
+ return _SelectedToolCall(name=None, arguments={})
212
+
213
+ name = _mapping_value(function_payload, "name")
214
+ raw_arguments = _mapping_value(function_payload, "arguments")
215
+ if not isinstance(name, str):
216
+ return _SelectedToolCall(name=None, arguments={})
217
+
218
+ if isinstance(raw_arguments, str):
219
+ decoded_arguments = json.loads(raw_arguments)
220
+ elif isinstance(raw_arguments, Mapping):
221
+ decoded_arguments = dict(raw_arguments)
222
+ else:
223
+ decoded_arguments = {}
224
+
225
+ arguments = {
226
+ key: str(value)
227
+ for key, value in decoded_arguments.items()
228
+ if isinstance(key, str)
229
+ }
230
+ return _SelectedToolCall(name=name, arguments=arguments)
231
+
232
+
233
+ def _call_live_model(
234
+ *, user_intent: str, tools: list[dict[str, object]]
235
+ ) -> _SelectedToolCall:
236
+ try:
237
+ completion = _get_litellm_completion()
238
+ except ModuleNotFoundError as exc:
239
+ raise RuntimeError(
240
+ "litellm is required. Install with: pip install litellm"
241
+ ) from exc
242
+
243
+ config = resolve_provider_config(default_model="openai/gpt-4o-mini")
244
+ print_startup_config(config)
245
+
246
+ kwargs: _LiteLLMCallKwargs = {
247
+ **build_litellm_provider_kwargs(config),
248
+ "messages": _build_messages(user_intent),
249
+ "tools": tools,
250
+ "tool_choice": "auto",
251
+ }
252
+
253
+ response = completion(**kwargs)
254
+ return _extract_selected_tool_call(response)
255
+
256
+
257
+ def run_live_model_turn(
258
+ *,
259
+ user_intent: str,
260
+ authoritative_state: State | None = None,
261
+ compiler_input: str = "",
262
+ artifact_path: Path | None = None,
263
+ model_tool_selector: Callable[..., _SelectedToolCall] | None = None,
264
+ ) -> LiveModelResult:
265
+ """Run one tool-gated turn with a live model or injected selector."""
266
+
267
+ if artifact_path is None:
268
+ artifact_path = Path(
269
+ "/tmp/context_compiler_mcp_calendar_admin/tool_calls.jsonl"
270
+ )
271
+
272
+ side_effect_store = CalendarAdminSideEffectStore(artifact_path=artifact_path)
273
+ host = CalendarAdminMcpHost()
274
+ engine = create_engine(state=_state_for_request(authoritative_state))
275
+ decision_kind: Literal["clarify", "update", "passthrough"] | None = None
276
+ prompt_to_user: str | None = None
277
+ effective_state = engine.state
278
+
279
+ if compiler_input:
280
+ decision = engine.step(compiler_input)
281
+ decision_kind = _decision_kind_name(decision)
282
+ prompt_to_user = decision.get("prompt_to_user")
283
+ if is_clarify(decision):
284
+ exposed_tool_names, hidden_tool_names = _exposed_tool_names(
285
+ host, engine.state
286
+ )
287
+ return {
288
+ "decision_kind": decision_kind,
289
+ "prompt_to_user": prompt_to_user,
290
+ "exposed_tool_names": exposed_tool_names,
291
+ "hidden_tool_names": hidden_tool_names,
292
+ "protected_tool_exposed": "calendar_admin_create_event"
293
+ in exposed_tool_names,
294
+ "model_selected_tool_name": None,
295
+ "executed": False,
296
+ "blocked_reason": (
297
+ "clarification required before exposing calendar admin MCP tools"
298
+ ),
299
+ "tool_result": None,
300
+ "execution_log": host.execution_log.copy(),
301
+ "side_effect_path": str(side_effect_store.artifact_path),
302
+ "side_effect_count": side_effect_store.count(),
303
+ }
304
+
305
+ decision_state = get_decision_state(decision)
306
+ effective_state = decision_state if decision_state is not None else engine.state
307
+
308
+ exposed_tool_names, hidden_tool_names = _exposed_tool_names(host, effective_state)
309
+ protected_tool_exposed = "calendar_admin_create_event" in exposed_tool_names
310
+ tools = _build_openai_tools(host, effective_state)
311
+ selector = model_tool_selector or _call_live_model
312
+ selected_tool_call = selector(user_intent=user_intent, tools=tools)
313
+
314
+ if selected_tool_call.name != "calendar_admin_create_event":
315
+ return {
316
+ "decision_kind": decision_kind,
317
+ "prompt_to_user": prompt_to_user,
318
+ "exposed_tool_names": exposed_tool_names,
319
+ "hidden_tool_names": hidden_tool_names,
320
+ "protected_tool_exposed": protected_tool_exposed,
321
+ "model_selected_tool_name": selected_tool_call.name,
322
+ "executed": False,
323
+ "blocked_reason": (
324
+ "model did not select protected admin tool"
325
+ if protected_tool_exposed
326
+ else "protected admin tool was not exposed to the model"
327
+ ),
328
+ "tool_result": None,
329
+ "execution_log": host.execution_log.copy(),
330
+ "side_effect_path": str(side_effect_store.artifact_path),
331
+ "side_effect_count": side_effect_store.count(),
332
+ }
333
+
334
+ if not protected_tool_exposed:
335
+ return {
336
+ "decision_kind": decision_kind,
337
+ "prompt_to_user": prompt_to_user,
338
+ "exposed_tool_names": exposed_tool_names,
339
+ "hidden_tool_names": hidden_tool_names,
340
+ "protected_tool_exposed": False,
341
+ "model_selected_tool_name": selected_tool_call.name,
342
+ "executed": False,
343
+ "blocked_reason": "calendar_admin state not authorized",
344
+ "tool_result": None,
345
+ "execution_log": host.execution_log.copy(),
346
+ "side_effect_path": str(side_effect_store.artifact_path),
347
+ "side_effect_count": side_effect_store.count(),
348
+ }
349
+
350
+ tool_call: McpToolCall = {
351
+ "tool_name": "calendar_admin_create_event",
352
+ "arguments": selected_tool_call.arguments,
353
+ }
354
+ tool_result = host.execute_mcp_tool(tool_call)
355
+ side_effect_store.append(tool_call=tool_call)
356
+ return {
357
+ "decision_kind": decision_kind,
358
+ "prompt_to_user": prompt_to_user,
359
+ "exposed_tool_names": exposed_tool_names,
360
+ "hidden_tool_names": hidden_tool_names,
361
+ "protected_tool_exposed": True,
362
+ "model_selected_tool_name": selected_tool_call.name,
363
+ "executed": True,
364
+ "blocked_reason": None,
365
+ "tool_result": tool_result,
366
+ "execution_log": host.execution_log.copy(),
367
+ "side_effect_path": str(side_effect_store.artifact_path),
368
+ "side_effect_count": side_effect_store.count(),
369
+ }
@@ -0,0 +1,12 @@
1
+ """Reference integrations 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" / "reference_integrations"
9
+
10
+ __path__ = [str(_PACKAGE_DIR)]
11
+ if _SOURCE_DIR.is_dir():
12
+ __path__.append(str(_SOURCE_DIR))
@@ -0,0 +1,251 @@
1
+ # LiteLLM Proxy (pre-call hook)
2
+
3
+ Saved compiler state changes whether LiteLLM Proxy blocks a request, forwards
4
+ it unchanged, or injects compiler state before the downstream call. This
5
+ example shows LiteLLM Proxy acting as the host-owned gateway surface.
6
+
7
+ Context Compiler is the authority layer for saved state. These hooks no longer
8
+ derive authority from transcript history. They process only the latest user
9
+ turn and rely on host-owned checkpoints for continuity.
10
+
11
+ Available hook files:
12
+
13
+ - Basic checkpoint-backed hook: `context_compiler_precall_hook.py`
14
+ - Directive-drafter-enabled checkpoint-backed hook:
15
+ `context_compiler_precall_hook_with_directive_drafter.py`
16
+
17
+ ## Runtime behavior
18
+
19
+ - LiteLLM Proxy is the gateway surface; Context Compiler remains the authority
20
+ layer for saved state.
21
+ - By default, the hooks run in stateless mode and process only the latest user
22
+ turn with a fresh engine.
23
+ - In explicit persistent mode, the hook resolves a session key, loads a saved
24
+ checkpoint, restores the engine, processes the latest user turn once, and
25
+ saves the resulting checkpoint after every decision, including `clarify`.
26
+ - In stateless mode, no continuity is preserved across requests.
27
+ - If result is `clarify`, the proxy does not call the downstream model and
28
+ LiteLLM surfaces the clarification as an HTTP 400 response.
29
+ - If result is `passthrough`, the proxy forwards the request normally.
30
+ - If result is `update`, the proxy injects compiler state as a system message
31
+ and then calls the model.
32
+ - Unsupported LiteLLM callback `call_type` values return the original request
33
+ data unchanged.
34
+
35
+ Optional directive-drafter behavior:
36
+
37
+ - The drafter runs only on the current/latest user turn.
38
+ - The hook restores checkpoints before drafting.
39
+ - Heuristic runs first; if no directive is found, LLM fallback is attempted.
40
+ - Forwarded upstream request messages are not rewritten except for the injected
41
+ compiler system message.
42
+
43
+ Model fallback output is structurally validated before handoff. This does not prove that the model interpreted the user correctly. The automated fallback path is experimental pending a separate source-aware acceptance policy and reviewed drafting workflow.
44
+
45
+ ## Session mode and session keys
46
+
47
+ The reference hooks support two explicit modes:
48
+
49
+ - `persistent`
50
+ - explicit mode
51
+ - requires a stable session key
52
+ - preserves saved state and pending clarification across requests
53
+ - `stateless`
54
+ - default mode
55
+ - processes only the latest user turn
56
+ - preserves no continuity
57
+
58
+ Set the mode with one of:
59
+
60
+ - top-level request field `context_compiler_mode`
61
+ - env var `CONTEXT_COMPILER_SESSION_MODE`
62
+
63
+ Persistent mode resolves session keys in this order:
64
+
65
+ 1. `context_compiler_session_key`
66
+ 2. `metadata.context_compiler_session_key`
67
+
68
+ If persistent mode cannot resolve a stable session key, the hook fails clearly
69
+ and does not fall back to transcript replay or implicit stateless behavior.
70
+
71
+ ## Checkpoint store contract
72
+
73
+ The hooks share a small repo-local support module:
74
+
75
+ ```python
76
+ class CheckpointStore(Protocol):
77
+ def load(self, session_key: str) -> Mapping[str, object] | None: ...
78
+ def save(self, session_key: str, checkpoint: Mapping[str, object]) -> None: ...
79
+ ```
80
+
81
+ The reference implementation uses an in-memory store for tests and local demos.
82
+ It is single-process only:
83
+
84
+ - no durability across restarts
85
+ - no multi-worker coordination
86
+ - no atomic compare-and-swap
87
+ - no expiration/cleanup policy
88
+
89
+ Real deployments should replace it with a host-owned store such as Redis or a
90
+ database-backed implementation.
91
+
92
+ ## Requirements
93
+
94
+ ```shell
95
+ pip install "context-compiler-example-integrations[litellm]"
96
+ export OPENAI_API_KEY=...
97
+ ```
98
+
99
+ Start with the compiler-only hook. Add `context-compiler-directive-drafter`
100
+ only if you want the optional directive-drafter variant.
101
+
102
+ For `context_compiler_precall_hook_with_directive_drafter.py`:
103
+
104
+ ```shell
105
+ pip install "context-compiler-example-integrations[all]"
106
+ ```
107
+
108
+ That variant requires `context-compiler-directive-drafter>=0.1.2`.
109
+
110
+ For the opt-in runtime smoke test, install the proxy runtime extras:
111
+
112
+ ```shell
113
+ uv sync --group proxy_runtime --no-editable
114
+ ```
115
+
116
+ ## Quickstart (copy/paste)
117
+
118
+ From the repo root:
119
+
120
+ ```shell
121
+ pip install "context-compiler-example-integrations[litellm]"
122
+ export OPENAI_API_KEY=...
123
+ litellm --config python/reference_integrations/litellm_proxy/config.example.yaml
124
+ ```
125
+
126
+ `config.example.yaml` includes both OpenAI and Ollama model definitions.
127
+ Use the Ollama model entry for local testing without API credentials.
128
+
129
+ ## Make a request
130
+
131
+ ```python
132
+ from openai import OpenAI
133
+
134
+ client = OpenAI(
135
+ api_key="anything",
136
+ base_url="http://localhost:4000",
137
+ )
138
+
139
+ response = client.chat.completions.create(
140
+ model="gpt-4o-mini",
141
+ messages=[{"role": "user", "content": "prohibit peanuts"}],
142
+ extra_body={"context_compiler_session_key": "demo-chat"},
143
+ )
144
+ ```
145
+
146
+ Or with curl:
147
+
148
+ ```shell
149
+ curl http://localhost:4000/v1/chat/completions \
150
+ -H "Content-Type: application/json" \
151
+ -H "Authorization: Bearer anything" \
152
+ -d '{
153
+ "model": "gpt-4o-mini",
154
+ "context_compiler_session_key": "demo-chat",
155
+ "messages": [{"role": "user", "content": "prohibit peanuts"}]
156
+ }'
157
+ ```
158
+
159
+ For explicit stateless mode:
160
+
161
+ ```json
162
+ {
163
+ "model": "gpt-4o-mini",
164
+ "context_compiler_mode": "stateless",
165
+ "messages": [{"role": "user", "content": "prohibit peanuts"}]
166
+ }
167
+ ```
168
+
169
+ For explicit persistent mode:
170
+
171
+ ```json
172
+ {
173
+ "model": "gpt-4o-mini",
174
+ "context_compiler_mode": "persistent",
175
+ "context_compiler_session_key": "demo-chat",
176
+ "messages": [{"role": "user", "content": "prohibit peanuts"}]
177
+ }
178
+ ```
179
+
180
+ ## Run proxy
181
+
182
+ The reference integration is covered by unit tests and an opt-in runtime smoke
183
+ test. See "Opt-in Runtime Smoke Test" below for details.
184
+
185
+ The proxy runs on `http://localhost:4000` by default. By default,
186
+ `config.example.yaml` points to the basic checkpoint-backed hook. To use the
187
+ directive-drafter variant, switch the callback path in the config. The callback
188
+ path must be importable by LiteLLM in the environment where the proxy process
189
+ starts.
190
+
191
+ When starting LiteLLM from the repo root, prefer fully qualified callback
192
+ imports in automated configs, for example:
193
+
194
+ ```text
195
+ python.reference_integrations.litellm_proxy.context_compiler_precall_hook.proxy_handler_instance
196
+ ```
197
+
198
+ Optional env vars for directive-drafter fallback:
199
+
200
+ ```shell
201
+ export PREPROCESSOR_MODEL=openai/gpt-4o-mini
202
+ export PREPROCESSOR_PROMPT_PROFILE=default
203
+ ```
204
+
205
+ `PREPROCESSOR_MODEL` is optional and defaults to `MODEL`.
206
+
207
+ For heuristic-first usage, keep `PREPROCESSOR_PROMPT_PROFILE=default`.
208
+ Use `llama` only for LLM-only fallback drafting with Llama-family models.
209
+
210
+ ## Notes
211
+
212
+ - Mixed-content user messages compile only text segments from the latest user
213
+ turn.
214
+ - `MODEL` and `PREPROCESSOR_MODEL` use LiteLLM format: `<provider>/<model>`.
215
+ - Corrupt or incompatible checkpoints fail clearly in persistent mode and do
216
+ not silently reset state.
217
+ - In the directive-drafter hook, drafter state context now comes from restored
218
+ checkpoint state rather than transcript-prefix reconstruction.
219
+ - Compound directive-shaped input such as `use docker and prohibit peanuts`
220
+ should produce a local clarify response telling the user to submit each
221
+ directive separately, without mutating saved state or forwarding upstream.
222
+
223
+ ## Troubleshooting
224
+
225
+ - callback import failures: verify the callback path configured in
226
+ `config.example.yaml` is importable in the current LiteLLM environment
227
+ - persistent mode rejects requests: provide a stable session key or opt into
228
+ explicit `stateless` mode
229
+ - proxy starts but upstream calls fail: check `OPENAI_API_KEY` and upstream
230
+ model/provider config in `config.example.yaml`
231
+ - directive-drafter fallback issues: `PREPROCESSOR_MODEL` defaults to `MODEL`;
232
+ set it explicitly only when using a separate fallback model
233
+
234
+ ## Opt-in Runtime Smoke Test
235
+
236
+ This repo includes an opt-in runtime smoke test for the LiteLLM Proxy
237
+ reference integration. The test starts a real LiteLLM Proxy process, runs the
238
+ basic hook and the directive-drafter hook in separate proxy launches, sends
239
+ local requests through the proxy with explicit session keys, verifies blocked
240
+ requests do not reach upstream, verifies allowed requests reach a local stub
241
+ upstream with the injected compiler contract, verifies the directive-drafter
242
+ path preserves the original forwarded user prompt text, and shuts each proxy
243
+ down cleanly.
244
+
245
+ It is intentionally not part of `./scripts/validate_python.sh`.
246
+
247
+ Run it from the repo root:
248
+
249
+ ```shell
250
+ RUN_LITELLM_PROXY_RUNTIME=1 uv run --group proxy_runtime pytest python/tests/test_litellm_proxy_runtime.py
251
+ ```