@warlock.js/ai 4.3.0 → 4.5.0
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.
- package/CHANGELOG.md +52 -48
- package/cjs/index.cjs +221 -14144
- package/cjs/src-C02yzsLs.cjs +22991 -0
- package/cjs/src-C02yzsLs.cjs.map +1 -0
- package/cjs/src-DFibP2FQ.cjs +30 -0
- package/esm/agent/agent-config.type.d.mts +90 -1
- package/esm/agent/agent-config.type.d.mts.map +1 -1
- package/esm/agent/agent-input-builder.mjs +27 -6
- package/esm/agent/agent-input-builder.mjs.map +1 -1
- package/esm/agent/agent.d.mts +40 -1
- package/esm/agent/agent.d.mts.map +1 -1
- package/esm/agent/agent.mjs +203 -30
- package/esm/agent/agent.mjs.map +1 -1
- package/esm/agent/index.d.mts +2 -1
- package/esm/agent/index.mjs +1 -0
- package/esm/agent/judge-config.type.d.mts +33 -0
- package/esm/agent/judge-config.type.d.mts.map +1 -0
- package/esm/agent/judge-config.type.mjs +13 -0
- package/esm/agent/judge-config.type.mjs.map +1 -0
- package/esm/ai-openai/src/embedder.mjs +4 -0
- package/esm/ai-openai/src/index.mjs +4 -0
- package/esm/ai-openai/src/model.mjs +5 -0
- package/esm/ai-openai/src/sdk.mjs +6 -0
- package/esm/ai-openai/src/utils/index.mjs +4 -0
- package/esm/ai-openai/src/utils/to-openai-tools.mjs +3 -0
- package/esm/ai-openai/src/utils/wrap-openai-error.mjs +4 -0
- package/esm/ai.d.mts +119 -53
- package/esm/ai.d.mts.map +1 -1
- package/esm/ai.mjs +43 -8
- package/esm/ai.mjs.map +1 -1
- package/esm/batch/batch.d.mts.map +1 -1
- package/esm/batch/batch.mjs +21 -1
- package/esm/batch/batch.mjs.map +1 -1
- package/esm/batch/batch.type.d.mts +11 -4
- package/esm/batch/batch.type.d.mts.map +1 -1
- package/esm/config.d.mts +39 -3
- package/esm/config.d.mts.map +1 -1
- package/esm/config.mjs +26 -2
- package/esm/config.mjs.map +1 -1
- package/esm/contracts/agent/agent-options.type.d.mts +11 -3
- package/esm/contracts/agent/agent-options.type.d.mts.map +1 -1
- package/esm/contracts/agent/eval.type.d.mts +43 -2
- package/esm/contracts/agent/eval.type.d.mts.map +1 -1
- package/esm/contracts/attachment-policy.type.d.mts +51 -0
- package/esm/contracts/attachment-policy.type.d.mts.map +1 -0
- package/esm/contracts/attachment.type.d.mts +16 -7
- package/esm/contracts/attachment.type.d.mts.map +1 -1
- package/esm/contracts/content-part.type.d.mts +14 -5
- package/esm/contracts/content-part.type.d.mts.map +1 -1
- package/esm/contracts/events/supervisor-events.type.d.mts.map +1 -1
- package/esm/contracts/index.d.mts +6 -4
- package/esm/contracts/model.contract.d.mts +11 -8
- package/esm/contracts/model.contract.d.mts.map +1 -1
- package/esm/contracts/orchestrator/index.d.mts +1 -0
- package/esm/contracts/orchestrator/orchestrator-config.type.d.mts +31 -2
- package/esm/contracts/orchestrator/orchestrator-config.type.d.mts.map +1 -1
- package/esm/contracts/orchestrator/session-lock.contract.d.mts +47 -0
- package/esm/contracts/orchestrator/session-lock.contract.d.mts.map +1 -0
- package/esm/contracts/planner/index.d.mts +1 -1
- package/esm/contracts/planner/planner-config.type.d.mts +31 -0
- package/esm/contracts/planner/planner-config.type.d.mts.map +1 -1
- package/esm/contracts/planner/planner-execute-options.type.d.mts +48 -1
- package/esm/contracts/planner/planner-execute-options.type.d.mts.map +1 -1
- package/esm/contracts/planner/planner-result.type.d.mts +19 -5
- package/esm/contracts/planner/planner-result.type.d.mts.map +1 -1
- package/esm/contracts/planner/planner.contract.d.mts +1 -1
- package/esm/contracts/result/agent-result.type.d.mts +6 -4
- package/esm/contracts/result/agent-result.type.d.mts.map +1 -1
- package/esm/contracts/result/base-report.type.d.mts +21 -3
- package/esm/contracts/result/base-report.type.d.mts.map +1 -1
- package/esm/contracts/result/base-report.type.mjs.map +1 -1
- package/esm/contracts/result/execution-report.type.d.mts +53 -1
- package/esm/contracts/result/execution-report.type.d.mts.map +1 -1
- package/esm/contracts/result/orchestrator-result.type.d.mts +15 -11
- package/esm/contracts/result/orchestrator-result.type.d.mts.map +1 -1
- package/esm/contracts/result/supervisor-result.type.d.mts +13 -1
- package/esm/contracts/result/supervisor-result.type.d.mts.map +1 -1
- package/esm/contracts/result/tool-call.type.d.mts +2 -2
- package/esm/contracts/result/tool-call.type.d.mts.map +1 -1
- package/esm/contracts/result/workflow-result.type.d.mts +1 -0
- package/esm/contracts/result/workflow-result.type.d.mts.map +1 -1
- package/esm/contracts/supervisor/supervisor-config.type.d.mts +29 -0
- package/esm/contracts/supervisor/supervisor-config.type.d.mts.map +1 -1
- package/esm/contracts/system-prompt.contract.d.mts +103 -1
- package/esm/contracts/system-prompt.contract.d.mts.map +1 -1
- package/esm/contracts/team/index.d.mts +1 -0
- package/esm/contracts/team/team-config.type.d.mts +127 -0
- package/esm/contracts/team/team-config.type.d.mts.map +1 -0
- package/esm/contracts/tool.contract.d.mts +4 -2
- package/esm/contracts/tool.contract.d.mts.map +1 -1
- package/esm/contracts/workflow/step.contract.d.mts +30 -5
- package/esm/contracts/workflow/step.contract.d.mts.map +1 -1
- package/esm/contracts/workflow/workflow.contract.d.mts +16 -0
- package/esm/contracts/workflow/workflow.contract.d.mts.map +1 -1
- package/esm/errors/error-code.type.d.mts +1 -1
- package/esm/errors/index.d.mts +1 -0
- package/esm/errors/index.mjs +1 -0
- package/esm/errors/outbound-policy-error.d.mts +27 -0
- package/esm/errors/outbound-policy-error.d.mts.map +1 -0
- package/esm/errors/outbound-policy-error.mjs +32 -0
- package/esm/errors/outbound-policy-error.mjs.map +1 -0
- package/esm/eval/dataset.d.mts +28 -0
- package/esm/eval/dataset.d.mts.map +1 -0
- package/esm/eval/dataset.mjs +112 -0
- package/esm/eval/dataset.mjs.map +1 -0
- package/esm/eval/dataset.type.d.mts +53 -0
- package/esm/eval/dataset.type.d.mts.map +1 -0
- package/esm/eval/eval-runner.d.mts.map +1 -1
- package/esm/eval/eval-runner.mjs +16 -2
- package/esm/eval/eval-runner.mjs.map +1 -1
- package/esm/eval/index.d.mts +20 -1
- package/esm/eval/index.d.mts.map +1 -1
- package/esm/eval/index.mjs +20 -2
- package/esm/eval/index.mjs.map +1 -1
- package/esm/eval/regression.d.mts +30 -0
- package/esm/eval/regression.d.mts.map +1 -0
- package/esm/eval/regression.mjs +51 -0
- package/esm/eval/regression.mjs.map +1 -0
- package/esm/eval/report-json.d.mts +30 -0
- package/esm/eval/report-json.d.mts.map +1 -0
- package/esm/eval/report-json.mjs +33 -0
- package/esm/eval/report-json.mjs.map +1 -0
- package/esm/eval/report-junit.d.mts +22 -0
- package/esm/eval/report-junit.d.mts.map +1 -0
- package/esm/eval/report-junit.mjs +60 -0
- package/esm/eval/report-junit.mjs.map +1 -0
- package/esm/guard/contracts/guard-options.type.d.mts +165 -0
- package/esm/guard/contracts/guard-options.type.d.mts.map +1 -0
- package/esm/guard/contracts/guardrail.contract.d.mts +78 -0
- package/esm/guard/contracts/guardrail.contract.d.mts.map +1 -0
- package/esm/guard/contracts/index.d.mts +4 -0
- package/esm/guard/contracts/openai-client.contract.d.mts +51 -0
- package/esm/guard/contracts/openai-client.contract.d.mts.map +1 -0
- package/esm/guard/contracts/verdict.type.d.mts +76 -0
- package/esm/guard/contracts/verdict.type.d.mts.map +1 -0
- package/esm/guard/detectors/index.d.mts +4 -0
- package/esm/guard/detectors/index.mjs +6 -0
- package/esm/guard/detectors/injection.d.mts +34 -0
- package/esm/guard/detectors/injection.d.mts.map +1 -0
- package/esm/guard/detectors/injection.mjs +254 -0
- package/esm/guard/detectors/injection.mjs.map +1 -0
- package/esm/guard/detectors/moderation.d.mts +32 -0
- package/esm/guard/detectors/moderation.d.mts.map +1 -0
- package/esm/guard/detectors/moderation.mjs +134 -0
- package/esm/guard/detectors/moderation.mjs.map +1 -0
- package/esm/guard/detectors/pii.d.mts +41 -0
- package/esm/guard/detectors/pii.d.mts.map +1 -0
- package/esm/guard/detectors/pii.mjs +199 -0
- package/esm/guard/detectors/pii.mjs.map +1 -0
- package/esm/guard/detectors/topic.d.mts +29 -0
- package/esm/guard/detectors/topic.d.mts.map +1 -0
- package/esm/guard/detectors/topic.mjs +99 -0
- package/esm/guard/detectors/topic.mjs.map +1 -0
- package/esm/guard/errors.d.mts +12 -0
- package/esm/guard/errors.d.mts.map +1 -0
- package/esm/guard/errors.mjs +18 -0
- package/esm/guard/errors.mjs.map +1 -0
- package/esm/guard/guard.d.mts +67 -0
- package/esm/guard/guard.d.mts.map +1 -0
- package/esm/guard/guard.mjs +209 -0
- package/esm/guard/guard.mjs.map +1 -0
- package/esm/guard/guardrail.d.mts +39 -0
- package/esm/guard/guardrail.d.mts.map +1 -0
- package/esm/guard/guardrail.mjs +22 -0
- package/esm/guard/guardrail.mjs.map +1 -0
- package/esm/human/contracts/approval.type.d.mts +154 -0
- package/esm/human/contracts/approval.type.d.mts.map +1 -0
- package/esm/human/contracts/human-approval.type.d.mts +38 -0
- package/esm/human/contracts/human-approval.type.d.mts.map +1 -0
- package/esm/human/contracts/index.d.mts +4 -0
- package/esm/human/contracts/interrupt-store.contract.d.mts +112 -0
- package/esm/human/contracts/interrupt-store.contract.d.mts.map +1 -0
- package/esm/human/contracts/resume.type.d.mts +77 -0
- package/esm/human/contracts/resume.type.d.mts.map +1 -0
- package/esm/human/errors.d.mts +84 -0
- package/esm/human/errors.d.mts.map +1 -0
- package/esm/human/errors.mjs +60 -0
- package/esm/human/errors.mjs.map +1 -0
- package/esm/human/human-approval.d.mts +57 -0
- package/esm/human/human-approval.d.mts.map +1 -0
- package/esm/human/human-approval.mjs +170 -0
- package/esm/human/human-approval.mjs.map +1 -0
- package/esm/human/policy.d.mts +55 -0
- package/esm/human/policy.d.mts.map +1 -0
- package/esm/human/policy.mjs +67 -0
- package/esm/human/policy.mjs.map +1 -0
- package/esm/human/register.mjs +37 -0
- package/esm/human/register.mjs.map +1 -0
- package/esm/human/resume-seed.mjs +53 -0
- package/esm/human/resume-seed.mjs.map +1 -0
- package/esm/human/resume.d.mts +54 -0
- package/esm/human/resume.d.mts.map +1 -0
- package/esm/human/resume.mjs +121 -0
- package/esm/human/resume.mjs.map +1 -0
- package/esm/human/stores/index.mjs +5 -0
- package/esm/human/stores/memory.d.mts +30 -0
- package/esm/human/stores/memory.d.mts.map +1 -0
- package/esm/human/stores/memory.mjs +91 -0
- package/esm/human/stores/memory.mjs.map +1 -0
- package/esm/human/stores/pg.d.mts +59 -0
- package/esm/human/stores/pg.d.mts.map +1 -0
- package/esm/human/stores/pg.mjs +220 -0
- package/esm/human/stores/pg.mjs.map +1 -0
- package/esm/human/stores/redis.d.mts +56 -0
- package/esm/human/stores/redis.d.mts.map +1 -0
- package/esm/human/stores/redis.mjs +201 -0
- package/esm/human/stores/redis.mjs.map +1 -0
- package/esm/index.d.mts +113 -22
- package/esm/index.mjs +82 -11
- package/esm/middleware/builtins/budget.mjs +6 -2
- package/esm/middleware/builtins/budget.mjs.map +1 -1
- package/esm/middleware/utils/extract-user-text.d.mts +8 -1
- package/esm/middleware/utils/extract-user-text.d.mts.map +1 -1
- package/esm/middleware/utils/extract-user-text.mjs +8 -1
- package/esm/middleware/utils/extract-user-text.mjs.map +1 -1
- package/esm/object-stream/index.d.mts +2 -0
- package/esm/object-stream/index.mjs +4 -0
- package/esm/object-stream/parse-partial-json.d.mts +22 -0
- package/esm/object-stream/parse-partial-json.d.mts.map +1 -0
- package/esm/object-stream/parse-partial-json.mjs +78 -0
- package/esm/object-stream/parse-partial-json.mjs.map +1 -0
- package/esm/object-stream/stream-object.d.mts +68 -0
- package/esm/object-stream/stream-object.d.mts.map +1 -0
- package/esm/object-stream/stream-object.mjs +104 -0
- package/esm/object-stream/stream-object.mjs.map +1 -0
- package/esm/observe/index.mjs +4 -0
- package/esm/observe/observer-registry.d.mts +30 -0
- package/esm/observe/observer-registry.d.mts.map +1 -0
- package/esm/observe/observer-registry.mjs +51 -0
- package/esm/observe/observer-registry.mjs.map +1 -0
- package/esm/observe/observer.contract.d.mts +40 -0
- package/esm/observe/observer.contract.d.mts.map +1 -0
- package/esm/observe/resolve-observers.d.mts +40 -0
- package/esm/observe/resolve-observers.d.mts.map +1 -0
- package/esm/observe/resolve-observers.mjs +73 -0
- package/esm/observe/resolve-observers.mjs.map +1 -0
- package/esm/orchestrator/execution.d.mts.map +1 -1
- package/esm/orchestrator/execution.mjs +5 -2
- package/esm/orchestrator/execution.mjs.map +1 -1
- package/esm/orchestrator/index.d.mts +1 -0
- package/esm/orchestrator/index.mjs +1 -0
- package/esm/orchestrator/orchestrator.d.mts.map +1 -1
- package/esm/orchestrator/orchestrator.mjs +39 -6
- package/esm/orchestrator/orchestrator.mjs.map +1 -1
- package/esm/orchestrator/session-lock.d.mts +25 -0
- package/esm/orchestrator/session-lock.d.mts.map +1 -0
- package/esm/orchestrator/session-lock.mjs +83 -0
- package/esm/orchestrator/session-lock.mjs.map +1 -0
- package/esm/planner/dag-scheduler.mjs +97 -0
- package/esm/planner/dag-scheduler.mjs.map +1 -0
- package/esm/planner/plan-prompt.d.mts +1 -1
- package/esm/planner/plan-prompt.d.mts.map +1 -1
- package/esm/planner/plan-prompt.mjs +2 -1
- package/esm/planner/plan-prompt.mjs.map +1 -1
- package/esm/planner/plan-schema.mjs +17 -14
- package/esm/planner/plan-schema.mjs.map +1 -1
- package/esm/planner/planner-run.d.mts.map +1 -1
- package/esm/planner/planner-run.mjs +300 -29
- package/esm/planner/planner-run.mjs.map +1 -1
- package/esm/planner/planner.mjs +1 -1
- package/esm/planner/planner.mjs.map +1 -1
- package/esm/prompt/errors.d.mts +57 -0
- package/esm/prompt/errors.d.mts.map +1 -0
- package/esm/prompt/errors.mjs +73 -0
- package/esm/prompt/errors.mjs.map +1 -0
- package/esm/prompt/index.d.mts +3 -0
- package/esm/prompt/index.mjs +4 -0
- package/esm/prompt/prompt-langfuse-sync.mjs +104 -0
- package/esm/prompt/prompt-langfuse-sync.mjs.map +1 -0
- package/esm/prompt/prompt-langfuse-sync.type.d.mts +32 -0
- package/esm/prompt/prompt-langfuse-sync.type.d.mts.map +1 -0
- package/esm/prompt/prompt-validate.mjs +170 -0
- package/esm/prompt/prompt-validate.mjs.map +1 -0
- package/esm/prompt/prompt.d.mts +54 -0
- package/esm/prompt/prompt.d.mts.map +1 -0
- package/esm/prompt/prompt.mjs +218 -0
- package/esm/prompt/prompt.mjs.map +1 -0
- package/esm/prompt/prompt.type.d.mts +174 -0
- package/esm/prompt/prompt.type.d.mts.map +1 -0
- package/esm/prompts/index.d.mts +3 -0
- package/esm/prompts/index.mjs +3 -0
- package/esm/prompts/prompts-manager.contract.d.mts +154 -0
- package/esm/prompts/prompts-manager.contract.d.mts.map +1 -0
- package/esm/prompts/prompts-manager.d.mts +38 -0
- package/esm/prompts/prompts-manager.d.mts.map +1 -0
- package/esm/prompts/prompts-manager.mjs +410 -0
- package/esm/prompts/prompts-manager.mjs.map +1 -0
- package/esm/prompts/prompts-manager.type.d.mts +172 -0
- package/esm/prompts/prompts-manager.type.d.mts.map +1 -0
- package/esm/prompts/prompts-validate.mjs +200 -0
- package/esm/prompts/prompts-validate.mjs.map +1 -0
- package/esm/rag/as-tool.mjs +48 -0
- package/esm/rag/as-tool.mjs.map +1 -0
- package/esm/rag/chunk/chunk.d.mts +24 -0
- package/esm/rag/chunk/chunk.d.mts.map +1 -0
- package/esm/rag/chunk/chunk.mjs +44 -0
- package/esm/rag/chunk/chunk.mjs.map +1 -0
- package/esm/rag/chunk/fixed.mjs +32 -0
- package/esm/rag/chunk/fixed.mjs.map +1 -0
- package/esm/rag/chunk/markdown.mjs +75 -0
- package/esm/rag/chunk/markdown.mjs.map +1 -0
- package/esm/rag/chunk/recursive.mjs +132 -0
- package/esm/rag/chunk/recursive.mjs.map +1 -0
- package/esm/rag/chunk/sentence.mjs +73 -0
- package/esm/rag/chunk/sentence.mjs.map +1 -0
- package/esm/rag/contracts/chunk-options.type.d.mts +35 -0
- package/esm/rag/contracts/chunk-options.type.d.mts.map +1 -0
- package/esm/rag/contracts/citation.type.d.mts +35 -0
- package/esm/rag/contracts/citation.type.d.mts.map +1 -0
- package/esm/rag/contracts/index.d.mts +4 -0
- package/esm/rag/contracts/rag-config.type.d.mts +68 -0
- package/esm/rag/contracts/rag-config.type.d.mts.map +1 -0
- package/esm/rag/contracts/rag-document.type.d.mts +21 -0
- package/esm/rag/contracts/rag-document.type.d.mts.map +1 -0
- package/esm/rag/hybrid/bm25.d.mts +23 -0
- package/esm/rag/hybrid/bm25.d.mts.map +1 -0
- package/esm/rag/hybrid/bm25.mjs +51 -0
- package/esm/rag/hybrid/bm25.mjs.map +1 -0
- package/esm/rag/hybrid/hybrid-rank.d.mts +33 -0
- package/esm/rag/hybrid/hybrid-rank.d.mts.map +1 -0
- package/esm/rag/hybrid/hybrid-rank.mjs +29 -0
- package/esm/rag/hybrid/hybrid-rank.mjs.map +1 -0
- package/esm/rag/hybrid/rrf.d.mts +25 -0
- package/esm/rag/hybrid/rrf.d.mts.map +1 -0
- package/esm/rag/hybrid/rrf.mjs +30 -0
- package/esm/rag/hybrid/rrf.mjs.map +1 -0
- package/esm/rag/index.d.mts +15 -0
- package/esm/rag/index.mjs +11 -0
- package/esm/rag/rag.d.mts +38 -0
- package/esm/rag/rag.d.mts.map +1 -0
- package/esm/rag/rag.mjs +126 -0
- package/esm/rag/rag.mjs.map +1 -0
- package/esm/rag/rerank/keyword-reranker.d.mts +32 -0
- package/esm/rag/rerank/keyword-reranker.d.mts.map +1 -0
- package/esm/rag/rerank/keyword-reranker.mjs +58 -0
- package/esm/rag/rerank/keyword-reranker.mjs.map +1 -0
- package/esm/rag/rerank/llm-reranker.d.mts +36 -0
- package/esm/rag/rerank/llm-reranker.d.mts.map +1 -0
- package/esm/rag/rerank/llm-reranker.mjs +85 -0
- package/esm/rag/rerank/llm-reranker.mjs.map +1 -0
- package/esm/rag/rerank/reranker.contract.d.mts +28 -0
- package/esm/rag/rerank/reranker.contract.d.mts.map +1 -0
- package/esm/rag/retrieve.mjs +68 -0
- package/esm/rag/retrieve.mjs.map +1 -0
- package/esm/rag/store/cache-vector-store.d.mts +27 -0
- package/esm/rag/store/cache-vector-store.d.mts.map +1 -0
- package/esm/rag/store/cache-vector-store.mjs +48 -0
- package/esm/rag/store/cache-vector-store.mjs.map +1 -0
- package/esm/rag/store/vector-store.contract.d.mts +38 -0
- package/esm/rag/store/vector-store.contract.d.mts.map +1 -0
- package/esm/rag/transforms/multi-query.d.mts +27 -0
- package/esm/rag/transforms/multi-query.d.mts.map +1 -0
- package/esm/rag/transforms/multi-query.mjs +41 -0
- package/esm/rag/transforms/multi-query.mjs.map +1 -0
- package/esm/security/index.mjs +5 -0
- package/esm/security/outbound-policy.d.mts +46 -0
- package/esm/security/outbound-policy.d.mts.map +1 -0
- package/esm/security/outbound-policy.mjs +187 -0
- package/esm/security/outbound-policy.mjs.map +1 -0
- package/esm/security/outbound-policy.type.d.mts +74 -0
- package/esm/security/outbound-policy.type.d.mts.map +1 -0
- package/esm/security/private-ip.d.mts +15 -0
- package/esm/security/private-ip.d.mts.map +1 -0
- package/esm/security/private-ip.mjs +48 -0
- package/esm/security/private-ip.mjs.map +1 -0
- package/esm/security/redact.d.mts +59 -0
- package/esm/security/redact.d.mts.map +1 -0
- package/esm/security/redact.mjs +122 -0
- package/esm/security/redact.mjs.map +1 -0
- package/esm/serve/serve.d.mts +50 -0
- package/esm/serve/serve.d.mts.map +1 -0
- package/esm/serve/serve.mjs +90 -0
- package/esm/serve/serve.mjs.map +1 -0
- package/esm/serve/sse.d.mts +20 -0
- package/esm/serve/sse.d.mts.map +1 -0
- package/esm/serve/sse.mjs +25 -0
- package/esm/serve/sse.mjs.map +1 -0
- package/esm/serve/stream-to-sse.d.mts +29 -0
- package/esm/serve/stream-to-sse.d.mts.map +1 -0
- package/esm/serve/stream-to-sse.mjs +37 -0
- package/esm/serve/stream-to-sse.mjs.map +1 -0
- package/esm/skills/catalog.d.mts +49 -0
- package/esm/skills/catalog.d.mts.map +1 -0
- package/esm/skills/catalog.mjs +140 -0
- package/esm/skills/catalog.mjs.map +1 -0
- package/esm/skills/contracts/skill-record.type.d.mts +37 -0
- package/esm/skills/contracts/skill-record.type.d.mts.map +1 -0
- package/esm/skills/contracts/skills-config.type.d.mts +108 -0
- package/esm/skills/contracts/skills-config.type.d.mts.map +1 -0
- package/esm/skills/contracts/skills-store.contract.d.mts +28 -0
- package/esm/skills/contracts/skills-store.contract.d.mts.map +1 -0
- package/esm/skills/contracts/skills.contract.d.mts +43 -0
- package/esm/skills/contracts/skills.contract.d.mts.map +1 -0
- package/esm/skills/index.d.mts +16 -0
- package/esm/skills/index.mjs +14 -0
- package/esm/skills/load-skill-tool.d.mts +38 -0
- package/esm/skills/load-skill-tool.d.mts.map +1 -0
- package/esm/skills/load-skill-tool.mjs +65 -0
- package/esm/skills/load-skill-tool.mjs.map +1 -0
- package/esm/skills/review-gate.d.mts +33 -0
- package/esm/skills/review-gate.d.mts.map +1 -0
- package/esm/skills/review-gate.mjs +60 -0
- package/esm/skills/review-gate.mjs.map +1 -0
- package/esm/skills/save-skill-tool.d.mts +39 -0
- package/esm/skills/save-skill-tool.d.mts.map +1 -0
- package/esm/skills/save-skill-tool.mjs +65 -0
- package/esm/skills/save-skill-tool.mjs.map +1 -0
- package/esm/skills/skills.d.mts +33 -0
- package/esm/skills/skills.d.mts.map +1 -0
- package/esm/skills/skills.mjs +109 -0
- package/esm/skills/skills.mjs.map +1 -0
- package/esm/skills/sources/directory-source.d.mts +19 -0
- package/esm/skills/sources/directory-source.d.mts.map +1 -0
- package/esm/skills/sources/directory-source.mjs +108 -0
- package/esm/skills/sources/directory-source.mjs.map +1 -0
- package/esm/skills/sources/index.d.mts +18 -0
- package/esm/skills/sources/index.d.mts.map +1 -0
- package/esm/skills/sources/index.mjs +27 -0
- package/esm/skills/sources/index.mjs.map +1 -0
- package/esm/skills/sources/parse-frontmatter.d.mts +27 -0
- package/esm/skills/sources/parse-frontmatter.d.mts.map +1 -0
- package/esm/skills/sources/parse-frontmatter.mjs +46 -0
- package/esm/skills/sources/parse-frontmatter.mjs.map +1 -0
- package/esm/skills/sources/store-source.d.mts +14 -0
- package/esm/skills/sources/store-source.d.mts.map +1 -0
- package/esm/skills/sources/store-source.mjs +15 -0
- package/esm/skills/sources/store-source.mjs.map +1 -0
- package/esm/skills/sources/url-source.d.mts +29 -0
- package/esm/skills/sources/url-source.d.mts.map +1 -0
- package/esm/skills/sources/url-source.mjs +117 -0
- package/esm/skills/sources/url-source.mjs.map +1 -0
- package/esm/skills/store/mock-skills-store.d.mts +57 -0
- package/esm/skills/store/mock-skills-store.d.mts.map +1 -0
- package/esm/skills/store/mock-skills-store.mjs +100 -0
- package/esm/skills/store/mock-skills-store.mjs.map +1 -0
- package/esm/skills/store/procedural-skill-store.d.mts +30 -0
- package/esm/skills/store/procedural-skill-store.d.mts.map +1 -0
- package/esm/skills/store/procedural-skill-store.mjs +125 -0
- package/esm/skills/store/procedural-skill-store.mjs.map +1 -0
- package/esm/supervisor/as-tool.mjs +2 -2
- package/esm/supervisor/as-tool.mjs.map +1 -1
- package/esm/supervisor/execution.d.mts.map +1 -1
- package/esm/supervisor/execution.mjs +31 -28
- package/esm/supervisor/execution.mjs.map +1 -1
- package/esm/supervisor/supervisor.d.mts.map +1 -1
- package/esm/supervisor/supervisor.mjs +8 -3
- package/esm/supervisor/supervisor.mjs.map +1 -1
- package/esm/system-prompt/index.d.mts +4 -0
- package/esm/system-prompt/system-prompt.d.mts +68 -4
- package/esm/system-prompt/system-prompt.d.mts.map +1 -1
- package/esm/system-prompt/system-prompt.mjs +89 -5
- package/esm/system-prompt/system-prompt.mjs.map +1 -1
- package/esm/team/gates.mjs +48 -0
- package/esm/team/gates.mjs.map +1 -0
- package/esm/team/index.d.mts +1 -0
- package/esm/team/index.mjs +3 -0
- package/esm/team/team.d.mts +42 -0
- package/esm/team/team.d.mts.map +1 -0
- package/esm/team/team.mjs +94 -0
- package/esm/team/team.mjs.map +1 -0
- package/esm/tool/executable-as-tool.d.mts.map +1 -1
- package/esm/tool/executable-as-tool.mjs +2 -2
- package/esm/tool/executable-as-tool.mjs.map +1 -1
- package/esm/tool/tool.d.mts.map +1 -1
- package/esm/tool/tool.mjs +2 -2
- package/esm/tool/tool.mjs.map +1 -1
- package/esm/utils/compute-cost.d.mts +17 -1
- package/esm/utils/compute-cost.d.mts.map +1 -1
- package/esm/utils/compute-cost.mjs +26 -1
- package/esm/utils/compute-cost.mjs.map +1 -1
- package/esm/utils/extract-json-lenient.d.mts +42 -0
- package/esm/utils/extract-json-lenient.d.mts.map +1 -0
- package/esm/utils/extract-json-lenient.mjs +97 -0
- package/esm/utils/extract-json-lenient.mjs.map +1 -0
- package/esm/utils/index.d.mts +4 -2
- package/esm/utils/index.mjs +3 -1
- package/esm/utils/json-schema.d.mts +1 -1
- package/esm/utils/prepare-attachment-part.d.mts +10 -1
- package/esm/utils/prepare-attachment-part.d.mts.map +1 -1
- package/esm/utils/prepare-attachment-part.mjs +103 -11
- package/esm/utils/prepare-attachment-part.mjs.map +1 -1
- package/esm/utils/resolve-attachment.d.mts +4 -3
- package/esm/utils/resolve-attachment.d.mts.map +1 -1
- package/esm/utils/resolve-attachment.mjs +4 -3
- package/esm/utils/resolve-attachment.mjs.map +1 -1
- package/esm/utils/run-context.d.mts +94 -0
- package/esm/utils/run-context.d.mts.map +1 -0
- package/esm/utils/run-context.mjs +98 -0
- package/esm/utils/run-context.mjs.map +1 -0
- package/esm/vcr/cassette-io.mjs +57 -0
- package/esm/vcr/cassette-io.mjs.map +1 -0
- package/esm/vcr/errors.d.mts +42 -0
- package/esm/vcr/errors.d.mts.map +1 -0
- package/esm/vcr/errors.mjs +37 -0
- package/esm/vcr/errors.mjs.map +1 -0
- package/esm/vcr/hash-request.d.mts +28 -0
- package/esm/vcr/hash-request.d.mts.map +1 -0
- package/esm/vcr/hash-request.mjs +118 -0
- package/esm/vcr/hash-request.mjs.map +1 -0
- package/esm/vcr/index.d.mts +4 -0
- package/esm/vcr/index.mjs +5 -0
- package/esm/vcr/vcr.d.mts +32 -0
- package/esm/vcr/vcr.d.mts.map +1 -0
- package/esm/vcr/vcr.mjs +248 -0
- package/esm/vcr/vcr.mjs.map +1 -0
- package/esm/vcr/vcr.type.d.mts +118 -0
- package/esm/vcr/vcr.type.d.mts.map +1 -0
- package/esm/workflow/as-tool.mjs +2 -2
- package/esm/workflow/as-tool.mjs.map +1 -1
- package/esm/workflow/engine.mjs +1 -0
- package/esm/workflow/engine.mjs.map +1 -1
- package/esm/workflow/step-runner.mjs +19 -20
- package/esm/workflow/step-runner.mjs.map +1 -1
- package/esm/workflow/workflow.d.mts.map +1 -1
- package/esm/workflow/workflow.mjs +8 -3
- package/esm/workflow/workflow.mjs.map +1 -1
- package/llms-full.txt +1683 -68
- package/llms.txt +16 -4
- package/package.json +7 -3
- package/skills/README.md +40 -4
- package/skills/ai-dx-helpers/SKILL.md +2 -2
- package/skills/approve-tool-calls/SKILL.md +134 -0
- package/skills/attach-ai-middleware/SKILL.md +1 -1
- package/skills/detect-and-redact-pii/SKILL.md +104 -0
- package/skills/durable-resume/SKILL.md +128 -0
- package/skills/escalate-block-to-human/SKILL.md +85 -0
- package/skills/eval-datasets-and-ci/SKILL.md +117 -0
- package/skills/guard-input-output/SKILL.md +117 -0
- package/skills/manage-prompts/SKILL.md +186 -0
- package/skills/observe-ai-flows/SKILL.md +94 -0
- package/skills/record-replay-llm/SKILL.md +92 -0
- package/skills/run-ai-agent/SKILL.md +26 -1
- package/skills/run-ai-rag/SKILL.md +139 -0
- package/skills/run-ai-team/SKILL.md +107 -0
- package/skills/run-orchestrator/SKILL.md +2 -0
- package/skills/run-planner/SKILL.md +73 -8
- package/skills/run-supervisor/SKILL.md +20 -1
- package/skills/use-runtime-skills/SKILL.md +106 -0
- package/skills/write-system-prompt/SKILL.md +30 -1
- package/cjs/index.cjs.map +0 -1
- package/esm/contracts/result/index.d.mts +0 -15
package/esm/rag/rag.mjs
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { resolveDefaultStore } from "../config.mjs";
|
|
2
|
+
import { chunk } from "./chunk/chunk.mjs";
|
|
3
|
+
import { ragAsTool } from "./as-tool.mjs";
|
|
4
|
+
import { retrieve } from "./retrieve.mjs";
|
|
5
|
+
import { cacheVectorStore } from "./store/cache-vector-store.mjs";
|
|
6
|
+
|
|
7
|
+
//#region ../@warlock.js/ai/src/rag/rag.ts
|
|
8
|
+
const DEFAULT_NAME = "rag";
|
|
9
|
+
const DEFAULT_NAMESPACE_PREFIX = "ai.rag";
|
|
10
|
+
/**
|
|
11
|
+
* Max chunk texts embedded per `embedder.embedMany()` call. One call is
|
|
12
|
+
* one provider request, so a giant document is sub-batched to stay under
|
|
13
|
+
* the provider's per-request token cap (the design's "chunk larger than
|
|
14
|
+
* provider per-request cap" guard).
|
|
15
|
+
*/
|
|
16
|
+
const DEFAULT_MAX_BATCH = 96;
|
|
17
|
+
/**
|
|
18
|
+
* Create a RAG pipeline: **chunk → embed → vector store → retrieve →
|
|
19
|
+
* rerank → cite**, reusing the app's `ai.embedder` for embedding, a
|
|
20
|
+
* `@warlock.js/cache` `CacheDriver` as the vector store, and the
|
|
21
|
+
* composite-as-tool engine to expose retrieval as a tool.
|
|
22
|
+
*
|
|
23
|
+
* Resolution is loud at construction (mirroring `memory()`):
|
|
24
|
+
* - `embedder` is required — a provider with no embedder must be caught
|
|
25
|
+
* here, not at first index.
|
|
26
|
+
* - `store` falls back to `ai.config({ defaultStore })`; if neither
|
|
27
|
+
* resolves, construction throws.
|
|
28
|
+
*
|
|
29
|
+
* `retrieve()` is return-only — it never auto-injects into a prompt; the
|
|
30
|
+
* caller formats the cited chunks (or uses `asTool()` for the agent loop).
|
|
31
|
+
* The reranker is OFF by default (cosine-only) unless `config.reranker`
|
|
32
|
+
* is set.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* import { ai } from "@warlock.js/ai";
|
|
36
|
+
* import { MemoryCacheDriver } from "@warlock.js/cache";
|
|
37
|
+
*
|
|
38
|
+
* const kb = ai.rag({
|
|
39
|
+
* name: "docs",
|
|
40
|
+
* embedder: openai.embedder({ name: "text-embedding-3-small" }),
|
|
41
|
+
* store: new MemoryCacheDriver(),
|
|
42
|
+
* chunk: { type: "markdown", size: 800, overlap: 120 },
|
|
43
|
+
* });
|
|
44
|
+
*
|
|
45
|
+
* await kb.index([{ id: "guide", text: longMarkdown, metadata: { url: "/guide" } }]);
|
|
46
|
+
* const { chunks } = await kb.retrieve("how do I configure caching?", { topK: 4 });
|
|
47
|
+
*/
|
|
48
|
+
function rag(config) {
|
|
49
|
+
const name = config.name ?? DEFAULT_NAME;
|
|
50
|
+
if (!config.embedder) throw new Error(`rag("${name}"): an \`embedder\` is required — pass one from a provider that supports embeddings (e.g. openai.embedder({ name: "text-embedding-3-small" }))`);
|
|
51
|
+
const driver = config.store ?? resolveDefaultStore();
|
|
52
|
+
if (!driver) throw new Error(`rag("${name}"): no store — pass \`store\` (a vector-capable @warlock.js/cache CacheDriver) or call \`ai.config({ defaultStore })\` at app boot before constructing the rag`);
|
|
53
|
+
const store = cacheVectorStore(driver);
|
|
54
|
+
const namespace = config.namespace ?? `${DEFAULT_NAMESPACE_PREFIX}.${name}`;
|
|
55
|
+
const embedder = config.embedder;
|
|
56
|
+
let indexedDimensions;
|
|
57
|
+
const instance = {
|
|
58
|
+
name,
|
|
59
|
+
async index(docs, chunkOverride) {
|
|
60
|
+
const chunkOptions = chunkOverride ?? config.chunk;
|
|
61
|
+
const limits = config.limits;
|
|
62
|
+
if (limits?.maxDocuments !== void 0 && docs.length > limits.maxDocuments) throw new Error(`rag("${name}"): index() received ${docs.length} documents, exceeding the configured maxDocuments of ${limits.maxDocuments}`);
|
|
63
|
+
if (limits?.maxBytes !== void 0) {
|
|
64
|
+
const totalBytes = docs.reduce((sum, doc) => sum + Buffer.byteLength(doc.text ?? ""), 0);
|
|
65
|
+
if (totalBytes > limits.maxBytes) throw new Error(`rag("${name}"): index() received ${totalBytes} bytes of document text, exceeding the configured maxBytes of ${limits.maxBytes}`);
|
|
66
|
+
}
|
|
67
|
+
const records = [];
|
|
68
|
+
for (const doc of docs) {
|
|
69
|
+
const pieces = chunk(doc.text, chunkOptions);
|
|
70
|
+
for (const piece of pieces) {
|
|
71
|
+
const value = {
|
|
72
|
+
sourceId: doc.id,
|
|
73
|
+
chunkIndex: piece.index,
|
|
74
|
+
span: piece.span,
|
|
75
|
+
text: piece.text,
|
|
76
|
+
metadata: doc.metadata
|
|
77
|
+
};
|
|
78
|
+
records.push({
|
|
79
|
+
key: keyFor(namespace, doc.id, piece.index),
|
|
80
|
+
value,
|
|
81
|
+
text: piece.text,
|
|
82
|
+
tags: doc.tags
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (records.length === 0) return { chunks: 0 };
|
|
87
|
+
if (limits?.maxChunks !== void 0 && records.length > limits.maxChunks) throw new Error(`rag("${name}"): index() produced ${records.length} chunks, exceeding the configured maxChunks of ${limits.maxChunks}`);
|
|
88
|
+
for (let offset = 0; offset < records.length; offset += DEFAULT_MAX_BATCH) {
|
|
89
|
+
const batch = records.slice(offset, offset + DEFAULT_MAX_BATCH);
|
|
90
|
+
const { vectors, dimensions } = await embedder.embedMany(batch.map((record) => record.text));
|
|
91
|
+
if (indexedDimensions === void 0 && dimensions !== 0) indexedDimensions = dimensions;
|
|
92
|
+
await Promise.all(batch.map((record, position) => store.upsert(record.key, record.value, vectors[position], record.tags)));
|
|
93
|
+
}
|
|
94
|
+
return { chunks: records.length };
|
|
95
|
+
},
|
|
96
|
+
async retrieve(query, options) {
|
|
97
|
+
return retrieve(query, {
|
|
98
|
+
embedder,
|
|
99
|
+
store,
|
|
100
|
+
namespace,
|
|
101
|
+
reranker: config.reranker,
|
|
102
|
+
defaults: config.retrieve,
|
|
103
|
+
indexedDimensions
|
|
104
|
+
}, options);
|
|
105
|
+
},
|
|
106
|
+
async clear() {
|
|
107
|
+
await store.removeNamespace(namespace);
|
|
108
|
+
},
|
|
109
|
+
asTool(options) {
|
|
110
|
+
return ragAsTool(name, (query, retrieveOptions) => instance.retrieve(query, retrieveOptions), options);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
return instance;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Namespaced key for a stored chunk. Uses the `.` separator (matching
|
|
117
|
+
* `SemanticMemory.keyFor`) so namespace-prefix filtering on the returned
|
|
118
|
+
* `hit.key` stays aligned with the cache's `parseKey` normalization.
|
|
119
|
+
*/
|
|
120
|
+
function keyFor(namespace, sourceId, chunkIndex) {
|
|
121
|
+
return `${namespace}.${sourceId}.${chunkIndex}`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
//#endregion
|
|
125
|
+
export { rag };
|
|
126
|
+
//# sourceMappingURL=rag.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rag.mjs","names":["chunkText","runRetrieve"],"sources":["../../../../../../../@warlock.js/ai/src/rag/rag.ts"],"sourcesContent":["import { resolveDefaultStore } from \"../config\";\nimport { chunk as chunkText } from \"./chunk/chunk\";\nimport type { ChunkOptions } from \"./contracts/chunk-options.type\";\nimport type { RetrieveOptions, RetrieveResult } from \"./contracts/citation.type\";\nimport type {\n Rag,\n RagAsToolOptions,\n RagConfig,\n} from \"./contracts/rag-config.type\";\nimport type { RagDocument } from \"./contracts/rag-document.type\";\nimport { ragAsTool } from \"./as-tool\";\nimport { retrieve as runRetrieve, type StoredChunk } from \"./retrieve\";\nimport { cacheVectorStore } from \"./store/cache-vector-store\";\nimport type { VectorStore } from \"./store/vector-store.contract\";\n\nconst DEFAULT_NAME = \"rag\";\nconst DEFAULT_NAMESPACE_PREFIX = \"ai.rag\";\n\n/**\n * Max chunk texts embedded per `embedder.embedMany()` call. One call is\n * one provider request, so a giant document is sub-batched to stay under\n * the provider's per-request token cap (the design's \"chunk larger than\n * provider per-request cap\" guard).\n */\nconst DEFAULT_MAX_BATCH = 96;\n\n/**\n * Create a RAG pipeline: **chunk → embed → vector store → retrieve →\n * rerank → cite**, reusing the app's `ai.embedder` for embedding, a\n * `@warlock.js/cache` `CacheDriver` as the vector store, and the\n * composite-as-tool engine to expose retrieval as a tool.\n *\n * Resolution is loud at construction (mirroring `memory()`):\n * - `embedder` is required — a provider with no embedder must be caught\n * here, not at first index.\n * - `store` falls back to `ai.config({ defaultStore })`; if neither\n * resolves, construction throws.\n *\n * `retrieve()` is return-only — it never auto-injects into a prompt; the\n * caller formats the cited chunks (or uses `asTool()` for the agent loop).\n * The reranker is OFF by default (cosine-only) unless `config.reranker`\n * is set.\n *\n * @example\n * import { ai } from \"@warlock.js/ai\";\n * import { MemoryCacheDriver } from \"@warlock.js/cache\";\n *\n * const kb = ai.rag({\n * name: \"docs\",\n * embedder: openai.embedder({ name: \"text-embedding-3-small\" }),\n * store: new MemoryCacheDriver(),\n * chunk: { type: \"markdown\", size: 800, overlap: 120 },\n * });\n *\n * await kb.index([{ id: \"guide\", text: longMarkdown, metadata: { url: \"/guide\" } }]);\n * const { chunks } = await kb.retrieve(\"how do I configure caching?\", { topK: 4 });\n */\nexport function rag(config: RagConfig): Rag {\n const name = config.name ?? DEFAULT_NAME;\n\n if (!config.embedder) {\n throw new Error(\n `rag(\"${name}\"): an \\`embedder\\` is required — pass one from a provider that supports embeddings (e.g. openai.embedder({ name: \"text-embedding-3-small\" }))`,\n );\n }\n\n const driver = config.store ?? resolveDefaultStore();\n\n if (!driver) {\n throw new Error(\n `rag(\"${name}\"): no store — pass \\`store\\` (a vector-capable @warlock.js/cache CacheDriver) or call \\`ai.config({ defaultStore })\\` at app boot before constructing the rag`,\n );\n }\n\n const store: VectorStore = cacheVectorStore(driver);\n const namespace = config.namespace ?? `${DEFAULT_NAMESPACE_PREFIX}.${name}`;\n const embedder = config.embedder;\n\n // Captured at first index for the dimension-mismatch guard in retrieve().\n let indexedDimensions: number | undefined;\n\n const instance: Rag = {\n name,\n\n async index(\n docs: RagDocument[],\n chunkOverride?: ChunkOptions,\n ): Promise<{ chunks: number }> {\n const chunkOptions = chunkOverride ?? config.chunk;\n\n // Ingestion guardrails (D5) — fail BEFORE any embedding spend.\n const limits = config.limits;\n if (limits?.maxDocuments !== undefined && docs.length > limits.maxDocuments) {\n throw new Error(\n `rag(\"${name}\"): index() received ${docs.length} documents, exceeding the configured maxDocuments of ${limits.maxDocuments}`,\n );\n }\n if (limits?.maxBytes !== undefined) {\n const totalBytes = docs.reduce(\n (sum, doc) => sum + Buffer.byteLength(doc.text ?? \"\"),\n 0,\n );\n if (totalBytes > limits.maxBytes) {\n throw new Error(\n `rag(\"${name}\"): index() received ${totalBytes} bytes of document text, exceeding the configured maxBytes of ${limits.maxBytes}`,\n );\n }\n }\n\n // Flatten every document into stored-chunk records + their texts,\n // preserving document order so a single batched embed maps back 1:1.\n const records: { key: string; value: StoredChunk; text: string; tags?: string[] }[] = [];\n\n for (const doc of docs) {\n const pieces = chunkText(doc.text, chunkOptions);\n\n for (const piece of pieces) {\n const value: StoredChunk = {\n sourceId: doc.id,\n chunkIndex: piece.index,\n span: piece.span,\n text: piece.text,\n metadata: doc.metadata,\n };\n\n records.push({\n key: keyFor(namespace, doc.id, piece.index),\n value,\n text: piece.text,\n tags: doc.tags,\n });\n }\n }\n\n // Empty / whitespace-only documents yield zero chunks — write\n // nothing and never embed an empty batch.\n if (records.length === 0) {\n return { chunks: 0 };\n }\n\n // Chunk cap (D5) — checked after chunking, still before embedding.\n if (limits?.maxChunks !== undefined && records.length > limits.maxChunks) {\n throw new Error(\n `rag(\"${name}\"): index() produced ${records.length} chunks, exceeding the configured maxChunks of ${limits.maxChunks}`,\n );\n }\n\n // Sub-batch the embed calls so one giant document does not blow the\n // provider's per-request token cap.\n for (let offset = 0; offset < records.length; offset += DEFAULT_MAX_BATCH) {\n const batch = records.slice(offset, offset + DEFAULT_MAX_BATCH);\n const { vectors, dimensions } = await embedder.embedMany(\n batch.map((record) => record.text),\n );\n\n if (indexedDimensions === undefined && dimensions !== 0) {\n indexedDimensions = dimensions;\n }\n\n await Promise.all(\n batch.map((record, position) =>\n store.upsert(record.key, record.value, vectors[position], record.tags),\n ),\n );\n }\n\n return { chunks: records.length };\n },\n\n async retrieve(query: string, options?: RetrieveOptions): Promise<RetrieveResult> {\n return runRetrieve(\n query,\n {\n embedder,\n store,\n namespace,\n reranker: config.reranker,\n defaults: config.retrieve,\n indexedDimensions,\n },\n options,\n );\n },\n\n async clear(): Promise<void> {\n await store.removeNamespace(namespace);\n },\n\n asTool(options?: RagAsToolOptions) {\n return ragAsTool(name, (query, retrieveOptions) => instance.retrieve(query, retrieveOptions), options);\n },\n };\n\n return instance;\n}\n\n/**\n * Namespaced key for a stored chunk. Uses the `.` separator (matching\n * `SemanticMemory.keyFor`) so namespace-prefix filtering on the returned\n * `hit.key` stays aligned with the cache's `parseKey` normalization.\n */\nfunction keyFor(namespace: string, sourceId: string, chunkIndex: number): string {\n return `${namespace}.${sourceId}.${chunkIndex}`;\n}\n"],"mappings":";;;;;;;AAeA,MAAM,eAAe;AACrB,MAAM,2BAA2B;;;;;;;AAQjC,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiC1B,SAAgB,IAAI,QAAwB;CAC1C,MAAM,OAAO,OAAO,QAAQ;CAE5B,IAAI,CAAC,OAAO,UACV,MAAM,IAAI,MACR,QAAQ,KAAK,+IACf;CAGF,MAAM,SAAS,OAAO,SAAS,oBAAoB;CAEnD,IAAI,CAAC,QACH,MAAM,IAAI,MACR,QAAQ,KAAK,+JACf;CAGF,MAAM,QAAqB,iBAAiB,MAAM;CAClD,MAAM,YAAY,OAAO,aAAa,GAAG,yBAAyB,GAAG;CACrE,MAAM,WAAW,OAAO;CAGxB,IAAI;CAEJ,MAAM,WAAgB;EACpB;EAEA,MAAM,MACJ,MACA,eAC6B;GAC7B,MAAM,eAAe,iBAAiB,OAAO;GAG7C,MAAM,SAAS,OAAO;GACtB,IAAI,QAAQ,iBAAiB,UAAa,KAAK,SAAS,OAAO,cAC7D,MAAM,IAAI,MACR,QAAQ,KAAK,uBAAuB,KAAK,OAAO,uDAAuD,OAAO,cAChH;GAEF,IAAI,QAAQ,aAAa,QAAW;IAClC,MAAM,aAAa,KAAK,QACrB,KAAK,QAAQ,MAAM,OAAO,WAAW,IAAI,QAAQ,EAAE,GACpD,CACF;IACA,IAAI,aAAa,OAAO,UACtB,MAAM,IAAI,MACR,QAAQ,KAAK,uBAAuB,WAAW,gEAAgE,OAAO,UACxH;GAEJ;GAIA,MAAM,UAAgF,CAAC;GAEvF,KAAK,MAAM,OAAO,MAAM;IACtB,MAAM,SAASA,MAAU,IAAI,MAAM,YAAY;IAE/C,KAAK,MAAM,SAAS,QAAQ;KAC1B,MAAM,QAAqB;MACzB,UAAU,IAAI;MACd,YAAY,MAAM;MAClB,MAAM,MAAM;MACZ,MAAM,MAAM;MACZ,UAAU,IAAI;KAChB;KAEA,QAAQ,KAAK;MACX,KAAK,OAAO,WAAW,IAAI,IAAI,MAAM,KAAK;MAC1C;MACA,MAAM,MAAM;MACZ,MAAM,IAAI;KACZ,CAAC;IACH;GACF;GAIA,IAAI,QAAQ,WAAW,GACrB,OAAO,EAAE,QAAQ,EAAE;GAIrB,IAAI,QAAQ,cAAc,UAAa,QAAQ,SAAS,OAAO,WAC7D,MAAM,IAAI,MACR,QAAQ,KAAK,uBAAuB,QAAQ,OAAO,iDAAiD,OAAO,WAC7G;GAKF,KAAK,IAAI,SAAS,GAAG,SAAS,QAAQ,QAAQ,UAAU,mBAAmB;IACzE,MAAM,QAAQ,QAAQ,MAAM,QAAQ,SAAS,iBAAiB;IAC9D,MAAM,EAAE,SAAS,eAAe,MAAM,SAAS,UAC7C,MAAM,KAAK,WAAW,OAAO,IAAI,CACnC;IAEA,IAAI,sBAAsB,UAAa,eAAe,GACpD,oBAAoB;IAGtB,MAAM,QAAQ,IACZ,MAAM,KAAK,QAAQ,aACjB,MAAM,OAAO,OAAO,KAAK,OAAO,OAAO,QAAQ,WAAW,OAAO,IAAI,CACvE,CACF;GACF;GAEA,OAAO,EAAE,QAAQ,QAAQ,OAAO;EAClC;EAEA,MAAM,SAAS,OAAe,SAAoD;GAChF,OAAOC,SACL,OACA;IACE;IACA;IACA;IACA,UAAU,OAAO;IACjB,UAAU,OAAO;IACjB;GACF,GACA,OACF;EACF;EAEA,MAAM,QAAuB;GAC3B,MAAM,MAAM,gBAAgB,SAAS;EACvC;EAEA,OAAO,SAA4B;GACjC,OAAO,UAAU,OAAO,OAAO,oBAAoB,SAAS,SAAS,OAAO,eAAe,GAAG,OAAO;EACvG;CACF;CAEA,OAAO;AACT;;;;;;AAOA,SAAS,OAAO,WAAmB,UAAkB,YAA4B;CAC/E,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG;AACrC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { RagReranker } from "./reranker.contract.mjs";
|
|
2
|
+
|
|
3
|
+
//#region ../@warlock.js/ai/src/rag/rerank/keyword-reranker.d.ts
|
|
4
|
+
/** Options for the {@link keywordReranker}. */
|
|
5
|
+
type KeywordRerankerOptions = {
|
|
6
|
+
/**
|
|
7
|
+
* Weight of the lexical-overlap signal blended with the original cosine
|
|
8
|
+
* score, in `[0, 1]`. `1` ranks purely by keyword overlap; `0` keeps the
|
|
9
|
+
* cosine order. Default `0.5`.
|
|
10
|
+
*/
|
|
11
|
+
weight?: number;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Zero-dependency lexical reranker (a BM25-lite, IDF-free keyword overlap).
|
|
15
|
+
*
|
|
16
|
+
* For each candidate it computes the fraction of distinct query terms that
|
|
17
|
+
* appear in the chunk, blends that with the candidate's original cosine
|
|
18
|
+
* score by `weight`, and sorts descending. A pure-lexical pass costs
|
|
19
|
+
* nothing beyond string splits — no peer, no model — so it is the
|
|
20
|
+
* recommended opt-in reranker when an embedding-only ranking surfaces a
|
|
21
|
+
* keyword-rich chunk too low.
|
|
22
|
+
*
|
|
23
|
+
* Ties (equal blended score) preserve the incoming order, so the cosine
|
|
24
|
+
* ranking breaks ties deterministically.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* const kb = ai.rag({ embedder, store, reranker: ai.rag.keywordReranker() });
|
|
28
|
+
*/
|
|
29
|
+
declare function keywordReranker(options?: KeywordRerankerOptions): RagReranker;
|
|
30
|
+
//#endregion
|
|
31
|
+
export { KeywordRerankerOptions, keywordReranker };
|
|
32
|
+
//# sourceMappingURL=keyword-reranker.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keyword-reranker.d.mts","names":[],"sources":["../../../../../../../../@warlock.js/ai/src/rag/rerank/keyword-reranker.ts"],"mappings":";;;;KAIY,sBAAA;EAAA;;;;AAMJ;EAAN,MAAM;AAAA;;;;;;;AA2B0E;;;;;;;;;;iBAAlE,eAAA,CAAgB,OAAA,GAAS,sBAAA,GAA8B,WAAW"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
//#region ../@warlock.js/ai/src/rag/rerank/keyword-reranker.ts
|
|
2
|
+
/** Splits text into lowercase alphanumeric terms. */
|
|
3
|
+
function tokenize(text) {
|
|
4
|
+
return text.toLowerCase().split(/[^a-z0-9]+/).filter((term) => term.length > 0);
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Zero-dependency lexical reranker (a BM25-lite, IDF-free keyword overlap).
|
|
8
|
+
*
|
|
9
|
+
* For each candidate it computes the fraction of distinct query terms that
|
|
10
|
+
* appear in the chunk, blends that with the candidate's original cosine
|
|
11
|
+
* score by `weight`, and sorts descending. A pure-lexical pass costs
|
|
12
|
+
* nothing beyond string splits — no peer, no model — so it is the
|
|
13
|
+
* recommended opt-in reranker when an embedding-only ranking surfaces a
|
|
14
|
+
* keyword-rich chunk too low.
|
|
15
|
+
*
|
|
16
|
+
* Ties (equal blended score) preserve the incoming order, so the cosine
|
|
17
|
+
* ranking breaks ties deterministically.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* const kb = ai.rag({ embedder, store, reranker: ai.rag.keywordReranker() });
|
|
21
|
+
*/
|
|
22
|
+
function keywordReranker(options = {}) {
|
|
23
|
+
const weight = options.weight ?? .5;
|
|
24
|
+
return {
|
|
25
|
+
name: "keyword",
|
|
26
|
+
async rerank(query, candidates) {
|
|
27
|
+
if (candidates.length === 0) return [];
|
|
28
|
+
const queryTerms = new Set(tokenize(query));
|
|
29
|
+
if (queryTerms.size === 0) return [...candidates];
|
|
30
|
+
const scored = candidates.map((candidate, position) => {
|
|
31
|
+
const chunkTerms = new Set(tokenize(candidate.text));
|
|
32
|
+
let overlap = 0;
|
|
33
|
+
for (const term of queryTerms) if (chunkTerms.has(term)) overlap += 1;
|
|
34
|
+
return {
|
|
35
|
+
candidate,
|
|
36
|
+
blended: weight * (overlap / queryTerms.size) + (1 - weight) * candidate.score,
|
|
37
|
+
position
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
scored.sort((first, second) => {
|
|
41
|
+
if (second.blended !== first.blended) return second.blended - first.blended;
|
|
42
|
+
return first.position - second.position;
|
|
43
|
+
});
|
|
44
|
+
return scored.map((entry) => ({
|
|
45
|
+
...entry.candidate,
|
|
46
|
+
score: entry.blended,
|
|
47
|
+
citation: {
|
|
48
|
+
...entry.candidate.citation,
|
|
49
|
+
score: entry.blended
|
|
50
|
+
}
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
//#endregion
|
|
57
|
+
export { keywordReranker };
|
|
58
|
+
//# sourceMappingURL=keyword-reranker.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keyword-reranker.mjs","names":[],"sources":["../../../../../../../../@warlock.js/ai/src/rag/rerank/keyword-reranker.ts"],"sourcesContent":["import type { RetrievedChunk } from \"../contracts/citation.type\";\nimport type { RagReranker } from \"./reranker.contract\";\n\n/** Options for the {@link keywordReranker}. */\nexport type KeywordRerankerOptions = {\n /**\n * Weight of the lexical-overlap signal blended with the original cosine\n * score, in `[0, 1]`. `1` ranks purely by keyword overlap; `0` keeps the\n * cosine order. Default `0.5`.\n */\n weight?: number;\n};\n\n/** Splits text into lowercase alphanumeric terms. */\nfunction tokenize(text: string): string[] {\n return text\n .toLowerCase()\n .split(/[^a-z0-9]+/)\n .filter((term) => term.length > 0);\n}\n\n/**\n * Zero-dependency lexical reranker (a BM25-lite, IDF-free keyword overlap).\n *\n * For each candidate it computes the fraction of distinct query terms that\n * appear in the chunk, blends that with the candidate's original cosine\n * score by `weight`, and sorts descending. A pure-lexical pass costs\n * nothing beyond string splits — no peer, no model — so it is the\n * recommended opt-in reranker when an embedding-only ranking surfaces a\n * keyword-rich chunk too low.\n *\n * Ties (equal blended score) preserve the incoming order, so the cosine\n * ranking breaks ties deterministically.\n *\n * @example\n * const kb = ai.rag({ embedder, store, reranker: ai.rag.keywordReranker() });\n */\nexport function keywordReranker(options: KeywordRerankerOptions = {}): RagReranker {\n const weight = options.weight ?? 0.5;\n\n return {\n name: \"keyword\",\n async rerank(query: string, candidates: RetrievedChunk[]): Promise<RetrievedChunk[]> {\n if (candidates.length === 0) {\n return [];\n }\n\n const queryTerms = new Set(tokenize(query));\n\n if (queryTerms.size === 0) {\n return [...candidates];\n }\n\n const scored = candidates.map((candidate, position) => {\n const chunkTerms = new Set(tokenize(candidate.text));\n\n let overlap = 0;\n for (const term of queryTerms) {\n if (chunkTerms.has(term)) {\n overlap += 1;\n }\n }\n\n const lexical = overlap / queryTerms.size;\n const blended = weight * lexical + (1 - weight) * candidate.score;\n\n return { candidate, blended, position };\n });\n\n scored.sort((first, second) => {\n if (second.blended !== first.blended) {\n return second.blended - first.blended;\n }\n\n // Stable on ties: keep the incoming (cosine) order.\n return first.position - second.position;\n });\n\n return scored.map((entry) => ({\n ...entry.candidate,\n score: entry.blended,\n citation: { ...entry.candidate.citation, score: entry.blended },\n }));\n },\n };\n}\n"],"mappings":";;AAcA,SAAS,SAAS,MAAwB;CACxC,OAAO,KACJ,YAAY,CAAC,CACb,MAAM,YAAY,CAAC,CACnB,QAAQ,SAAS,KAAK,SAAS,CAAC;AACrC;;;;;;;;;;;;;;;;;AAkBA,SAAgB,gBAAgB,UAAkC,CAAC,GAAgB;CACjF,MAAM,SAAS,QAAQ,UAAU;CAEjC,OAAO;EACL,MAAM;EACN,MAAM,OAAO,OAAe,YAAyD;GACnF,IAAI,WAAW,WAAW,GACxB,OAAO,CAAC;GAGV,MAAM,aAAa,IAAI,IAAI,SAAS,KAAK,CAAC;GAE1C,IAAI,WAAW,SAAS,GACtB,OAAO,CAAC,GAAG,UAAU;GAGvB,MAAM,SAAS,WAAW,KAAK,WAAW,aAAa;IACrD,MAAM,aAAa,IAAI,IAAI,SAAS,UAAU,IAAI,CAAC;IAEnD,IAAI,UAAU;IACd,KAAK,MAAM,QAAQ,YACjB,IAAI,WAAW,IAAI,IAAI,GACrB,WAAW;IAOf,OAAO;KAAE;KAAW,SAFJ,UADA,UAAU,WAAW,SACD,IAAI,UAAU,UAAU;KAE/B;IAAS;GACxC,CAAC;GAED,OAAO,MAAM,OAAO,WAAW;IAC7B,IAAI,OAAO,YAAY,MAAM,SAC3B,OAAO,OAAO,UAAU,MAAM;IAIhC,OAAO,MAAM,WAAW,OAAO;GACjC,CAAC;GAED,OAAO,OAAO,KAAK,WAAW;IAC5B,GAAG,MAAM;IACT,OAAO,MAAM;IACb,UAAU;KAAE,GAAG,MAAM,UAAU;KAAU,OAAO,MAAM;IAAQ;GAChE,EAAE;EACJ;CACF;AACF"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { ModelContract } from "../../contracts/model.contract.mjs";
|
|
2
|
+
import { RagReranker } from "./reranker.contract.mjs";
|
|
3
|
+
|
|
4
|
+
//#region ../@warlock.js/ai/src/rag/rerank/llm-reranker.d.ts
|
|
5
|
+
/** Options for the {@link llmReranker}. */
|
|
6
|
+
type LlmRerankerOptions = {
|
|
7
|
+
/** The model used to score candidate relevance. Required. */model: ModelContract;
|
|
8
|
+
/**
|
|
9
|
+
* How many candidates to score per model call. Larger batches mean
|
|
10
|
+
* fewer round-trips but a longer prompt. Default `10`.
|
|
11
|
+
*/
|
|
12
|
+
batchSize?: number;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Optional model-backed reranker.
|
|
16
|
+
*
|
|
17
|
+
* Asks an LLM to grade each over-fetched candidate's relevance to the
|
|
18
|
+
* query on a `0..1` scale, then sorts descending by the model's score.
|
|
19
|
+
* Candidates the model does not score keep their original cosine score, so
|
|
20
|
+
* a partial/garbled reply degrades gracefully rather than dropping hits.
|
|
21
|
+
* Scoring is batched (`batchSize`) to bound prompt length.
|
|
22
|
+
*
|
|
23
|
+
* Unlike {@link keywordReranker}, this costs one or more model calls per
|
|
24
|
+
* retrieval — opt in only when precision matters more than latency/cost.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* const kb = ai.rag({
|
|
28
|
+
* embedder,
|
|
29
|
+
* store,
|
|
30
|
+
* reranker: ai.rag.llmReranker({ model: openai.model({ name: "gpt-4o-mini" }) }),
|
|
31
|
+
* });
|
|
32
|
+
*/
|
|
33
|
+
declare function llmReranker(options: LlmRerankerOptions): RagReranker;
|
|
34
|
+
//#endregion
|
|
35
|
+
export { LlmRerankerOptions, llmReranker };
|
|
36
|
+
//# sourceMappingURL=llm-reranker.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llm-reranker.d.mts","names":[],"sources":["../../../../../../../../@warlock.js/ai/src/rag/rerank/llm-reranker.ts"],"mappings":";;;;;KAMY,kBAAA;EAAA,6DAEV,KAAA,EAAO,aAAa;;;;;EAKpB,SAAA;AAAA;AAAS;AA8FX;;;;;;;;AAAqE;;;;;;;;;;AA9F1D,iBA8FK,WAAA,CAAY,OAAA,EAAS,kBAAA,GAAqB,WAAW"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
//#region ../@warlock.js/ai/src/rag/rerank/llm-reranker.ts
|
|
2
|
+
/**
|
|
3
|
+
* Build the scoring prompt — the model rates each candidate's relevance to
|
|
4
|
+
* the query on a `0..1` scale and replies with one `index: score` line per
|
|
5
|
+
* candidate. Kept terse and JSON-light so any chat model can answer.
|
|
6
|
+
*/
|
|
7
|
+
function buildPrompt(query, candidates) {
|
|
8
|
+
return [{
|
|
9
|
+
role: "system",
|
|
10
|
+
content: "You are a relevance grader. For each numbered passage, rate how well it answers the query on a scale from 0 (irrelevant) to 1 (fully relevant). Reply with ONLY a JSON array of objects like [{\"index\":0,\"score\":0.9}], one entry per passage, no prose."
|
|
11
|
+
}, {
|
|
12
|
+
role: "user",
|
|
13
|
+
content: `Query: ${query}\n\nPassages:\n${candidates.map((candidate, index) => `[${index}] ${candidate.text}`).join("\n\n")}`
|
|
14
|
+
}];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Parse the model's reply into a score map. Tolerant of surrounding prose:
|
|
18
|
+
* extracts the first JSON array and reads `{ index, score }` entries.
|
|
19
|
+
* Returns an empty map when nothing parseable is found, so the caller can
|
|
20
|
+
* fall back to the original order.
|
|
21
|
+
*/
|
|
22
|
+
function parseScores(reply) {
|
|
23
|
+
const scores = /* @__PURE__ */ new Map();
|
|
24
|
+
const start = reply.indexOf("[");
|
|
25
|
+
const end = reply.lastIndexOf("]");
|
|
26
|
+
if (start === -1 || end === -1 || end <= start) return scores;
|
|
27
|
+
let parsed;
|
|
28
|
+
try {
|
|
29
|
+
parsed = JSON.parse(reply.slice(start, end + 1));
|
|
30
|
+
} catch {
|
|
31
|
+
return scores;
|
|
32
|
+
}
|
|
33
|
+
if (!Array.isArray(parsed)) return scores;
|
|
34
|
+
for (const entry of parsed) if (entry && typeof entry.index === "number" && typeof entry.score === "number" && Number.isFinite(entry.score)) scores.set(entry.index, Math.max(0, Math.min(1, entry.score)));
|
|
35
|
+
return scores;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Optional model-backed reranker.
|
|
39
|
+
*
|
|
40
|
+
* Asks an LLM to grade each over-fetched candidate's relevance to the
|
|
41
|
+
* query on a `0..1` scale, then sorts descending by the model's score.
|
|
42
|
+
* Candidates the model does not score keep their original cosine score, so
|
|
43
|
+
* a partial/garbled reply degrades gracefully rather than dropping hits.
|
|
44
|
+
* Scoring is batched (`batchSize`) to bound prompt length.
|
|
45
|
+
*
|
|
46
|
+
* Unlike {@link keywordReranker}, this costs one or more model calls per
|
|
47
|
+
* retrieval — opt in only when precision matters more than latency/cost.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* const kb = ai.rag({
|
|
51
|
+
* embedder,
|
|
52
|
+
* store,
|
|
53
|
+
* reranker: ai.rag.llmReranker({ model: openai.model({ name: "gpt-4o-mini" }) }),
|
|
54
|
+
* });
|
|
55
|
+
*/
|
|
56
|
+
function llmReranker(options) {
|
|
57
|
+
const batchSize = options.batchSize ?? 10;
|
|
58
|
+
return {
|
|
59
|
+
name: "llm",
|
|
60
|
+
async rerank(query, candidates) {
|
|
61
|
+
if (candidates.length === 0) return [];
|
|
62
|
+
const rescored = [];
|
|
63
|
+
for (let offset = 0; offset < candidates.length; offset += batchSize) {
|
|
64
|
+
const batch = candidates.slice(offset, offset + batchSize);
|
|
65
|
+
const scores = parseScores((await options.model.complete(buildPrompt(query, batch))).content);
|
|
66
|
+
batch.forEach((candidate, index) => {
|
|
67
|
+
const score = scores.has(index) ? scores.get(index) : candidate.score;
|
|
68
|
+
rescored.push({
|
|
69
|
+
...candidate,
|
|
70
|
+
score,
|
|
71
|
+
citation: {
|
|
72
|
+
...candidate.citation,
|
|
73
|
+
score
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return rescored.sort((first, second) => second.score - first.score);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
//#endregion
|
|
84
|
+
export { llmReranker };
|
|
85
|
+
//# sourceMappingURL=llm-reranker.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llm-reranker.mjs","names":[],"sources":["../../../../../../../../@warlock.js/ai/src/rag/rerank/llm-reranker.ts"],"sourcesContent":["import type { Message } from \"../../contracts/conversation-message.type\";\nimport type { ModelContract } from \"../../contracts/model.contract\";\nimport type { RetrievedChunk } from \"../contracts/citation.type\";\nimport type { RagReranker } from \"./reranker.contract\";\n\n/** Options for the {@link llmReranker}. */\nexport type LlmRerankerOptions = {\n /** The model used to score candidate relevance. Required. */\n model: ModelContract;\n /**\n * How many candidates to score per model call. Larger batches mean\n * fewer round-trips but a longer prompt. Default `10`.\n */\n batchSize?: number;\n};\n\n/**\n * A single relevance score the model returns for a candidate, in `[0, 1]`,\n * keyed by the candidate's position in the batch.\n */\ntype ScoreLine = {\n index: number;\n score: number;\n};\n\n/**\n * Build the scoring prompt — the model rates each candidate's relevance to\n * the query on a `0..1` scale and replies with one `index: score` line per\n * candidate. Kept terse and JSON-light so any chat model can answer.\n */\nfunction buildPrompt(query: string, candidates: RetrievedChunk[]): Message[] {\n const lines = candidates\n .map((candidate, index) => `[${index}] ${candidate.text}`)\n .join(\"\\n\\n\");\n\n return [\n {\n role: \"system\",\n content:\n \"You are a relevance grader. For each numbered passage, rate how well it answers the query on a scale from 0 (irrelevant) to 1 (fully relevant). Reply with ONLY a JSON array of objects like [{\\\"index\\\":0,\\\"score\\\":0.9}], one entry per passage, no prose.\",\n },\n {\n role: \"user\",\n content: `Query: ${query}\\n\\nPassages:\\n${lines}`,\n },\n ];\n}\n\n/**\n * Parse the model's reply into a score map. Tolerant of surrounding prose:\n * extracts the first JSON array and reads `{ index, score }` entries.\n * Returns an empty map when nothing parseable is found, so the caller can\n * fall back to the original order.\n */\nfunction parseScores(reply: string): Map<number, number> {\n const scores = new Map<number, number>();\n const start = reply.indexOf(\"[\");\n const end = reply.lastIndexOf(\"]\");\n\n if (start === -1 || end === -1 || end <= start) {\n return scores;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(reply.slice(start, end + 1));\n } catch {\n return scores;\n }\n\n if (!Array.isArray(parsed)) {\n return scores;\n }\n\n for (const entry of parsed as ScoreLine[]) {\n if (\n entry &&\n typeof entry.index === \"number\" &&\n typeof entry.score === \"number\" &&\n Number.isFinite(entry.score)\n ) {\n scores.set(entry.index, Math.max(0, Math.min(1, entry.score)));\n }\n }\n\n return scores;\n}\n\n/**\n * Optional model-backed reranker.\n *\n * Asks an LLM to grade each over-fetched candidate's relevance to the\n * query on a `0..1` scale, then sorts descending by the model's score.\n * Candidates the model does not score keep their original cosine score, so\n * a partial/garbled reply degrades gracefully rather than dropping hits.\n * Scoring is batched (`batchSize`) to bound prompt length.\n *\n * Unlike {@link keywordReranker}, this costs one or more model calls per\n * retrieval — opt in only when precision matters more than latency/cost.\n *\n * @example\n * const kb = ai.rag({\n * embedder,\n * store,\n * reranker: ai.rag.llmReranker({ model: openai.model({ name: \"gpt-4o-mini\" }) }),\n * });\n */\nexport function llmReranker(options: LlmRerankerOptions): RagReranker {\n const batchSize = options.batchSize ?? 10;\n\n return {\n name: \"llm\",\n async rerank(query: string, candidates: RetrievedChunk[]): Promise<RetrievedChunk[]> {\n if (candidates.length === 0) {\n return [];\n }\n\n const rescored: RetrievedChunk[] = [];\n\n for (let offset = 0; offset < candidates.length; offset += batchSize) {\n const batch = candidates.slice(offset, offset + batchSize);\n const response = await options.model.complete(buildPrompt(query, batch));\n const scores = parseScores(response.content);\n\n batch.forEach((candidate, index) => {\n const score = scores.has(index) ? (scores.get(index) as number) : candidate.score;\n\n rescored.push({\n ...candidate,\n score,\n citation: { ...candidate.citation, score },\n });\n });\n }\n\n return rescored.sort((first, second) => second.score - first.score);\n },\n };\n}\n"],"mappings":";;;;;;AA8BA,SAAS,YAAY,OAAe,YAAyC;CAK3E,OAAO,CACL;EACE,MAAM;EACN,SACE;CACJ,GACA;EACE,MAAM;EACN,SAAS,UAAU,MAAM,iBAZf,WACX,KAAK,WAAW,UAAU,IAAI,MAAM,IAAI,UAAU,MAAM,CAAC,CACzD,KAAK,MAU0C;CAChD,CACF;AACF;;;;;;;AAQA,SAAS,YAAY,OAAoC;CACvD,MAAM,yBAAS,IAAI,IAAoB;CACvC,MAAM,QAAQ,MAAM,QAAQ,GAAG;CAC/B,MAAM,MAAM,MAAM,YAAY,GAAG;CAEjC,IAAI,UAAU,MAAM,QAAQ,MAAM,OAAO,OACvC,OAAO;CAGT,IAAI;CACJ,IAAI;EACF,SAAS,KAAK,MAAM,MAAM,MAAM,OAAO,MAAM,CAAC,CAAC;CACjD,QAAQ;EACN,OAAO;CACT;CAEA,IAAI,CAAC,MAAM,QAAQ,MAAM,GACvB,OAAO;CAGT,KAAK,MAAM,SAAS,QAClB,IACE,SACA,OAAO,MAAM,UAAU,YACvB,OAAO,MAAM,UAAU,YACvB,OAAO,SAAS,MAAM,KAAK,GAE3B,OAAO,IAAI,MAAM,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,MAAM,KAAK,CAAC,CAAC;CAIjE,OAAO;AACT;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,YAAY,SAA0C;CACpE,MAAM,YAAY,QAAQ,aAAa;CAEvC,OAAO;EACL,MAAM;EACN,MAAM,OAAO,OAAe,YAAyD;GACnF,IAAI,WAAW,WAAW,GACxB,OAAO,CAAC;GAGV,MAAM,WAA6B,CAAC;GAEpC,KAAK,IAAI,SAAS,GAAG,SAAS,WAAW,QAAQ,UAAU,WAAW;IACpE,MAAM,QAAQ,WAAW,MAAM,QAAQ,SAAS,SAAS;IAEzD,MAAM,SAAS,aAAY,MADJ,QAAQ,MAAM,SAAS,YAAY,OAAO,KAAK,CAAC,EACpC,CAAC,OAAO;IAE3C,MAAM,SAAS,WAAW,UAAU;KAClC,MAAM,QAAQ,OAAO,IAAI,KAAK,IAAK,OAAO,IAAI,KAAK,IAAe,UAAU;KAE5E,SAAS,KAAK;MACZ,GAAG;MACH;MACA,UAAU;OAAE,GAAG,UAAU;OAAU;MAAM;KAC3C,CAAC;IACH,CAAC;GACH;GAEA,OAAO,SAAS,MAAM,OAAO,WAAW,OAAO,QAAQ,MAAM,KAAK;EACpE;CACF;AACF"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { RetrievedChunk } from "../contracts/citation.type.mjs";
|
|
2
|
+
|
|
3
|
+
//#region ../@warlock.js/ai/src/rag/rerank/reranker.contract.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Reranks an over-fetched candidate set against the query before the
|
|
6
|
+
* pipeline slices to `topK`. Optional — when no reranker is configured the
|
|
7
|
+
* raw cosine order from the vector store is kept.
|
|
8
|
+
*
|
|
9
|
+
* A reranker receives the candidates already scored by cosine similarity
|
|
10
|
+
* and returns them reordered (and typically re-scored). The pipeline does
|
|
11
|
+
* NOT trust the input order; it relies entirely on the returned order. A
|
|
12
|
+
* reranker that throws is caught by the pipeline, which falls back to the
|
|
13
|
+
* cosine order — so an implementation may throw to opt out of a given
|
|
14
|
+
* query rather than returning garbage.
|
|
15
|
+
*/
|
|
16
|
+
interface RagReranker {
|
|
17
|
+
/** Stable name for logs / diagnostics. */
|
|
18
|
+
readonly name: string;
|
|
19
|
+
/**
|
|
20
|
+
* Reorder (and optionally re-score) `candidates` by relevance to
|
|
21
|
+
* `query`. Returns the reranked list; the pipeline slices `topK` from
|
|
22
|
+
* the front.
|
|
23
|
+
*/
|
|
24
|
+
rerank(query: string, candidates: RetrievedChunk[]): Promise<RetrievedChunk[]>;
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { RagReranker };
|
|
28
|
+
//# sourceMappingURL=reranker.contract.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reranker.contract.d.mts","names":[],"sources":["../../../../../../../../@warlock.js/ai/src/rag/rerank/reranker.contract.ts"],"mappings":";;;;;AAcA;;;;;;;;;;UAAiB,WAAA;EAQmB;EAAA,SANzB,IAAA;EAM4C;;;AAAsB;;EAA3E,MAAA,CAAO,KAAA,UAAe,UAAA,EAAY,cAAA,KAAmB,OAAA,CAAQ,cAAA;AAAA"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
//#region ../@warlock.js/ai/src/rag/retrieve.ts
|
|
2
|
+
/** Default number of chunks returned after reranking. */
|
|
3
|
+
const DEFAULT_TOP_K = 5;
|
|
4
|
+
/** Default cosine floor applied at the vector-store stage. */
|
|
5
|
+
const DEFAULT_THRESHOLD = .5;
|
|
6
|
+
/**
|
|
7
|
+
* The cite pipeline: embed the query → over-fetch candidates from the
|
|
8
|
+
* store → filter to this rag's namespace → map to {@link RetrievedChunk}s
|
|
9
|
+
* with a {@link Citation} → optionally rerank → slice `topK`.
|
|
10
|
+
*
|
|
11
|
+
* Behavior matches the design's failure modes:
|
|
12
|
+
* - No hits clearing the threshold → `{ query, chunks: [] }`, never throws.
|
|
13
|
+
* - Namespace-prefix filtering keeps two rags sharing one driver isolated.
|
|
14
|
+
* - A reranker that throws is caught; the raw cosine order is used instead.
|
|
15
|
+
* - A dimension mismatch (indexed with model A, queried with model B)
|
|
16
|
+
* throws a clear error rather than returning garbage hits.
|
|
17
|
+
*/
|
|
18
|
+
async function retrieve(query, deps, options = {}) {
|
|
19
|
+
const topK = options.topK ?? deps.defaults?.topK ?? 5;
|
|
20
|
+
const threshold = options.threshold ?? deps.defaults?.threshold ?? .5;
|
|
21
|
+
const tags = options.tags ?? deps.defaults?.tags;
|
|
22
|
+
const candidates = options.candidates ?? deps.defaults?.candidates ?? Math.max(topK * 4, topK);
|
|
23
|
+
const { vector, dimensions } = await deps.embedder.embed(query);
|
|
24
|
+
if (deps.indexedDimensions !== void 0 && dimensions !== 0 && deps.indexedDimensions !== 0 && dimensions !== deps.indexedDimensions) throw new Error(`rag.retrieve(): query embedder dimensions (${dimensions}) do not match the dimensions captured at index time (${deps.indexedDimensions}); index and query must use the same embedding model`);
|
|
25
|
+
const hits = await deps.store.query(vector, {
|
|
26
|
+
topK: candidates,
|
|
27
|
+
threshold,
|
|
28
|
+
tags
|
|
29
|
+
});
|
|
30
|
+
const prefix = `${deps.namespace}.`;
|
|
31
|
+
let retrieved = hits.filter((hit) => hit.key.startsWith(prefix)).map((hit) => toRetrievedChunk(hit.value, hit.score));
|
|
32
|
+
retrieved = await applyReranker(query, retrieved, deps.reranker);
|
|
33
|
+
return {
|
|
34
|
+
query,
|
|
35
|
+
chunks: retrieved.slice(0, topK)
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/** Build a cited {@link RetrievedChunk} from a stored chunk + its cosine score. */
|
|
39
|
+
function toRetrievedChunk(stored, score) {
|
|
40
|
+
const citation = {
|
|
41
|
+
sourceId: stored.sourceId,
|
|
42
|
+
chunkIndex: stored.chunkIndex,
|
|
43
|
+
span: stored.span,
|
|
44
|
+
score,
|
|
45
|
+
metadata: stored.metadata
|
|
46
|
+
};
|
|
47
|
+
return {
|
|
48
|
+
text: stored.text,
|
|
49
|
+
score,
|
|
50
|
+
citation
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Run the optional reranker, degrading to the raw cosine order if it
|
|
55
|
+
* throws — a flaky optional reranker must never fail the whole retrieval.
|
|
56
|
+
*/
|
|
57
|
+
async function applyReranker(query, candidates, reranker) {
|
|
58
|
+
if (!reranker) return candidates;
|
|
59
|
+
try {
|
|
60
|
+
return await reranker.rerank(query, candidates);
|
|
61
|
+
} catch {
|
|
62
|
+
return candidates;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
//#endregion
|
|
67
|
+
export { retrieve };
|
|
68
|
+
//# sourceMappingURL=retrieve.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retrieve.mjs","names":[],"sources":["../../../../../../../@warlock.js/ai/src/rag/retrieve.ts"],"sourcesContent":["import type { EmbedderContract } from \"../contracts/embedder.contract\";\nimport type {\n Citation,\n RetrievedChunk,\n RetrieveOptions,\n RetrieveResult,\n} from \"./contracts/citation.type\";\nimport type { RagReranker } from \"./rerank/reranker.contract\";\nimport type { VectorStore } from \"./store/vector-store.contract\";\n\n/** Default number of chunks returned after reranking. */\nexport const DEFAULT_TOP_K = 5;\n\n/** Default cosine floor applied at the vector-store stage. */\nexport const DEFAULT_THRESHOLD = 0.5;\n\n/**\n * Shape persisted per chunk in the vector store. The vector itself is held\n * by the driver's own index (passed via `set({ vector })`), so it is not\n * duplicated here.\n */\nexport type StoredChunk = {\n sourceId: string;\n chunkIndex: number;\n span: [start: number, end: number];\n text: string;\n metadata?: Record<string, unknown>;\n};\n\n/** Dependencies the retrieve pipeline needs, resolved once by `rag()`. */\nexport type RetrieveDeps = {\n embedder: EmbedderContract;\n store: VectorStore;\n /** Namespace prefix every stored key carries (e.g. `\"ai.rag.docs\"`). */\n namespace: string;\n /** Optional reranker; when absent the cosine order is kept. */\n reranker?: RagReranker;\n /** Pipeline-level retrieval defaults. */\n defaults?: RetrieveOptions;\n /**\n * Dimension count captured at first index for the mismatch guard. When\n * set, the query embedder's `dimensions` must equal it.\n */\n indexedDimensions?: number;\n};\n\n/**\n * The cite pipeline: embed the query → over-fetch candidates from the\n * store → filter to this rag's namespace → map to {@link RetrievedChunk}s\n * with a {@link Citation} → optionally rerank → slice `topK`.\n *\n * Behavior matches the design's failure modes:\n * - No hits clearing the threshold → `{ query, chunks: [] }`, never throws.\n * - Namespace-prefix filtering keeps two rags sharing one driver isolated.\n * - A reranker that throws is caught; the raw cosine order is used instead.\n * - A dimension mismatch (indexed with model A, queried with model B)\n * throws a clear error rather than returning garbage hits.\n */\nexport async function retrieve(\n query: string,\n deps: RetrieveDeps,\n options: RetrieveOptions = {},\n): Promise<RetrieveResult> {\n const topK = options.topK ?? deps.defaults?.topK ?? DEFAULT_TOP_K;\n const threshold = options.threshold ?? deps.defaults?.threshold ?? DEFAULT_THRESHOLD;\n const tags = options.tags ?? deps.defaults?.tags;\n const candidates = options.candidates ?? deps.defaults?.candidates ?? Math.max(topK * 4, topK);\n\n const { vector, dimensions } = await deps.embedder.embed(query);\n\n if (\n deps.indexedDimensions !== undefined &&\n dimensions !== 0 &&\n deps.indexedDimensions !== 0 &&\n dimensions !== deps.indexedDimensions\n ) {\n throw new Error(\n `rag.retrieve(): query embedder dimensions (${dimensions}) do not match the dimensions captured at index time (${deps.indexedDimensions}); index and query must use the same embedding model`,\n );\n }\n\n const hits = await deps.store.query<StoredChunk>(vector, {\n topK: candidates,\n threshold,\n tags,\n });\n\n const prefix = `${deps.namespace}.`;\n\n let retrieved: RetrievedChunk[] = hits\n .filter((hit) => hit.key.startsWith(prefix))\n .map((hit) => toRetrievedChunk(hit.value, hit.score));\n\n retrieved = await applyReranker(query, retrieved, deps.reranker);\n\n return { query, chunks: retrieved.slice(0, topK) };\n}\n\n/** Build a cited {@link RetrievedChunk} from a stored chunk + its cosine score. */\nfunction toRetrievedChunk(stored: StoredChunk, score: number): RetrievedChunk {\n const citation: Citation = {\n sourceId: stored.sourceId,\n chunkIndex: stored.chunkIndex,\n span: stored.span,\n score,\n metadata: stored.metadata,\n };\n\n return { text: stored.text, score, citation };\n}\n\n/**\n * Run the optional reranker, degrading to the raw cosine order if it\n * throws — a flaky optional reranker must never fail the whole retrieval.\n */\nasync function applyReranker(\n query: string,\n candidates: RetrievedChunk[],\n reranker: RagReranker | undefined,\n): Promise<RetrievedChunk[]> {\n if (!reranker) {\n return candidates;\n }\n\n try {\n return await reranker.rerank(query, candidates);\n } catch {\n // Logged at the call site in a richer build; here we degrade silently\n // to vector-only ranking rather than aborting the retrieval.\n return candidates;\n }\n}\n"],"mappings":";;AAWA,MAAa,gBAAgB;;AAG7B,MAAa,oBAAoB;;;;;;;;;;;;;AA4CjC,eAAsB,SACpB,OACA,MACA,UAA2B,CAAC,GACH;CACzB,MAAM,OAAO,QAAQ,QAAQ,KAAK,UAAU;CAC5C,MAAM,YAAY,QAAQ,aAAa,KAAK,UAAU;CACtD,MAAM,OAAO,QAAQ,QAAQ,KAAK,UAAU;CAC5C,MAAM,aAAa,QAAQ,cAAc,KAAK,UAAU,cAAc,KAAK,IAAI,OAAO,GAAG,IAAI;CAE7F,MAAM,EAAE,QAAQ,eAAe,MAAM,KAAK,SAAS,MAAM,KAAK;CAE9D,IACE,KAAK,sBAAsB,UAC3B,eAAe,KACf,KAAK,sBAAsB,KAC3B,eAAe,KAAK,mBAEpB,MAAM,IAAI,MACR,8CAA8C,WAAW,wDAAwD,KAAK,kBAAkB,qDAC1I;CAGF,MAAM,OAAO,MAAM,KAAK,MAAM,MAAmB,QAAQ;EACvD,MAAM;EACN;EACA;CACF,CAAC;CAED,MAAM,SAAS,GAAG,KAAK,UAAU;CAEjC,IAAI,YAA8B,KAC/B,QAAQ,QAAQ,IAAI,IAAI,WAAW,MAAM,CAAC,CAAC,CAC3C,KAAK,QAAQ,iBAAiB,IAAI,OAAO,IAAI,KAAK,CAAC;CAEtD,YAAY,MAAM,cAAc,OAAO,WAAW,KAAK,QAAQ;CAE/D,OAAO;EAAE;EAAO,QAAQ,UAAU,MAAM,GAAG,IAAI;CAAE;AACnD;;AAGA,SAAS,iBAAiB,QAAqB,OAA+B;CAC5E,MAAM,WAAqB;EACzB,UAAU,OAAO;EACjB,YAAY,OAAO;EACnB,MAAM,OAAO;EACb;EACA,UAAU,OAAO;CACnB;CAEA,OAAO;EAAE,MAAM,OAAO;EAAM;EAAO;CAAS;AAC9C;;;;;AAMA,eAAe,cACb,OACA,YACA,UAC2B;CAC3B,IAAI,CAAC,UACH,OAAO;CAGT,IAAI;EACF,OAAO,MAAM,SAAS,OAAO,OAAO,UAAU;CAChD,QAAQ;EAGN,OAAO;CACT;AACF"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { VectorStore } from "./vector-store.contract.mjs";
|
|
2
|
+
import { CacheDriver } from "@warlock.js/cache";
|
|
3
|
+
|
|
4
|
+
//#region ../@warlock.js/ai/src/rag/store/cache-vector-store.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Adapt any `@warlock.js/cache` `CacheDriver` to the {@link VectorStore}
|
|
7
|
+
* narrowing the RAG pipeline depends on. The cache driver IS the vector
|
|
8
|
+
* store — exactly as `SemanticMemory` and `semanticCache` already use it:
|
|
9
|
+
*
|
|
10
|
+
* - `upsert` → `driver.set(key, value, { vector, tags })`
|
|
11
|
+
* - `query` → `driver.similar<T>(vector, { topK, threshold, tags })`
|
|
12
|
+
* - `removeNamespace` → `driver.removeNamespace(namespace)`
|
|
13
|
+
*
|
|
14
|
+
* Drivers without similarity support throw `CacheUnsupportedError` from
|
|
15
|
+
* `set({ vector })` / `similar()`; the error surfaces unchanged so the
|
|
16
|
+
* caller sees the cache layer's own message (pointing at the `pg` /
|
|
17
|
+
* `redis` drivers for production-scale similarity).
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* const store = cacheVectorStore(new MemoryCacheDriver());
|
|
21
|
+
* await store.upsert("ai.rag.docs.guide.0", { text: "…" }, vector);
|
|
22
|
+
* const hits = await store.query(queryVector, { topK: 5, threshold: 0.5 });
|
|
23
|
+
*/
|
|
24
|
+
declare function cacheVectorStore(driver: CacheDriver<any, any>): VectorStore;
|
|
25
|
+
//#endregion
|
|
26
|
+
export { cacheVectorStore };
|
|
27
|
+
//# sourceMappingURL=cache-vector-store.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache-vector-store.d.mts","names":[],"sources":["../../../../../../../../@warlock.js/ai/src/rag/store/cache-vector-store.ts"],"mappings":";;;;;;AAsBA;;;;;;;;AAA4E;;;;;;;;;iBAA5D,gBAAA,CAAiB,MAAA,EAAQ,WAAA,aAAwB,WAAW"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
//#region ../@warlock.js/ai/src/rag/store/cache-vector-store.ts
|
|
2
|
+
/**
|
|
3
|
+
* Adapt any `@warlock.js/cache` `CacheDriver` to the {@link VectorStore}
|
|
4
|
+
* narrowing the RAG pipeline depends on. The cache driver IS the vector
|
|
5
|
+
* store — exactly as `SemanticMemory` and `semanticCache` already use it:
|
|
6
|
+
*
|
|
7
|
+
* - `upsert` → `driver.set(key, value, { vector, tags })`
|
|
8
|
+
* - `query` → `driver.similar<T>(vector, { topK, threshold, tags })`
|
|
9
|
+
* - `removeNamespace` → `driver.removeNamespace(namespace)`
|
|
10
|
+
*
|
|
11
|
+
* Drivers without similarity support throw `CacheUnsupportedError` from
|
|
12
|
+
* `set({ vector })` / `similar()`; the error surfaces unchanged so the
|
|
13
|
+
* caller sees the cache layer's own message (pointing at the `pg` /
|
|
14
|
+
* `redis` drivers for production-scale similarity).
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* const store = cacheVectorStore(new MemoryCacheDriver());
|
|
18
|
+
* await store.upsert("ai.rag.docs.guide.0", { text: "…" }, vector);
|
|
19
|
+
* const hits = await store.query(queryVector, { topK: 5, threshold: 0.5 });
|
|
20
|
+
*/
|
|
21
|
+
function cacheVectorStore(driver) {
|
|
22
|
+
return {
|
|
23
|
+
async upsert(key, value, vector, tags) {
|
|
24
|
+
await driver.set(key, value, tags && tags.length > 0 ? {
|
|
25
|
+
vector,
|
|
26
|
+
tags
|
|
27
|
+
} : { vector });
|
|
28
|
+
},
|
|
29
|
+
async query(vector, options) {
|
|
30
|
+
return (await driver.similar(vector, {
|
|
31
|
+
topK: options.topK,
|
|
32
|
+
threshold: options.threshold,
|
|
33
|
+
tags: options.tags
|
|
34
|
+
})).map((hit) => ({
|
|
35
|
+
key: hit.key,
|
|
36
|
+
value: hit.value,
|
|
37
|
+
score: hit.score
|
|
38
|
+
}));
|
|
39
|
+
},
|
|
40
|
+
async removeNamespace(namespace) {
|
|
41
|
+
await driver.removeNamespace(namespace);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
//#endregion
|
|
47
|
+
export { cacheVectorStore };
|
|
48
|
+
//# sourceMappingURL=cache-vector-store.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache-vector-store.mjs","names":[],"sources":["../../../../../../../../@warlock.js/ai/src/rag/store/cache-vector-store.ts"],"sourcesContent":["import type { CacheDriver } from \"@warlock.js/cache\";\nimport type { VectorStore } from \"./vector-store.contract\";\n\n/**\n * Adapt any `@warlock.js/cache` `CacheDriver` to the {@link VectorStore}\n * narrowing the RAG pipeline depends on. The cache driver IS the vector\n * store — exactly as `SemanticMemory` and `semanticCache` already use it:\n *\n * - `upsert` → `driver.set(key, value, { vector, tags })`\n * - `query` → `driver.similar<T>(vector, { topK, threshold, tags })`\n * - `removeNamespace` → `driver.removeNamespace(namespace)`\n *\n * Drivers without similarity support throw `CacheUnsupportedError` from\n * `set({ vector })` / `similar()`; the error surfaces unchanged so the\n * caller sees the cache layer's own message (pointing at the `pg` /\n * `redis` drivers for production-scale similarity).\n *\n * @example\n * const store = cacheVectorStore(new MemoryCacheDriver());\n * await store.upsert(\"ai.rag.docs.guide.0\", { text: \"…\" }, vector);\n * const hits = await store.query(queryVector, { topK: 5, threshold: 0.5 });\n */\nexport function cacheVectorStore(driver: CacheDriver<any, any>): VectorStore {\n return {\n async upsert(\n key: string,\n value: unknown,\n vector: number[],\n tags?: string[],\n ): Promise<void> {\n await driver.set(key, value, tags && tags.length > 0 ? { vector, tags } : { vector });\n },\n\n async query<T>(\n vector: number[],\n options: { topK: number; threshold?: number; tags?: string[] },\n ): Promise<{ key: string; value: T; score: number }[]> {\n const hits = await driver.similar<T>(vector, {\n topK: options.topK,\n threshold: options.threshold,\n tags: options.tags,\n });\n\n return hits.map((hit: { key: string; value: T; score: number }) => ({\n key: hit.key,\n value: hit.value,\n score: hit.score,\n }));\n },\n\n async removeNamespace(namespace: string): Promise<void> {\n await driver.removeNamespace(namespace);\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,iBAAiB,QAA4C;CAC3E,OAAO;EACL,MAAM,OACJ,KACA,OACA,QACA,MACe;GACf,MAAM,OAAO,IAAI,KAAK,OAAO,QAAQ,KAAK,SAAS,IAAI;IAAE;IAAQ;GAAK,IAAI,EAAE,OAAO,CAAC;EACtF;EAEA,MAAM,MACJ,QACA,SACqD;GAOrD,QAAO,MANY,OAAO,QAAW,QAAQ;IAC3C,MAAM,QAAQ;IACd,WAAW,QAAQ;IACnB,MAAM,QAAQ;GAChB,CAAC,EAEU,CAAC,KAAK,SAAmD;IAClE,KAAK,IAAI;IACT,OAAO,IAAI;IACX,OAAO,IAAI;GACb,EAAE;EACJ;EAEA,MAAM,gBAAgB,WAAkC;GACtD,MAAM,OAAO,gBAAgB,SAAS;EACxC;CACF;AACF"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//#region ../@warlock.js/ai/src/rag/store/vector-store.contract.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* The three vector operations the RAG pipeline needs, expressed as a thin
|
|
4
|
+
* structural narrowing of the `@warlock.js/cache` `CacheDriver` surface
|
|
5
|
+
* (`set({ vector })` + `similar()` + `removeNamespace()`).
|
|
6
|
+
*
|
|
7
|
+
* This is NOT a new storage engine — v1 has exactly one implementation,
|
|
8
|
+
* {@link cacheVectorStore}, which adapts any `CacheDriver`. The contract
|
|
9
|
+
* exists so a future non-cache backend can be swapped in without touching
|
|
10
|
+
* the pipeline. Cache stays embedding-agnostic; the RAG vocabulary
|
|
11
|
+
* (`upsert` / `query`) lives here, in the rag feature.
|
|
12
|
+
*/
|
|
13
|
+
interface VectorStore {
|
|
14
|
+
/**
|
|
15
|
+
* Index a value under `key` with its embedding vector. Optional `tags`
|
|
16
|
+
* are stored alongside the entry so `query({ tags })` can restrict the
|
|
17
|
+
* candidate set to a subset of sources.
|
|
18
|
+
*/
|
|
19
|
+
upsert(key: string, value: unknown, vector: number[], tags?: string[]): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Cosine-nearest entries to `vector` clearing `threshold`, capped at
|
|
22
|
+
* `topK`, optionally restricted to entries carrying one of `tags`.
|
|
23
|
+
*/
|
|
24
|
+
query<T>(vector: number[], options: {
|
|
25
|
+
topK: number;
|
|
26
|
+
threshold?: number;
|
|
27
|
+
tags?: string[];
|
|
28
|
+
}): Promise<{
|
|
29
|
+
key: string;
|
|
30
|
+
value: T;
|
|
31
|
+
score: number;
|
|
32
|
+
}[]>;
|
|
33
|
+
/** Drop every entry written under `namespace`. */
|
|
34
|
+
removeNamespace(namespace: string): Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
export { VectorStore };
|
|
38
|
+
//# sourceMappingURL=vector-store.contract.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vector-store.contract.d.mts","names":[],"sources":["../../../../../../../../@warlock.js/ai/src/rag/store/vector-store.contract.ts"],"mappings":";;AAWA;;;;;;;;;;UAAiB,WAAA;EAMK;;;;;EAApB,MAAA,CAAO,GAAA,UAAa,KAAA,WAAgB,MAAA,YAAkB,IAAA,cAAkB,OAAA;EAMtE;;;;EADF,KAAA,IACE,MAAA,YACA,OAAA;IAAW,IAAA;IAAc,SAAA;IAAoB,IAAA;EAAA,IAC5C,OAAA;IAAU,GAAA;IAAa,KAAA,EAAO,CAAA;IAAG,KAAA;EAAA;EAEO;EAA3C,eAAA,CAAgB,SAAA,WAAoB,OAAA;AAAA"}
|