Skip to content

Commit b02fc0e

Browse files
authored
Merge pull request #186 from kalibr-ai/feat/meta-prompt-generation
feat: LLM meta-prompt generation (v1.14.1)
2 parents 002b299 + a2c3354 commit b02fc0e

3 files changed

Lines changed: 111 additions & 5 deletions

File tree

kalibr/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def call_openai(prompt):
3636
kalibr version # Show version
3737
"""
3838

39-
__version__ = "1.14.0"
39+
__version__ = "1.14.1"
4040

4141
# Auto-instrument LLM SDKs on import (can be disabled via env var)
4242
import os

kalibr/router.py

Lines changed: 109 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,16 @@ class HealConfig:
2020
judge_model: Model id for the Gate 2 judge (uses DeepSeek/OpenAI keys from env).
2121
repair_model: Optional override model for repair calls. ``None`` reuses the
2222
same model that produced the failing output.
23+
meta_prompt_enabled: When True, generate a task-specific system prompt via
24+
a cheap LLM before each heal step. Combined with repair prompts on retry.
25+
Fails open (never blocks or raises).
2326
"""
2427

2528
max_retries: int = 2
2629
gate2_enabled: bool = False
2730
judge_model: str = "deepseek-chat"
2831
repair_model: Optional[str] = None
32+
meta_prompt_enabled: bool = False
2933

3034
from kalibr.provision import resolve_credentials
3135
from opentelemetry import trace as otel_trace
@@ -39,6 +43,11 @@ class HealConfig:
3943
_auto_reported_traces: set = set()
4044
_auto_reported_lock = threading.Lock()
4145

46+
# Module-level cache for generated meta-prompts. Key: hash of goal + user preview.
47+
# Value: (generated_prompt, timestamp). TTL enforced at read time.
48+
_meta_prompt_cache: Dict[str, tuple] = {}
49+
_META_PROMPT_TTL_SECONDS = 300.0
50+
4251

