context-compiler-example-integrations 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- context_compiler_example_integrations/README.md +71 -0
- context_compiler_example_integrations/__init__.py +1 -0
- context_compiler_example_integrations/examples/__init__.py +12 -0
- context_compiler_example_integrations/examples/_shared/__init__.py +1 -0
- context_compiler_example_integrations/examples/_shared/litellm_request.py +61 -0
- context_compiler_example_integrations/examples/_shared/provider_mode.py +91 -0
- context_compiler_example_integrations/examples/checkpoint_continuation/README.md +77 -0
- context_compiler_example_integrations/examples/checkpoint_continuation/example.py +169 -0
- context_compiler_example_integrations/examples/checkpoint_continuation/fastapi/README.md +85 -0
- context_compiler_example_integrations/examples/checkpoint_continuation/fastapi/app.py +205 -0
- context_compiler_example_integrations/examples/execution_authorization/README.md +31 -0
- context_compiler_example_integrations/examples/execution_authorization/expense_approval/README.md +77 -0
- context_compiler_example_integrations/examples/execution_authorization/expense_approval/__init__.py +1 -0
- context_compiler_example_integrations/examples/execution_authorization/expense_approval/example.py +168 -0
- context_compiler_example_integrations/examples/execution_authorization/expense_approval/fastapi/README.md +97 -0
- context_compiler_example_integrations/examples/execution_authorization/expense_approval/fastapi/__init__.py +1 -0
- context_compiler_example_integrations/examples/execution_authorization/expense_approval/fastapi/app.py +312 -0
- context_compiler_example_integrations/examples/execution_authorization/expense_approval/fastapi/model_approval.py +98 -0
- context_compiler_example_integrations/examples/gateway_middleware/README.md +49 -0
- context_compiler_example_integrations/examples/gateway_middleware/customer_support_routing/README.md +40 -0
- context_compiler_example_integrations/examples/gateway_middleware/customer_support_routing/__init__.py +1 -0
- context_compiler_example_integrations/examples/gateway_middleware/customer_support_routing/example.py +220 -0
- context_compiler_example_integrations/examples/prompt_construction/README.md +27 -0
- context_compiler_example_integrations/examples/prompt_construction/litellm/README.md +197 -0
- context_compiler_example_integrations/examples/prompt_construction/litellm/basic.py +392 -0
- context_compiler_example_integrations/examples/prompt_construction/litellm/confirmation_helper.py +91 -0
- context_compiler_example_integrations/examples/prompt_construction/litellm/with_directive_drafter.py +524 -0
- context_compiler_example_integrations/examples/prompt_construction/writing_assistant/README.md +92 -0
- context_compiler_example_integrations/examples/prompt_construction/writing_assistant/__init__.py +1 -0
- context_compiler_example_integrations/examples/prompt_construction/writing_assistant/example.py +205 -0
- context_compiler_example_integrations/examples/retrieval_filtering/README.md +65 -0
- context_compiler_example_integrations/examples/retrieval_filtering/chromadb_hr_policy_lookup/README.md +120 -0
- context_compiler_example_integrations/examples/retrieval_filtering/chromadb_hr_policy_lookup/__init__.py +1 -0
- context_compiler_example_integrations/examples/retrieval_filtering/chromadb_hr_policy_lookup/example.py +320 -0
- context_compiler_example_integrations/examples/retrieval_filtering/hr_policy_lookup/README.md +123 -0
- context_compiler_example_integrations/examples/retrieval_filtering/hr_policy_lookup/__init__.py +1 -0
- context_compiler_example_integrations/examples/retrieval_filtering/hr_policy_lookup/example.py +300 -0
- context_compiler_example_integrations/examples/schema_selection/README.md +41 -0
- context_compiler_example_integrations/examples/schema_selection/litellm_response_format/response_format.py +218 -0
- context_compiler_example_integrations/examples/schema_selection/ollama_structured_output/README.md +60 -0
- context_compiler_example_integrations/examples/schema_selection/ollama_structured_output/example.py +171 -0
- context_compiler_example_integrations/examples/schema_selection/refund_intake/README.md +95 -0
- context_compiler_example_integrations/examples/schema_selection/refund_intake/__init__.py +1 -0
- context_compiler_example_integrations/examples/schema_selection/refund_intake/example.py +180 -0
- context_compiler_example_integrations/examples/tool_gating/README.md +55 -0
- context_compiler_example_integrations/examples/tool_gating/calendar_admin/README.md +34 -0
- context_compiler_example_integrations/examples/tool_gating/calendar_admin/__init__.py +1 -0
- context_compiler_example_integrations/examples/tool_gating/calendar_admin/example.py +195 -0
- context_compiler_example_integrations/examples/tool_gating/mcp_calendar_admin/README.md +93 -0
- context_compiler_example_integrations/examples/tool_gating/mcp_calendar_admin/__init__.py +1 -0
- context_compiler_example_integrations/examples/tool_gating/mcp_calendar_admin/example.py +250 -0
- context_compiler_example_integrations/examples/tool_gating/mcp_calendar_admin/live_model.py +369 -0
- context_compiler_example_integrations/reference_integrations/__init__.py +12 -0
- context_compiler_example_integrations/reference_integrations/litellm_proxy/README.md +251 -0
- context_compiler_example_integrations/reference_integrations/litellm_proxy/_checkpoint_support.py +146 -0
- context_compiler_example_integrations/reference_integrations/litellm_proxy/config.example.yaml +23 -0
- context_compiler_example_integrations/reference_integrations/litellm_proxy/context_compiler_precall_hook.py +163 -0
- context_compiler_example_integrations/reference_integrations/litellm_proxy/context_compiler_precall_hook_with_directive_drafter.py +289 -0
- context_compiler_example_integrations/reference_integrations/openwebui_pipe/README.md +233 -0
- context_compiler_example_integrations/reference_integrations/openwebui_pipe/open_webui_pipe.py +750 -0
- context_compiler_example_integrations/reference_integrations/openwebui_pipe/open_webui_pipe_with_directive_drafter.py +1045 -0
- context_compiler_example_integrations-0.1.0.dist-info/METADATA +110 -0
- context_compiler_example_integrations-0.1.0.dist-info/RECORD +65 -0
- context_compiler_example_integrations-0.1.0.dist-info/WHEEL +4 -0
- context_compiler_example_integrations-0.1.0.dist-info/licenses/LICENSE +173 -0
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
"""Minimal LiteLLM integration with Context Compiler.
|
|
2
|
+
|
|
3
|
+
Flow:
|
|
4
|
+
1. Call engine.step(user_input)
|
|
5
|
+
2. clarify -> return prompt_to_user (no model call)
|
|
6
|
+
3. update -> return deterministic acknowledgment text (no model call)
|
|
7
|
+
4. passthrough -> call LiteLLM with compiled state + user input
|
|
8
|
+
|
|
9
|
+
Intended host usage:
|
|
10
|
+
- collect user input
|
|
11
|
+
- call handle_turn(user_input, engine)
|
|
12
|
+
- display returned assistant text
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import logging
|
|
16
|
+
import re
|
|
17
|
+
from collections.abc import Callable, Mapping, Sequence
|
|
18
|
+
from importlib import import_module
|
|
19
|
+
from typing import TypedDict, cast
|
|
20
|
+
|
|
21
|
+
from context_compiler import (
|
|
22
|
+
DECISION_CLARIFY,
|
|
23
|
+
DECISION_PASSTHROUGH,
|
|
24
|
+
DECISION_UPDATE,
|
|
25
|
+
POLICY_PROHIBIT,
|
|
26
|
+
POLICY_USE,
|
|
27
|
+
State,
|
|
28
|
+
get_clarify_prompt,
|
|
29
|
+
get_decision_state,
|
|
30
|
+
get_policy_items,
|
|
31
|
+
get_premise_value,
|
|
32
|
+
is_clarify,
|
|
33
|
+
is_passthrough,
|
|
34
|
+
is_update,
|
|
35
|
+
state_diff,
|
|
36
|
+
)
|
|
37
|
+
from context_compiler.engine import Engine
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
from .confirmation_helper import (
|
|
41
|
+
is_confirmation_text,
|
|
42
|
+
summarize_confirmation_update_from_checkpoint,
|
|
43
|
+
)
|
|
44
|
+
except ImportError:
|
|
45
|
+
from confirmation_helper import (
|
|
46
|
+
is_confirmation_text,
|
|
47
|
+
summarize_confirmation_update_from_checkpoint,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
from context_compiler_example_integrations.examples._shared.provider_mode import (
|
|
51
|
+
print_startup_config,
|
|
52
|
+
resolve_provider_config,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
logger = logging.getLogger(__name__)
|
|
56
|
+
# Example-only in-memory checkpoint store.
|
|
57
|
+
# This keeps continuation state only for the current process lifetime.
|
|
58
|
+
# Real deployments should persist checkpoints externally (DB/Redis/etc.),
|
|
59
|
+
# or restart continuity for pending flows will be lost.
|
|
60
|
+
_CHECKPOINTS_BY_SESSION_KEY: dict[str, str] = {}
|
|
61
|
+
_RESTORED_ENGINE_BY_SESSION_KEY: dict[str, int] = {}
|
|
62
|
+
SHOW_CONTEXT_COMPILER_TRACE = False
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class _LiteLLMCallKwargs(TypedDict, total=False):
|
|
66
|
+
model: str
|
|
67
|
+
messages: list[dict[str, str]]
|
|
68
|
+
api_key: str
|
|
69
|
+
temperature: float
|
|
70
|
+
api_base: str
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _extract_response_content(response: object) -> str | None:
|
|
74
|
+
if isinstance(response, Mapping):
|
|
75
|
+
choices = response.get("choices")
|
|
76
|
+
if isinstance(choices, Sequence) and choices:
|
|
77
|
+
first = choices[0]
|
|
78
|
+
if isinstance(first, Mapping):
|
|
79
|
+
message = first.get("message")
|
|
80
|
+
if isinstance(message, Mapping):
|
|
81
|
+
content = message.get("content")
|
|
82
|
+
if isinstance(content, str):
|
|
83
|
+
return content
|
|
84
|
+
|
|
85
|
+
choices_attr = getattr(response, "choices", None)
|
|
86
|
+
if isinstance(choices_attr, Sequence) and choices_attr:
|
|
87
|
+
first = choices_attr[0]
|
|
88
|
+
message_attr = getattr(first, "message", None)
|
|
89
|
+
content_attr = getattr(message_attr, "content", None)
|
|
90
|
+
if isinstance(content_attr, str):
|
|
91
|
+
return content_attr
|
|
92
|
+
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _render_state_lines(state: object) -> list[str]:
|
|
97
|
+
if not isinstance(state, dict):
|
|
98
|
+
return ["- unavailable"]
|
|
99
|
+
typed_state = cast(State, state)
|
|
100
|
+
|
|
101
|
+
premise = get_premise_value(typed_state)
|
|
102
|
+
use_items = sorted(get_policy_items(typed_state, POLICY_USE))
|
|
103
|
+
prohibit_items = sorted(get_policy_items(typed_state, POLICY_PROHIBIT))
|
|
104
|
+
|
|
105
|
+
lines = [f"- premise: {premise if premise is not None else '(none)'}"]
|
|
106
|
+
lines.append(f"- use: {', '.join(use_items) if use_items else '(none)'}")
|
|
107
|
+
lines.append(
|
|
108
|
+
f"- prohibit: {', '.join(prohibit_items) if prohibit_items else '(none)'}"
|
|
109
|
+
)
|
|
110
|
+
return lines
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _build_trace_text(
|
|
114
|
+
*,
|
|
115
|
+
original_input: str,
|
|
116
|
+
compiler_input: str,
|
|
117
|
+
decision: object,
|
|
118
|
+
state_before: object,
|
|
119
|
+
state_after: object,
|
|
120
|
+
llm_called: bool,
|
|
121
|
+
) -> str:
|
|
122
|
+
kind = decision.get("kind", "unknown") if isinstance(decision, dict) else "unknown"
|
|
123
|
+
lines = [
|
|
124
|
+
"Context Compiler trace",
|
|
125
|
+
f"- original_input: {original_input}",
|
|
126
|
+
f"- compiler_input: {compiler_input}",
|
|
127
|
+
f"- decision: {kind}",
|
|
128
|
+
f"- llm_called: {'yes' if llm_called else 'no'}",
|
|
129
|
+
]
|
|
130
|
+
if isinstance(state_before, dict) and isinstance(state_after, dict):
|
|
131
|
+
diff = state_diff(cast(State, state_before), cast(State, state_after))
|
|
132
|
+
lines.append(f"- state_changed: {'yes' if diff['changed'] else 'no'}")
|
|
133
|
+
lines.append("state_before:")
|
|
134
|
+
lines.extend(_render_state_lines(state_before))
|
|
135
|
+
lines.append("state_after:")
|
|
136
|
+
lines.extend(_render_state_lines(state_after))
|
|
137
|
+
return "\n".join(lines)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _render_compiled_state_contract(compiled_state: State) -> str:
|
|
141
|
+
premise = get_premise_value(compiled_state)
|
|
142
|
+
use_items = sorted(get_policy_items(compiled_state, POLICY_USE))
|
|
143
|
+
prohibit_items = sorted(get_policy_items(compiled_state, POLICY_PROHIBIT))
|
|
144
|
+
|
|
145
|
+
lines: list[str] = ["The following constraints are authoritative."]
|
|
146
|
+
if premise:
|
|
147
|
+
lines.append(f"Current premise: {premise}.")
|
|
148
|
+
if use_items:
|
|
149
|
+
lines.append("Items marked use: " + ", ".join(use_items) + ".")
|
|
150
|
+
if prohibit_items:
|
|
151
|
+
lines.append("Items marked prohibit: " + ", ".join(prohibit_items) + ".")
|
|
152
|
+
lines.append("If user text conflicts with constraints, follow constraints exactly.")
|
|
153
|
+
|
|
154
|
+
return "Host policy contract:\n" + "\n".join(f"- {line}" for line in lines)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _build_messages(user_input: str, compiled_state: State) -> list[dict[str, str]]:
|
|
158
|
+
return [
|
|
159
|
+
{
|
|
160
|
+
"role": "system",
|
|
161
|
+
"content": "You are a helpful assistant.\n"
|
|
162
|
+
+ _render_compiled_state_contract(compiled_state),
|
|
163
|
+
},
|
|
164
|
+
{"role": "user", "content": user_input},
|
|
165
|
+
]
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _call_litellm(messages: list[dict[str, str]]) -> str:
|
|
169
|
+
try:
|
|
170
|
+
litellm_module = import_module("litellm")
|
|
171
|
+
except ModuleNotFoundError as exc:
|
|
172
|
+
raise RuntimeError(
|
|
173
|
+
"litellm is required. Install with: pip install litellm"
|
|
174
|
+
) from exc
|
|
175
|
+
completion_fn = cast(Callable[..., object], litellm_module.completion)
|
|
176
|
+
|
|
177
|
+
config = resolve_provider_config(default_model="openai/gpt-4o-mini")
|
|
178
|
+
print_startup_config(config, logger=logger)
|
|
179
|
+
|
|
180
|
+
kwargs: _LiteLLMCallKwargs = {
|
|
181
|
+
"model": config.model,
|
|
182
|
+
"messages": messages,
|
|
183
|
+
"temperature": 0,
|
|
184
|
+
"api_base": config.base_url,
|
|
185
|
+
}
|
|
186
|
+
if config.api_key:
|
|
187
|
+
kwargs["api_key"] = config.api_key
|
|
188
|
+
|
|
189
|
+
response = completion_fn(**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 _restore_session_checkpoint_if_needed(
|
|
197
|
+
engine: Engine, session_key: str | None
|
|
198
|
+
) -> None:
|
|
199
|
+
if session_key is None:
|
|
200
|
+
return
|
|
201
|
+
engine_id = id(engine)
|
|
202
|
+
if _RESTORED_ENGINE_BY_SESSION_KEY.get(session_key) == engine_id:
|
|
203
|
+
return
|
|
204
|
+
|
|
205
|
+
checkpoint = _CHECKPOINTS_BY_SESSION_KEY.get(session_key)
|
|
206
|
+
if checkpoint is not None:
|
|
207
|
+
engine.import_checkpoint_json(checkpoint)
|
|
208
|
+
_RESTORED_ENGINE_BY_SESSION_KEY[session_key] = engine_id
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _persist_session_checkpoint_if_needed(
|
|
212
|
+
engine: Engine, kind: str, session_key: str | None
|
|
213
|
+
) -> None:
|
|
214
|
+
if session_key is None:
|
|
215
|
+
return
|
|
216
|
+
if kind not in {DECISION_UPDATE, DECISION_CLARIFY}:
|
|
217
|
+
return
|
|
218
|
+
_CHECKPOINTS_BY_SESSION_KEY[session_key] = engine.export_checkpoint_json()
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _render_item_label(value: str) -> str:
|
|
222
|
+
return re.sub(r"\s+", " ", value).strip().lower()
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _near_miss_directive_clarify(value: str) -> str | None:
|
|
226
|
+
normalized = re.sub(r"\s+", " ", value.strip())
|
|
227
|
+
lower = normalized.lower()
|
|
228
|
+
|
|
229
|
+
if lower in {"reset premise", "reset premises", "clear premises"}:
|
|
230
|
+
return "Unknown directive.\nUse 'clear premise' or 'reset policies'."
|
|
231
|
+
if lower.startswith("set premise to "):
|
|
232
|
+
return "Invalid premise syntax.\nUse 'set premise <value>'."
|
|
233
|
+
if lower.startswith("change premise ") and not lower.startswith(
|
|
234
|
+
"change premise to "
|
|
235
|
+
):
|
|
236
|
+
return "Invalid premise syntax.\nUse 'change premise to <value>'."
|
|
237
|
+
return None
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _summarize_confirmation_update(user_input: str, checkpoint: object) -> str:
|
|
241
|
+
summarize_fn: Callable[[str, object], str] = (
|
|
242
|
+
summarize_confirmation_update_from_checkpoint
|
|
243
|
+
)
|
|
244
|
+
return summarize_fn(user_input, checkpoint)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _summarize_update_from_input(user_input: str) -> str:
|
|
248
|
+
normalized = re.sub(r"\s+", " ", user_input.strip())
|
|
249
|
+
lower = normalized.lower()
|
|
250
|
+
|
|
251
|
+
if lower == "clear state":
|
|
252
|
+
return "State cleared."
|
|
253
|
+
if lower == "clear premise":
|
|
254
|
+
return "Premise cleared."
|
|
255
|
+
if lower == "reset policies":
|
|
256
|
+
return "Policies reset."
|
|
257
|
+
|
|
258
|
+
replacement_match = re.match(
|
|
259
|
+
r"^use\s+(.+?)\s+instead\s+of\s+(.+)$", normalized, flags=re.IGNORECASE
|
|
260
|
+
)
|
|
261
|
+
if replacement_match is not None:
|
|
262
|
+
item = _render_item_label(replacement_match.group(1).rstrip(" .!?"))
|
|
263
|
+
if item:
|
|
264
|
+
return f"State updated: Use {item}."
|
|
265
|
+
|
|
266
|
+
use_match = re.match(r"^use\s+(.+)$", normalized, flags=re.IGNORECASE)
|
|
267
|
+
if use_match is not None:
|
|
268
|
+
item = _render_item_label(use_match.group(1).rstrip(" .!?"))
|
|
269
|
+
if item:
|
|
270
|
+
return f"State updated: Use {item}."
|
|
271
|
+
|
|
272
|
+
prohibit_match = re.match(r"^prohibit\s+(.+)$", normalized, flags=re.IGNORECASE)
|
|
273
|
+
if prohibit_match is not None:
|
|
274
|
+
item = _render_item_label(prohibit_match.group(1).rstrip(" .!?"))
|
|
275
|
+
if item:
|
|
276
|
+
return f"State updated: Prohibit {item}."
|
|
277
|
+
|
|
278
|
+
remove_policy_match = re.match(
|
|
279
|
+
r"^remove\s+policy\s+(.+)$", normalized, flags=re.IGNORECASE
|
|
280
|
+
)
|
|
281
|
+
if remove_policy_match is not None:
|
|
282
|
+
item = _render_item_label(remove_policy_match.group(1).rstrip(" .!?"))
|
|
283
|
+
if item:
|
|
284
|
+
return f"State updated: Removed policy {item}."
|
|
285
|
+
|
|
286
|
+
return "State updated."
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def _append_trace(
|
|
290
|
+
response_text: str,
|
|
291
|
+
*,
|
|
292
|
+
original_input: str,
|
|
293
|
+
compiler_input: str,
|
|
294
|
+
decision: object,
|
|
295
|
+
state_before: object,
|
|
296
|
+
state_after: object,
|
|
297
|
+
llm_called: bool,
|
|
298
|
+
) -> str:
|
|
299
|
+
if not SHOW_CONTEXT_COMPILER_TRACE:
|
|
300
|
+
return response_text
|
|
301
|
+
trace_text = _build_trace_text(
|
|
302
|
+
original_input=original_input,
|
|
303
|
+
compiler_input=compiler_input,
|
|
304
|
+
decision=decision,
|
|
305
|
+
state_before=state_before,
|
|
306
|
+
state_after=state_after,
|
|
307
|
+
llm_called=llm_called,
|
|
308
|
+
)
|
|
309
|
+
return f"{response_text}\n\n{trace_text}"
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def handle_turn(
|
|
313
|
+
user_input: str, engine: Engine, *, session_key: str | None = None
|
|
314
|
+
) -> str:
|
|
315
|
+
_restore_session_checkpoint_if_needed(engine, session_key)
|
|
316
|
+
state_before = engine.state
|
|
317
|
+
has_pending_before = engine.has_pending_clarification()
|
|
318
|
+
checkpoint_before = engine.export_checkpoint() if has_pending_before else None
|
|
319
|
+
logger.debug("litellm_basic: engine_input=%s", f"user_input len={len(user_input)}")
|
|
320
|
+
decision = engine.step(user_input)
|
|
321
|
+
if is_clarify(decision):
|
|
322
|
+
kind = DECISION_CLARIFY
|
|
323
|
+
elif is_update(decision):
|
|
324
|
+
kind = DECISION_UPDATE
|
|
325
|
+
else:
|
|
326
|
+
kind = DECISION_PASSTHROUGH
|
|
327
|
+
logger.debug("litellm_basic: decision=%s", kind)
|
|
328
|
+
near_miss_prompt = _near_miss_directive_clarify(user_input)
|
|
329
|
+
|
|
330
|
+
if is_clarify(decision):
|
|
331
|
+
_persist_session_checkpoint_if_needed(engine, kind, session_key)
|
|
332
|
+
response_text = near_miss_prompt or get_clarify_prompt(decision) or ""
|
|
333
|
+
return _append_trace(
|
|
334
|
+
response_text,
|
|
335
|
+
original_input=user_input,
|
|
336
|
+
compiler_input=user_input,
|
|
337
|
+
decision=decision,
|
|
338
|
+
state_before=state_before,
|
|
339
|
+
state_after=engine.state,
|
|
340
|
+
llm_called=False,
|
|
341
|
+
)
|
|
342
|
+
if near_miss_prompt is not None and is_passthrough(decision):
|
|
343
|
+
return _append_trace(
|
|
344
|
+
near_miss_prompt,
|
|
345
|
+
original_input=user_input,
|
|
346
|
+
compiler_input=user_input,
|
|
347
|
+
decision={"kind": DECISION_CLARIFY, "prompt_to_user": near_miss_prompt},
|
|
348
|
+
state_before=state_before,
|
|
349
|
+
state_after=engine.state,
|
|
350
|
+
llm_called=False,
|
|
351
|
+
)
|
|
352
|
+
_persist_session_checkpoint_if_needed(engine, kind, session_key)
|
|
353
|
+
if (
|
|
354
|
+
is_update(decision)
|
|
355
|
+
and is_confirmation_text(user_input)
|
|
356
|
+
and checkpoint_before is not None
|
|
357
|
+
):
|
|
358
|
+
response_text = _summarize_confirmation_update(user_input, checkpoint_before)
|
|
359
|
+
return _append_trace(
|
|
360
|
+
response_text,
|
|
361
|
+
original_input=user_input,
|
|
362
|
+
compiler_input=user_input,
|
|
363
|
+
decision=decision,
|
|
364
|
+
state_before=state_before,
|
|
365
|
+
state_after=engine.state,
|
|
366
|
+
llm_called=False,
|
|
367
|
+
)
|
|
368
|
+
if is_update(decision):
|
|
369
|
+
response_text = _summarize_update_from_input(user_input)
|
|
370
|
+
return _append_trace(
|
|
371
|
+
response_text,
|
|
372
|
+
original_input=user_input,
|
|
373
|
+
compiler_input=user_input,
|
|
374
|
+
decision=decision,
|
|
375
|
+
state_before=state_before,
|
|
376
|
+
state_after=engine.state,
|
|
377
|
+
llm_called=False,
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
decision_state = get_decision_state(decision)
|
|
381
|
+
compiled_state = decision_state if decision_state is not None else engine.state
|
|
382
|
+
messages = _build_messages(user_input, compiled_state)
|
|
383
|
+
response_text = _call_litellm(messages)
|
|
384
|
+
return _append_trace(
|
|
385
|
+
response_text,
|
|
386
|
+
original_input=user_input,
|
|
387
|
+
compiler_input=user_input,
|
|
388
|
+
decision=decision,
|
|
389
|
+
state_before=state_before,
|
|
390
|
+
state_after=compiled_state,
|
|
391
|
+
llm_called=True,
|
|
392
|
+
)
|
context_compiler_example_integrations/examples/prompt_construction/litellm/confirmation_helper.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Local confirmation helpers for the LiteLLM prompt-construction examples.
|
|
2
|
+
|
|
3
|
+
This keeps example behavior deterministic within this repository instead of
|
|
4
|
+
depending on the separately versioned host_support package.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
|
|
9
|
+
_TRAILING_CONFIRM_PUNCT_RE = re.compile(r"[.,!?]+$")
|
|
10
|
+
|
|
11
|
+
_AFFIRMATIVE_CONFIRMATION_TOKENS = frozenset(
|
|
12
|
+
{"yes", "yes please", "yep", "yeah", "sure", "ok", "okay"}
|
|
13
|
+
)
|
|
14
|
+
_NEGATIVE_CONFIRMATION_TOKENS = frozenset({"no", "nope", "no thanks"})
|
|
15
|
+
|
|
16
|
+
CONFIRMATION_TOKENS: frozenset[str] = (
|
|
17
|
+
_AFFIRMATIVE_CONFIRMATION_TOKENS | _NEGATIVE_CONFIRMATION_TOKENS
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _render_item_label(value: str) -> str:
|
|
22
|
+
return re.sub(r"\s+", " ", value).strip()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _normalize_confirmation_text(value: str) -> str:
|
|
26
|
+
normalized = value.strip().lower()
|
|
27
|
+
normalized = re.sub(r"\s+", " ", normalized)
|
|
28
|
+
normalized = _TRAILING_CONFIRM_PUNCT_RE.sub("", normalized).strip()
|
|
29
|
+
return re.sub(r"\s+", " ", normalized)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def is_confirmation_text(value: str) -> bool:
|
|
33
|
+
return _normalize_confirmation_text(value) in CONFIRMATION_TOKENS
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _summarize_pending_confirmation_update(pending: object) -> str:
|
|
37
|
+
if not isinstance(pending, dict):
|
|
38
|
+
return "State updated."
|
|
39
|
+
|
|
40
|
+
replacement = pending.get("replacement")
|
|
41
|
+
if not isinstance(replacement, dict):
|
|
42
|
+
return "State updated."
|
|
43
|
+
|
|
44
|
+
kind = replacement.get("kind")
|
|
45
|
+
new_item = replacement.get("new_item")
|
|
46
|
+
old_item = replacement.get("old_item")
|
|
47
|
+
|
|
48
|
+
if kind == "use_only" and isinstance(new_item, str):
|
|
49
|
+
new_label = _render_item_label(new_item)
|
|
50
|
+
if new_label:
|
|
51
|
+
return f"State updated: Use {new_label}."
|
|
52
|
+
return "State updated."
|
|
53
|
+
|
|
54
|
+
if (
|
|
55
|
+
kind == "replace_use"
|
|
56
|
+
and isinstance(new_item, str)
|
|
57
|
+
and isinstance(old_item, str)
|
|
58
|
+
):
|
|
59
|
+
new_label = _render_item_label(new_item)
|
|
60
|
+
old_label = _render_item_label(old_item)
|
|
61
|
+
if not new_label or not old_label:
|
|
62
|
+
return "State updated."
|
|
63
|
+
|
|
64
|
+
prompt = pending.get("prompt_to_user")
|
|
65
|
+
prohibited_old_prompt = (
|
|
66
|
+
f'"{old_item}" is currently prohibited. '
|
|
67
|
+
f'Did you mean to remove it and use "{new_item}" instead?'
|
|
68
|
+
)
|
|
69
|
+
if prompt == prohibited_old_prompt:
|
|
70
|
+
return (
|
|
71
|
+
f"State updated: Removed prohibition on {old_label}; use {new_label}."
|
|
72
|
+
)
|
|
73
|
+
return f"State updated: Replaced {old_label} with {new_label}."
|
|
74
|
+
|
|
75
|
+
return "State updated."
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def summarize_confirmation_update(user_input: str, pending: object) -> str:
|
|
79
|
+
normalized = _normalize_confirmation_text(user_input)
|
|
80
|
+
if normalized in _NEGATIVE_CONFIRMATION_TOKENS:
|
|
81
|
+
return "State unchanged."
|
|
82
|
+
if normalized not in _AFFIRMATIVE_CONFIRMATION_TOKENS:
|
|
83
|
+
return "State updated."
|
|
84
|
+
return _summarize_pending_confirmation_update(pending)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def summarize_confirmation_update_from_checkpoint(
|
|
88
|
+
user_input: str, checkpoint: object
|
|
89
|
+
) -> str:
|
|
90
|
+
pending = checkpoint.get("pending") if isinstance(checkpoint, dict) else None
|
|
91
|
+
return summarize_confirmation_update(user_input, pending)
|