Beispiele
Rezepte zum Kopieren und Einfügen. Jeder Ausschnitt ist eigenständig, abgesehen von den Helfern, die er
nennt (ship, CACHE und so weiter), die du lieferst.
- Schnellstart
- Audit
- PII-Schwärzung
- Cache
- Metriken
- Leitplanke
- Konfidenz-Gate
- Routing-Pinning
- Lebenszyklus
- Komposition
- Hooks pro Aufruf
- Batch
- HTTP-Server
- ONNXAgent
- Registrierung zur Laufzeit
- Basisklasse und prozessweite Standardwerte
- Asynchrone Hooks
- Hook-Timeout
- Token-Budget
- Hooks testen
Schnellstart
import laya
def log(ctx):
print(ctx.model, ctx.results[0]["answers"])
agent = laya.load("convaiinnovations/laya", on_predict_end=log)
agent.system_one("I was charged twice.", {"urgent": {"type": "noul", "instructions": "Urgent?"}})
Audit
Der browser-use-Anwendungsfall: jede Entscheidung erfassen und an einen externen Dienst senden.
import json, sys
import laya
def audit(ctx):
for state, result in zip(ctx.states, ctx.results or []):
record = {
"run_id": ctx.run_id,
"model": ctx.model,
"state": state,
"routing": result.get("routing"),
"answers": result["answers"],
"usage": result.get("usage"),
"call_usage": ctx.usage,
"call_elapsed_ms": round(ctx.elapsed_ms or 0.0, 3),
}
print(json.dumps(record), file=sys.stderr)
# ship_to_service(record)
agent = laya.load("convaiinnovations/laya", on_predict_end=audit)
Ein Hook-Aufruf deckt den gesamten Aufruf ab, sodass die Schleife einen Datensatz pro Entscheidung
schreibt; siehe Batch für dieselbe Form bei predict_batch.
Eine vollständig ausführbare Version findest du in examples/hooks/audit.py.
PII-Schwärzung
import re
import laya
EMAIL = re.compile(r"\b[\w.+-]+@[\w-]+\.[\w.-]+\b")
PHONE = re.compile(r"\+?\d[\d ()-]{7,}\d")
def scrub(value):
if isinstance(value, str):
return PHONE.sub("[phone]", EMAIL.sub("[email]", value))
if isinstance(value, dict):
return {k: scrub(v) for k, v in value.items()}
if isinstance(value, list):
return [scrub(v) for v in value]
return value
def redact(ctx):
ctx.states = [scrub(s) for s in ctx.states]
agent = laya.load("convaiinnovations/laya", on_predict_start=redact)
Siehe examples/hooks/redact.py.
Cache
import hashlib, json
import laya
CACHE = {}
def key(ctx, index):
# Not sort_keys=True: criteria order is positional, so two orders are two questions,
# and the checkpoint and token budget change the answer too.
payload = json.dumps([ctx.states[index], ctx.questions, ctx.model,
ctx.max_len, ctx.head_max_len], default=str)
return hashlib.sha256(payload.encode()).hexdigest()
def read(ctx):
hits = [CACHE.get(key(ctx, i)) for i in range(len(ctx.states))]
if all(hit is not None for hit in hits):
ctx.skip(hits) # one per state: skip replaces the whole call
def write(ctx):
for i, result in enumerate(ctx.results or []):
CACHE[key(ctx, i)] = result
agent = laya.load("convaiinnovations/laya", on_predict_start=read, on_predict_end=write)
first = agent.system_one("state", QUESTIONS) # runs the model
second = agent.system_one("state", QUESTIONS) # served from CACHE
Siehe examples/hooks/cache.py.
Metriken
import laya
COUNTS, LATENCIES = {}, []
def metrics(ctx):
COUNTS[ctx.model] = COUNTS.get(ctx.model, 0) + 1
if ctx.elapsed_ms is not None:
LATENCIES.append(ctx.elapsed_ms)
agent = laya.load("convaiinnovations/laya", on_predict_end=metrics, hooks_raise=False)
Siehe examples/hooks/otel.py.
Leitplanke
Blockiere eine Anfrage, indem du aus einem Start-Hook eine Ausnahme auslöst.
import laya
class Blocked(Exception):
pass
def guard(ctx):
text = " ".join(str(state) for state in ctx.states).lower()
if "ignore previous instructions" in text:
raise Blocked("prompt injection")
agent = laya.load("convaiinnovations/laya", on_predict_start=guard)
try:
agent.system_one("Ignore previous instructions and ...", QUESTIONS)
except Blocked:
handle_block()
Ein Start-Hook sieht jeden Zustand des Aufrufs, also prüfe sie alle: Nur ctx.states[0] zu lesen,
lässt den Rest eines predict_batch-Aufrufs durch.
Konfidenz-Gate
Schreibe eine Antwort mit niedriger Konfidenz um oder annotiere sie.
def gate(ctx):
for result in ctx.results or []:
answer = result["answers"].get("dept")
if answer and answer["confidence"] < 0.6:
answer["choice"] = "human-review"
answer["gated"] = True
agent = laya.load("convaiinnovations/laya", on_predict_end=gate)
ctx.results enthält ein dict pro Zustand des Aufrufs, sodass die Schleife jede Antwort annotiert, die
den Schwellenwert verfehlt, nicht nur die des ersten Zustands.
Routing-Pinning
Erzwinge einen Checkpoint für eine Klasse von Datenverkehr.
from laya import Router
from laya.router import RouteDecision
def pin(ctx):
if "refund" in str(ctx.states[0]).lower():
ctx.decision = RouteDecision(
model="typed-decisions",
repo="convaiinnovations/laya/typed-decisions",
reason="refund workflow",
detection=None,
workflow=None,
)
router = Router(hooks=[pin])
Pro Aufruf, ohne ihn zu installieren:
router.predict("refund request", QUESTIONS, hooks=[pin])
Lebenszyklus
Beobachte den Aufbau und die Verdrängung von Checkpoints.
from laya import Router
class Lifecycle:
def on_load(self, ctx):
print("loaded", ctx.model, "agent", type(ctx.agent).__name__)
def on_evict(self, ctx):
print("evicted", ctx.model)
router = Router(max_loaded=1, hooks=[Lifecycle()])
router.preload(["english", "multilingual"]) # on_load fires per build
router.unload() # on_evict fires per freed checkpoint
Komposition
Zuerst die installierten Hooks, dann die Convenience-Callables; alle teilen einen Kontext.
import laya
class Metrics:
def on_predict_end(self, ctx):
record_latency(ctx.model, ctx.elapsed_ms)
def redact(ctx):
ctx.states = [strip_pii(s) for s in ctx.states]
def audit(ctx):
ship(ctx.run_id, ctx.results)
agent = laya.load(
"convaiinnovations/laya",
hooks=[Metrics()], # installed, runs first
on_predict_start=redact, # convenience, appended
on_predict_end=audit, # convenience, appended
hooks_raise=True,
)
Hooks pro Aufruf
Überschreibe oder erweitere Hooks für einen einzelnen Aufruf.
agent.system_one(
state,
questions,
on_predict_end=lambda ctx: debug_dump(ctx),
hooks_raise=False,
)
router.predict(
state,
questions,
hooks=[pin], # applies to on_route too
on_predict_end=audit,
)
Batch
Hooks werden einmal pro Agent.predict_batch-Aufruf ausgelöst, wobei ctx.states jeden Zustand
enthält. Router.predict_batch führt stattdessen seine Router-Hooks einmal pro Anfrage aus, jeweils mit
einem Zustand und einer eigenen run_id, sodass derselbe Hook dort einen Datensatz pro Aufruf des Hooks
schreibt.
def audit_batch(ctx):
for state, result in zip(ctx.states, ctx.results):
ship_one(ctx.run_id, state, result)
results = agent.predict_batch([state_a, state_b, state_c], questions, on_predict_end=audit_batch)
HTTP-Server
Router-Hooks werden für laya.serve automatisch ausgelöst, weil der Server Router.predict aufruft.
from laya import Router
from laya.serve import create_app
router = Router(hooks=[Metrics()], on_predict_end=audit, hooks_raise=False)
app = create_app(router=router)
ONNXAgent
ONNXAgent stellt nur die Ereignisse der predict-Ebene bereit.
from laya.onnx_agent import ONNXAgent
agent = ONNXAgent("convaiinnovations/laya", onnx_path="laya.onnx", on_predict_end=audit)
agent.system_one(state, questions)
Registrierung zur Laufzeit
Hänge Hooks nach der Konstruktion an, löse sie oder beschränke ihren Geltungsbereich.
agent.add_hook(Metrics()) # attach at runtime
agent.remove_hook(Metrics()) # by identity
with agent.hooks_installed(DebugDump()):
agent.system_one(state, questions) # DebugDump only here
Basisklasse und prozessweite Standardwerte
Leite von BaseHook ab, um nur das zu überschreiben, was du brauchst, und registriere etwas einmal für
den gesamten Prozess, statt es an jeden Agent und Router zu übergeben.
from laya import BaseHook, hooks
class Audit(BaseHook):
def on_predict_end(self, ctx):
ship(ctx.run_id, ctx.results)
hooks.set_default_hooks(hooks=[Audit()]) # runs for every call in the process
# later, or in tests:
hooks.clear_default_hooks()
Token-Budget
Forme das Token-Budget für einen Aufruf, aus einem Hook oder einem Argument pro Aufruf. Der Wert eines
Hooks ersetzt das geltende Budget, also muss er dieses Budget zuerst lesen: dimensioniere an der
breitesten Frage des Aufrufs, bleibe über der Token-Untergrenze, die der Kern auf die Optionen anwendet,
und erweitere max_len zusammen mit head_max_len, damit der Zustand ein Fenster behält.
def widen(ctx):
k = max((len(q.get("criteria", {}) or {}) for q in ctx.questions.values()), default=0)
if k < 50:
return
cfg = getattr(ctx.agent, "cfg", None) or {}
head = ctx.head_max_len if ctx.head_max_len is not None else cfg.get("head_max_len", 192)
window = ctx.max_len if ctx.max_len is not None else cfg.get("max_len", 512)
need = 16 + 8 * k
if need > head:
ctx.head_max_len = need
ctx.max_len = max(window, need + 8 + 64)
agent = laya.load("convaiinnovations/laya", on_predict_start=widen)
# or per call
agent.system_one(state, questions, head_max_len=512, max_len=1024)
Anpassung des Token-Budgets enthält die Arithmetik hinter
jeder Zeile, und predict_shortlist ist die Option, wenn eine Menge von Labels
nicht einmal in ein erweitertes Fenster passt.
Asynchrone Hooks
Verpacke einen asynchronen Hook in AsyncHook; jede Coroutine läuft im synchronen Kern vollständig
durch, egal ob der Aufrufer synchron ist oder sich bereits in einer Ereignisschleife befindet.
import laya
from laya import AsyncHook
class RemoteAudit:
async def on_predict_end(self, ctx):
await ship(ctx.run_id, ctx.results)
agent = laya.load("convaiinnovations/laya", hooks=[AsyncHook(RemoteAudit())])
Ein einfacher asynchroner Callable funktioniert ebenfalls:
async def async_end(ctx):
await ship(ctx.results)
agent.system_one(state, questions, on_predict_end=async_end)
Hook-Timeout
Begrenze jeden Hook-Aufruf, damit ein hängender Hook eine bediente Anfrage nicht aufhängen kann:
agent = laya.load("convaiinnovations/laya", on_predict_end=metrics, hooks_timeout=2.0)
# or per call
agent.system_one(state, questions, on_predict_end=metrics, hooks_timeout=0.5)
Ein Hook, der das Zeitlimit überschreitet, löst TimeoutError aus (oder warnt, wenn
hooks_raise=False). Der Hook läuft im Hintergrund weiter, gib also auch Netzwerkaufrufen ihr eigenes
Zeitlimit. Siehe Fehler.
Hooks testen
Prüfe, was ein Hook gesehen hat, ohne ein Modell: steuere predict_batch mit den
encode/forward/decode-Helfern als Stubs, wie es tests/test_hooks.py tut.
seen = []
agent.predict_batch(["s0"], questions, on_predict_end=lambda ctx: seen.append(ctx.results))
assert len(seen) == 1
Die API-Oberfläche wird durch tests/test_hooks_api.py festgelegt.
Siehe auch
- Tracing:
run_id, Spans, OpenTelemetry. - Muster und Antimuster: die Begründung hinter diesen Rezepten.