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,300 @@
1
+ """Minimal retrieval-filtering example for HR policy lookup."""
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Literal, TypedDict, cast
5
+
6
+ from context_compiler import (
7
+ POLICY_PROHIBIT,
8
+ POLICY_USE,
9
+ State,
10
+ create_engine,
11
+ get_decision_state,
12
+ get_policy_items,
13
+ get_premise_value,
14
+ is_clarify,
15
+ )
16
+ from context_compiler.engine import Engine
17
+
18
+ EMPLOYEE_ACCESS = "employee_hr_access"
19
+ MANAGER_ACCESS = "manager_hr_access"
20
+ LEAVE_CASE_PREMISE = "case concerns leave eligibility after a parental leave request"
21
+ GENERAL_HANDBOOK_PREMISE = (
22
+ "case concerns general employee handbook expectations for a new hire"
23
+ )
24
+ STAFFING_CASE_PREMISE = "case concerns staffing approval for a team reorganization"
25
+
26
+
27
+ class PolicyDocument(TypedDict):
28
+ document_id: str
29
+ title: str
30
+ audience: Literal["employee", "manager", "executive"]
31
+ keywords: list[str]
32
+ relevance_tags: list[str]
33
+ content: str
34
+
35
+
36
+ class RetrievalResult(TypedDict):
37
+ query: str
38
+ eligible_document_ids: list[str]
39
+ returned_document_ids: list[str]
40
+ blocked_reason: str | None
41
+
42
+
43
+ class RetrievalTurnResult(TypedDict):
44
+ decision_kind: Literal["clarify", "update", "passthrough"]
45
+ prompt_to_user: str | None
46
+ retrieval_result: RetrievalResult
47
+
48
+
49
+ CaseContext = Literal["general_handbook_case", "leave_case", "staffing_case"]
50
+
51
+
52
+ @dataclass
53
+ class HRPolicyRetriever:
54
+ """Host-owned retrieval implementation with deterministic filtering."""
55
+
56
+ documents: list[PolicyDocument] = field(default_factory=list)
57
+
58
+ def search(
59
+ self,
60
+ query: str,
61
+ *,
62
+ allowed_audiences: set[str],
63
+ case_context: CaseContext | None,
64
+ ) -> RetrievalResult:
65
+ eligible_documents = [
66
+ document
67
+ for document in self.documents
68
+ if document["audience"] in allowed_audiences
69
+ ]
70
+ relevance_filtered_documents = filter_documents_by_case_context(
71
+ eligible_documents,
72
+ case_context,
73
+ )
74
+ normalized_query_terms = set(query.lower().split())
75
+ returned_documents = [
76
+ document
77
+ for document in relevance_filtered_documents
78
+ if normalized_query_terms & set(document["keywords"])
79
+ ]
80
+
81
+ return {
82
+ "query": query,
83
+ "eligible_document_ids": [
84
+ document["document_id"] for document in eligible_documents
85
+ ],
86
+ "returned_document_ids": [
87
+ document["document_id"] for document in returned_documents
88
+ ],
89
+ "blocked_reason": None,
90
+ }
91
+
92
+
93
+ def example_documents() -> list[PolicyDocument]:
94
+ return [
95
+ {
96
+ "document_id": "employee_handbook",
97
+ "title": "Employee Handbook",
98
+ "audience": "employee",
99
+ "keywords": ["employee", "handbook", "benefits", "leave"],
100
+ "relevance_tags": ["general_handbook_case", "general_hr"],
101
+ "content": "General HR policy, leave policy, and workplace expectations.",
102
+ },
103
+ {
104
+ "document_id": "leave_of_absence_policy",
105
+ "title": "Leave of Absence Policy",
106
+ "audience": "employee",
107
+ "keywords": ["leave", "eligibility", "parental"],
108
+ "relevance_tags": ["leave_case"],
109
+ "content": "Leave eligibility, parental leave steps, and required documentation.",
110
+ },
111
+ {
112
+ "document_id": "manager_handbook",
113
+ "title": "Manager Handbook",
114
+ "audience": "manager",
115
+ "keywords": ["manager", "handbook", "approvals", "staffing"],
116
+ "relevance_tags": ["general_handbook_case", "staffing_case"],
117
+ "content": "Manager escalation guidance, staffing policy, and approvals.",
118
+ },
119
+ {
120
+ "document_id": "executive_compensation_policy",
121
+ "title": "Executive Compensation Policy",
122
+ "audience": "executive",
123
+ "keywords": ["executive", "compensation", "bonus", "board"],
124
+ "relevance_tags": ["executive_only"],
125
+ "content": "Executive compensation bands, board review, and bonus structure.",
126
+ },
127
+ ]
128
+
129
+
130
+ def _decision_kind_name(
131
+ decision: object,
132
+ ) -> Literal["clarify", "update", "passthrough"]:
133
+ if not isinstance(decision, dict):
134
+ raise ValueError("unexpected decision shape")
135
+
136
+ kind = decision.get("kind")
137
+ kind_name = getattr(kind, "value", None)
138
+ if kind_name not in {"clarify", "update", "passthrough"}:
139
+ raise ValueError(f"unexpected decision kind: {kind_name}")
140
+ return cast(Literal["clarify", "update", "passthrough"], kind_name)
141
+
142
+
143
+ def allowed_audiences_from_state(state: State) -> set[str]:
144
+ """Read allowed retrieval audiences from authoritative compiler state."""
145
+
146
+ use_items = set(get_policy_items(state, POLICY_USE))
147
+ prohibit_items = set(get_policy_items(state, POLICY_PROHIBIT))
148
+
149
+ if MANAGER_ACCESS in prohibit_items:
150
+ return set()
151
+
152
+ if MANAGER_ACCESS in use_items:
153
+ return {"employee", "manager"}
154
+
155
+ if EMPLOYEE_ACCESS in prohibit_items:
156
+ return set()
157
+
158
+ if EMPLOYEE_ACCESS in use_items:
159
+ return {"employee"}
160
+
161
+ return set()
162
+
163
+
164
+ def classify_premise_as_case_context(premise: str | None) -> CaseContext | None:
165
+ """Map saved HR case facts to a host-owned retrieval relevance context."""
166
+
167
+ if premise is None:
168
+ return None
169
+
170
+ normalized_premise = premise.casefold()
171
+ if (
172
+ "general employee handbook" in normalized_premise
173
+ and "new hire" in normalized_premise
174
+ ):
175
+ return "general_handbook_case"
176
+
177
+ if (
178
+ "leave eligibility" in normalized_premise
179
+ and "parental leave" in normalized_premise
180
+ ):
181
+ return "leave_case"
182
+
183
+ if (
184
+ "staffing approval" in normalized_premise
185
+ and "team reorganization" in normalized_premise
186
+ ):
187
+ return "staffing_case"
188
+
189
+ return None
190
+
191
+
192
+ def filter_documents_by_case_context(
193
+ documents: list[PolicyDocument],
194
+ case_context: CaseContext | None,
195
+ ) -> list[PolicyDocument]:
196
+ """Limit relevance only within the already eligible document set."""
197
+
198
+ if case_context is None:
199
+ return [
200
+ document
201
+ for document in documents
202
+ if "general_handbook_case" in document["relevance_tags"]
203
+ ]
204
+
205
+ relevant_documents = [
206
+ document for document in documents if case_context in document["relevance_tags"]
207
+ ]
208
+ if relevant_documents:
209
+ return relevant_documents
210
+
211
+ return []
212
+
213
+
214
+ def retrieve_hr_documents(
215
+ query: str,
216
+ *,
217
+ state: State,
218
+ retriever: HRPolicyRetriever,
219
+ ) -> RetrievalResult:
220
+ """Retrieve only documents the host deems eligible from compiler state."""
221
+
222
+ premise = get_premise_value(state)
223
+ return retriever.search(
224
+ query,
225
+ allowed_audiences=allowed_audiences_from_state(state),
226
+ case_context=classify_premise_as_case_context(premise),
227
+ )
228
+
229
+
230
+ def handle_retrieval_turn(
231
+ engine: Engine,
232
+ *,
233
+ compiler_input: str,
234
+ query: str,
235
+ retriever: HRPolicyRetriever,
236
+ ) -> RetrievalTurnResult:
237
+ """Resolve policy updates, but block retrieval on contradictory turns."""
238
+
239
+ decision = engine.step(compiler_input)
240
+
241
+ if is_clarify(decision):
242
+ return {
243
+ "decision_kind": "clarify",
244
+ "prompt_to_user": decision.get("prompt_to_user"),
245
+ "retrieval_result": {
246
+ "query": query,
247
+ "eligible_document_ids": [],
248
+ "returned_document_ids": [],
249
+ "blocked_reason": "clarification required before retrieval policy changes",
250
+ },
251
+ }
252
+
253
+ authoritative_state = get_decision_state(decision)
254
+ if authoritative_state is None:
255
+ authoritative_state = engine.state
256
+
257
+ return {
258
+ "decision_kind": _decision_kind_name(decision),
259
+ "prompt_to_user": decision.get("prompt_to_user"),
260
+ "retrieval_result": retrieve_hr_documents(
261
+ query,
262
+ state=authoritative_state,
263
+ retriever=retriever,
264
+ ),
265
+ }
266
+
267
+
268
+ def run_demo() -> dict[str, RetrievalResult]:
269
+ """Run a deterministic retrieval-filtering demonstration."""
270
+
271
+ query = "handbook policy"
272
+ retriever = HRPolicyRetriever(documents=example_documents())
273
+
274
+ absent_engine = create_engine()
275
+ employee_engine = create_engine()
276
+ employee_engine.step(f"use {EMPLOYEE_ACCESS}")
277
+ manager_engine = create_engine()
278
+ manager_engine.step(f"use {MANAGER_ACCESS}")
279
+
280
+ return {
281
+ "absent_state": retrieve_hr_documents(
282
+ query,
283
+ state=absent_engine.state,
284
+ retriever=retriever,
285
+ ),
286
+ "employee_access": retrieve_hr_documents(
287
+ query,
288
+ state=employee_engine.state,
289
+ retriever=retriever,
290
+ ),
291
+ "manager_access": retrieve_hr_documents(
292
+ query,
293
+ state=manager_engine.state,
294
+ retriever=retriever,
295
+ ),
296
+ }
297
+
298
+
299
+ if __name__ == "__main__":
300
+ print(run_demo())
@@ -0,0 +1,41 @@
1
+ # Schema selection
2
+
3
+ These examples show how authoritative state changes which host-side path runs.
4
+
5
+ They demonstrate observable runtime behavior changes rather than model compliance.
6
+
7
+ ## Examples
8
+
9
+ ### `refund_intake`
10
+
11
+ Shows two generic schema-selection mechanisms in the same customer
12
+ order/support intake domain.
13
+
14
+ Policy-driven selection can route requests when state contains:
15
+
16
+ ```text
17
+ use refund_intake
18
+ ```
19
+
20
+ Premise-driven selection can also change schema choice for the same ambiguous
21
+ request when saved factual order context changes.
22
+
23
+ This example keeps premise factual. It does not treat premise as a disguised
24
+ workflow command.
25
+
26
+ ### `ollama_structured_output`
27
+
28
+ Shows a host selecting an Ollama `format` schema from compiled policy state.
29
+
30
+ ### `litellm_response_format`
31
+
32
+ Shows a host selecting a LiteLLM `response_format` from compiled policy state.
33
+
34
+ ## Technology-specific examples
35
+
36
+ The generic examples teach the enforcement point first.
37
+
38
+ Concrete runtime surfaces currently linked from this repo:
39
+
40
+ - [python/examples/schema_selection/ollama_structured_output/README.md](ollama_structured_output/README.md)
41
+ - `python/examples/schema_selection/litellm_response_format/response_format.py`
@@ -0,0 +1,218 @@
1
+ """Minimal LiteLLM response_format selection from authoritative state.
2
+
3
+ Flow:
4
+ Context Compiler state -> host response_format decision -> LiteLLM model call.
5
+
6
+ This example keeps model execution optional so tests can validate behavior
7
+ without a live provider.
8
+ """
9
+
10
+ import os
11
+ from collections.abc import Callable, Mapping
12
+ from importlib import import_module
13
+ from typing import Any, TypedDict, cast
14
+
15
+ from context_compiler import (
16
+ POLICY_PROHIBIT,
17
+ POLICY_USE,
18
+ State,
19
+ create_engine,
20
+ get_clarify_prompt,
21
+ get_decision_state,
22
+ get_policy_items,
23
+ is_clarify,
24
+ )
25
+ from context_compiler.engine import Engine
26
+
27
+ from context_compiler_example_integrations.examples._shared.provider_mode import (
28
+ print_startup_config,
29
+ resolve_provider_config,
30
+ )
31
+
32
+ COMPACT_SUMMARY_RESPONSE_FORMAT: dict[str, Any] = {
33
+ "type": "json_schema",
34
+ "json_schema": {
35
+ "name": "compact_summary",
36
+ "schema": {
37
+ "type": "object",
38
+ "properties": {
39
+ "summary": {
40
+ "type": "string",
41
+ "description": "A compact summary of the answer.",
42
+ }
43
+ },
44
+ "required": ["summary"],
45
+ "additionalProperties": False,
46
+ },
47
+ },
48
+ }
49
+
50
+ ACTION_PLAN_RESPONSE_FORMAT: dict[str, Any] = {
51
+ "type": "json_schema",
52
+ "json_schema": {
53
+ "name": "action_plan",
54
+ "schema": {
55
+ "type": "object",
56
+ "properties": {
57
+ "steps": {
58
+ "type": "array",
59
+ "items": {"type": "string"},
60
+ "description": "Ordered next steps for the user.",
61
+ }
62
+ },
63
+ "required": ["steps"],
64
+ "additionalProperties": False,
65
+ },
66
+ },
67
+ }
68
+
69
+ _RESPONSE_FORMAT_BY_ITEM: dict[str, dict[str, Any]] = {
70
+ "compact_summary": COMPACT_SUMMARY_RESPONSE_FORMAT,
71
+ "action_plan": ACTION_PLAN_RESPONSE_FORMAT,
72
+ }
73
+
74
+
75
+ class TurnPlan(TypedDict):
76
+ decision_kind: str
77
+ clarify_prompt: str | None
78
+ selected_response_format_item: str | None
79
+ response_format: dict[str, Any] | None
80
+
81
+
82
+ class _LiteLLMCallKwargs(TypedDict, total=False):
83
+ model: str
84
+ messages: list[dict[str, str]]
85
+ temperature: float
86
+ api_base: str
87
+ api_key: str
88
+ response_format: dict[str, Any]
89
+
90
+
91
+ def select_litellm_response_format(
92
+ state: State,
93
+ ) -> tuple[str | None, dict[str, Any] | None]:
94
+ """Return (policy_item, response_format) or (None, None) when no safe match exists."""
95
+
96
+ use_items = set(get_policy_items(state, POLICY_USE))
97
+ prohibit_items = set(get_policy_items(state, POLICY_PROHIBIT))
98
+
99
+ for item, response_format in _RESPONSE_FORMAT_BY_ITEM.items():
100
+ if item in use_items and item not in prohibit_items:
101
+ return item, response_format
102
+
103
+ return None, None
104
+
105
+
106
+ def plan_turn(user_input: str, engine: Engine) -> TurnPlan:
107
+ """Run compiler step and decide whether to request LiteLLM structured output."""
108
+
109
+ decision = engine.step(user_input)
110
+ if is_clarify(decision):
111
+ return {
112
+ "decision_kind": "clarify",
113
+ "clarify_prompt": get_clarify_prompt(decision),
114
+ "selected_response_format_item": None,
115
+ "response_format": None,
116
+ }
117
+
118
+ decision_state = get_decision_state(decision)
119
+ compiled_state = decision_state if decision_state is not None else engine.state
120
+ selected_item, response_format = select_litellm_response_format(compiled_state)
121
+
122
+ return {
123
+ "decision_kind": str(decision["kind"]),
124
+ "clarify_prompt": None,
125
+ "selected_response_format_item": selected_item,
126
+ "response_format": response_format,
127
+ }
128
+
129
+
130
+ def _get_litellm_completion() -> Callable[..., object]:
131
+ litellm_module = import_module("litellm")
132
+ return cast(Callable[..., object], litellm_module.completion)
133
+
134
+
135
+ def _extract_response_content(response: object) -> str | None:
136
+ if isinstance(response, Mapping):
137
+ choices = response.get("choices")
138
+ if isinstance(choices, list) and choices:
139
+ first = choices[0]
140
+ if isinstance(first, Mapping):
141
+ message = first.get("message")
142
+ if isinstance(message, Mapping):
143
+ content = message.get("content")
144
+ if isinstance(content, str):
145
+ return content
146
+
147
+ choices_attr = getattr(response, "choices", None)
148
+ if isinstance(choices_attr, list) and choices_attr:
149
+ first = choices_attr[0]
150
+ message_attr = getattr(first, "message", None)
151
+ content_attr = getattr(message_attr, "content", None)
152
+ if isinstance(content_attr, str):
153
+ return content_attr
154
+
155
+ return None
156
+
157
+
158
+ def optional_litellm_call(
159
+ *,
160
+ user_input: str,
161
+ response_format: Mapping[str, Any] | None,
162
+ ) -> str:
163
+ """Optional smoke call to LiteLLM.
164
+
165
+ If `response_format` is provided, it is passed through unchanged.
166
+ """
167
+
168
+ try:
169
+ completion = _get_litellm_completion()
170
+ except ModuleNotFoundError as exc:
171
+ raise RuntimeError(
172
+ "litellm is required. Install with: pip install litellm"
173
+ ) from exc
174
+
175
+ config = resolve_provider_config(default_model="openai/gpt-4o-mini")
176
+ print_startup_config(config)
177
+
178
+ kwargs: _LiteLLMCallKwargs = {
179
+ "model": config.model,
180
+ "messages": [{"role": "user", "content": user_input}],
181
+ "temperature": 0,
182
+ "api_base": config.base_url,
183
+ }
184
+ if config.api_key:
185
+ kwargs["api_key"] = config.api_key
186
+ if response_format is not None:
187
+ kwargs["response_format"] = dict(response_format)
188
+
189
+ response = completion(**kwargs)
190
+ content = _extract_response_content(response)
191
+ if content is None:
192
+ raise RuntimeError("LiteLLM response missing choices[0].message.content")
193
+ return content
194
+
195
+
196
+ def main() -> None:
197
+ engine = create_engine()
198
+
199
+ # Demonstration setup.
200
+ engine.step("use compact_summary")
201
+ engine.step("prohibit action_plan")
202
+
203
+ plan = plan_turn("Summarize what changed in this project.", engine)
204
+ print("decision_kind:", plan["decision_kind"])
205
+ print("selected_response_format_item:", plan["selected_response_format_item"])
206
+ print("response_format_selected:", plan["response_format"] is not None)
207
+
208
+ # Optional model execution path; disabled by default.
209
+ if os.getenv("RUN_LITELLM_SMOKE") == "1":
210
+ response = optional_litellm_call(
211
+ user_input="Summarize what changed in this project.",
212
+ response_format=plan["response_format"],
213
+ )
214
+ print("litellm_response:", response)
215
+
216
+
217
+ if __name__ == "__main__":
218
+ main()
@@ -0,0 +1,60 @@
1
+ # Ollama structured output (host-side selection)
2
+
3
+ Policy state changes which schema the host sends through Ollama's `format`
4
+ field, or whether it sends none. This example shows that host-side
5
+ schema-selection behavior without relying on prompt reinjection.
6
+
7
+ Flow:
8
+
9
+ `Context Compiler state -> host schema decision -> Ollama format request -> model call`
10
+
11
+ The host reads compiled policy state, picks a JSON Schema (or none), and sends that choice through Ollama's `format` field.
12
+
13
+ ## What this example guarantees
14
+
15
+ - Context Compiler provides deterministic state transitions.
16
+ - The host integration decides whether to request a schema.
17
+ - Ollama structured output is a runtime request made by the host.
18
+ - If policy state is unknown or insufficient, the host requests no schema.
19
+
20
+ ## What `prohibit shell_command` means here
21
+
22
+ - The host will not request the `shell_command` schema.
23
+ - The host may still request a different schema when policy supports it (for example, `python_script`).
24
+ - This does not block normal language discussion about shell commands.
25
+
26
+ ## Observable behavior
27
+
28
+ Given policy state:
29
+
30
+ ```text
31
+ use python_script
32
+ prohibit shell_command
33
+ ```
34
+
35
+ this host selects `python_script` schema and does not request `shell_command` schema.
36
+
37
+ ## Test boundary
38
+
39
+ Tests verify schema selection behavior only:
40
+
41
+ - compiler state -> selected schema (or no schema)
42
+ - contradiction handling stays in compiler `clarify`
43
+
44
+ Tests do not assert exact model wording.
45
+
46
+ ## Run without Ollama
47
+
48
+ ```shell
49
+ uv run python python/examples/schema_selection/ollama_structured_output/example.py
50
+ ```
51
+
52
+ ## Optional Ollama smoke run
53
+
54
+ ```shell
55
+ export RUN_OLLAMA_SMOKE=1
56
+ export OLLAMA_MODEL=llama3.1
57
+ uv run python python/examples/schema_selection/ollama_structured_output/example.py
58
+ ```
59
+
60
+ When smoke mode is enabled, the host sends the selected JSON Schema through Ollama `format`.