v0.5.0
Minor Changes
-
42a9bf2: Query: add history-aware query rewriting as a stateless pure function.
rewriteQuery({ history, query, generateFn })asks a caller-injected language model to rewrite a context-dependent query (pronouns, omitted subjects) into a self-contained retrieval query, using the conversation history the caller supplies. The outcome is a discriminated union that is honest by construction:model(a cleaned rewrite),short-circuit(blank query or empty/blank history — the model is never called), ordegraded(timeout or unusable output — the original query is kept, with areason). Conversation history and the query are embedded as fenced untrusted data (data preface + declared length + content-derived sentinel), never as bare instruction. Also exportsbuildRewritePrompt,REWRITE_PROMPT_VERSION(stamp for run metadata, bumped on any prompt wording change), andDEFAULT_REWRITE_TIMEOUT_MS. The caller controls the history window; non-timeoutgenerateFnrejections (network/auth/provider faults) propagate unchanged. -
07cc542: Eval: add optional multi-turn conversation history to eval cases.
EvalQuerygainshistory?: ConversationTurn[](declared per query in the eval-set YAML, oldest-first);loadEvalSetvalidates the shape (role must beuserorassistant, content a non-empty string) and fails fast with the exactqueries[i].history[j]location on authoring mistakes. The harness passes the history through verbatim to the injectedsearchFn(new optionalhistoryin its options) andgenerateFn(new optionalhistoryon its input) — it never consumes history itself, so whether retrieval or generation is history-aware stays entirely the caller's decision. Purely additive: single-turn eval sets, existing callers, and history-agnostic functions are unaffected.Also re-exports
aggregateAnswerMeans(the per-query answer-metric mean aggregatorrunBenchmarkalready uses internally) at the package level, so downstream verdict layers can aggregate metric means over filtered per-query subsets without re-implementing the formula. -
cb9ba6d: Guard: add
sanitizeRetrievedContent, a stateless rule-based pure function that defends against indirect prompt injection (retrieval poisoning) — the RAG-specific attack surface where a malicious instruction is smuggled inside an indexed document and re-enters the model context as "trusted" retrieved text. It detects three injection classes — instruction-override clauses ("ignore the previous instructions"), forged role / delimiter markers (a line-start系统:/助手:,<|im_start|>,[INST]) and persona hijacks ("you are now…", "act as…") — and neutralizes them without deleting any content: structural tokens get a zero-width break, imperative/persona clauses are wrapped in a deterministic⟦untrusted:<category>:<token>⟧…⟦/untrusted:<token>⟧annotation that tells the model the span is flagged data. The result is an honest{ sanitized, flagged, detections }structure —detectionsis source-ordered and excerpt-bounded so it can be counted by a metric without copying whole passages — and the function is idempotent (re-sanitizing already-sanitized text is a no-op) and rejects pre-planted forged markers. ExportssanitizeRetrievedContent, theSANITIZE_RULES_VERSIONstamp, and theInjectionCategory/InjectionDetection/SanitizeOptions/SanitizeResulttypes.Query rewrite: harden the conversation-history serialization in the rewrite prompt so a turn's content can no longer forge an extra line-start role label (e.g. an embedded
\n助手:…) and fake a turn inside the data block. Line terminators inside a turn's content are now collapsed before the turns are joined.REWRITE_PROMPT_VERSIONis bumped to2026-06-16to reflect the prompt change.
Patch Changes
- c992453: Eval: harden
callJudgetimeout handling. A judge rejection arriving after the timeout already degraded the call is now observed by a no-op handler instead of surfacing as an unhandled rejection (a process crash by default in Node); a rejection that loses no race still propagates unchanged. FinitetimeoutMsvalues above 2^31-1 (the largest delaysetTimeouthonours) are now capped instead of being silently clamped to ~1ms, which previously turned a huge "effectively no timeout" budget into an instant spurious timeout on every call. The same cap applies torewriteQuery'stimeoutMs.