4352
def _auto_report_outcome(
4453
trace_id: str,
@@ -255,6 +264,87 @@ def _gate2_judge(
255264
pass
256265
return {"score": None, "issues": [], "skipped": True}
257266

267+
async def _generate_meta_prompt(
268+
self,
269+
goal: str,
270+
messages: list,
271+
model_id: str,
272+
) -> Optional[str]:
273+
"""Generate a task-specific system prompt via a cheap LLM. Never raises.
274+
275+
Returns None on any error (missing key, parse failure, network error).
276+
Results cached for 5 minutes keyed on goal + first 100 chars of last user
277+
message, so repeated identical requests reuse the same prompt.
278+
"""
279+
import asyncio as _asyncio
280+
import hashlib as _hashlib
281+
import time as _time
282+
283+
try:
284+
user_preview = ""
285+
if messages:
286+
last = messages[-1]
287+
if isinstance(last, dict) and last.get("role") == "user":
288+
user_preview = str(last.get("content") or "")
289+
else:
290+
for m in reversed(messages):
291+
if isinstance(m, dict) and m.get("role") == "user":
292+
user_preview = str(m.get("content") or "")
293+
break
294+
295+
cache_key_raw = f"{goal}|{user_preview[:100]}"
296+
cache_key = _hashlib.sha256(cache_key_raw.encode("utf-8")).hexdigest()
297+
298+
now = _time.time()
299+
cached = _meta_prompt_cache.get(cache_key)
300+
if cached is not None:
301+
prompt, ts = cached
302+
if now - ts < _META_PROMPT_TTL_SECONDS:
303+
return prompt
304+
305+
deepseek_key = os.environ.get("DEEPSEEK_API_KEY")
306+
openai_key = os.environ.get("OPENAI_API_KEY")
307+
if not deepseek_key and not openai_key:
308+
return None
309+
310+
try:
311+
from openai import OpenAI
312+
except ImportError:
313+
return None
314+
315+
meta_prompt = (
316+
"Generate a concise system prompt (under 150 words) for an AI completing this task.\n"
317+
f"Goal type: {goal}\n"
318+
f"Task preview: {user_preview[:300]}\n"
319+
"Output ONLY the system prompt text."
320+
)
321+
322+
def _do_call() -> Optional[str]:
323+
try:
324+
if deepseek_key:
325+
client = OpenAI(api_key=deepseek_key, base_url="https://api.deepseek.com")
326+
model = "deepseek-chat"
327+
else:
328+
client = OpenAI(api_key=openai_key)
329+
model = "gpt-4o-mini"
330+
response = client.chat.completions.create(
331+
model=model,
332+
messages=[{"role": "user", "content": meta_prompt}],
333+
timeout=15.0,
334+
)
335+
return (response.choices[0].message.content or "").strip()
336+
except Exception:
337+
return None
338+
339+
generated = await _asyncio.to_thread(_do_call)
340+
if not generated:
341+
return None
342+
343+
_meta_prompt_cache[cache_key] = (generated, now)
344+
return generated
345+
except Exception:
346+
return None
347+
258348
def _repair_prompt(
259349
self,
260350
goal: str,
@@ -302,6 +392,7 @@ def run(
302392
max_retries: int = 2,
303393
gate2_enabled: bool = False,
304394
judge_model: str = "deepseek-chat",
395+
meta_prompt_enabled: bool = False,
305396
) -> Dict[str, Any]:
306397
"""Run the heal loop across paths until success or exhaustion.
307398
@@ -313,14 +404,28 @@ def run(
313404
last_failure_category: Optional[str] = None
314405
last_response: Any = None
315406

407+
meta_sys_prompt: Optional[str] = None
408+
if meta_prompt_enabled:
409+
import asyncio as _asyncio
410+
first_model = ""
411+
if paths:
412+
first_path = paths[0]
413+
first_model = first_path["model"] if isinstance(first_path, dict) else str(first_path)
414+
try:
415+
meta_sys_prompt = _asyncio.run(
416+
self._generate_meta_prompt(goal, messages, first_model)
417+
)
418+
except Exception:
419+
meta_sys_prompt = None
420+
316421
for path in paths:
317422
model_id = path["model"] if isinstance(path, dict) else str(path)
318423
if not model_id:
319424
continue
320425
if model_id not in models_tried:
321426
models_tried.append(model_id)
322427

323-
repair_system: Optional[str] = None
428+
repair_system: Optional[str] = meta_sys_prompt
324429

325430
for attempt in range(max_retries + 1):
326431
output = ""
@@ -372,7 +477,7 @@ def run(
372477
break
373478
if gate2_issues:
374479
repair = f"{repair} Gate 2 judge flagged: {gate2_issues}"
375-
repair_system = repair
480+
repair_system = f"{meta_sys_prompt}\n\n{repair}" if meta_sys_prompt else repair
376481
heal_count += 1
377482
continue
378483

@@ -388,7 +493,7 @@ def run(
388493
repair = self._repair_prompt(goal, output, messages, model_id)
389494
if not repair:
390495
break
391-
repair_system = repair
496+
repair_system = f"{meta_sys_prompt}\n\n{repair}" if meta_sys_prompt else repair
392497
heal_count += 1
393498

394499
return {
@@ -737,6 +842,7 @@ def _heal_dispatch(m_id: str, msgs: List[Dict], system_prompt: Optional[str] = N
737842
max_retries=cfg.max_retries,
738843
gate2_enabled=cfg.gate2_enabled,
739844
judge_model=cfg.judge_model,
845+
meta_prompt_enabled=cfg.meta_prompt_enabled,
740846
)
741847

742848
router_span.set_attribute("kalibr.healing", True)

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "kalibr"
7-
version = "1.14.0"
7+
version = "1.14.1"
88
description = "Outcome-aware LLM routing for production AI agents. Routes between models, tools, and parameters based on real success signals using Thompson Sampling. Automatic fallback, cost optimization, and continuous learning — no redeploy required."
99
authors = [{name = "Kalibr Team", email = "support@kalibr.systems"}]
1010
readme = "README.md"

0 commit comments

Comments
 (0)