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/retrieval_filtering/hr_policy_lookup/example.py
ADDED
|
@@ -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()
|
context_compiler_example_integrations/examples/schema_selection/ollama_structured_output/README.md
ADDED
|
@@ -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`.
|