@lunora/ai 1.0.0-alpha.95 → 1.0.0-alpha.97
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/dist/index.mjs +1 -1
- package/dist/packem_shared/AI_DEFAULT_EMBEDDING_MODEL_ENV-B58f6yo6.mjs +1 -0
- package/dist/packem_shared/DEFAULT_MODEL_PRICES-CIJLs1Ah.mjs +1 -0
- package/dist/packem_shared/{createAi-CHSAfN98.mjs → createAi-mXofcZid.mjs} +1 -1
- package/dist/packem_shared/defineRag-Cs7m4xJJ.mjs +8 -0
- package/dist/packem_shared/{markdownChunker-Bcv56GEz.mjs → markdownChunker-iW8V4klY.mjs} +3 -3
- package/dist/packem_shared/ragSyncTriggers-hgMcA4f2.mjs +1 -0
- package/dist/packem_shared/stable-key-B_BlboiY.mjs +1 -0
- package/dist/rag/index.d.mts +44 -25
- package/dist/rag/index.d.ts +44 -25
- package/dist/rag/index.mjs +1 -1
- package/package.json +2 -2
- package/dist/packem_shared/AI_DEFAULT_EMBEDDING_MODEL_ENV-BEAzcoBw.mjs +0 -1
- package/dist/packem_shared/DEFAULT_MODEL_PRICES-Q8uxdiuV.mjs +0 -1
- package/dist/packem_shared/defineRag-DK8Ifpn_.mjs +0 -8
- package/dist/packem_shared/ragSyncTriggers-DPqzBNFw.mjs +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{default as _}from"./packem_shared/createAi-
|
|
1
|
+
import{default as _}from"./packem_shared/createAi-mXofcZid.mjs";import{AI_DEFAULT_EMBEDDING_MODEL_ENV as a,AI_DEFAULT_MODEL_ENV as E,AI_GATEWAY_ACCOUNT_ID_ENV as o,AI_GATEWAY_ID_ENV as r,AI_GATEWAY_METADATA_MAX_KEYS as T,AI_GATEWAY_TAGS_ENV as I,AI_GATEWAY_TOKEN_ENV as l,buildAiGatewayMetadataFields as m,readAiGatewayEnvTags as s,resolveAiGateway as D}from"./packem_shared/AI_DEFAULT_EMBEDDING_MODEL_ENV-B58f6yo6.mjs";import{DEFAULT_MODEL_PRICES as M,estimateModelCost as d,lookupModelPrice as N}from"./packem_shared/DEFAULT_MODEL_PRICES-CIJLs1Ah.mjs";import{embed as x,embedMany as O,generateObject as c,generateText as f,hasToolCall as p,jsonSchema as L,streamObject as V,streamText as W,tool as Y}from"ai";import{createWorkersAI as n}from"workers-ai-provider";export{a as AI_DEFAULT_EMBEDDING_MODEL_ENV,E as AI_DEFAULT_MODEL_ENV,o as AI_GATEWAY_ACCOUNT_ID_ENV,r as AI_GATEWAY_ID_ENV,T as AI_GATEWAY_METADATA_MAX_KEYS,I as AI_GATEWAY_TAGS_ENV,l as AI_GATEWAY_TOKEN_ENV,M as DEFAULT_MODEL_PRICES,m as buildAiGatewayMetadataFields,_ as createAi,n as createWorkersAI,x as embed,O as embedMany,d as estimateModelCost,c as generateObject,f as generateText,p as hasToolCall,L as jsonSchema,N as lookupModelPrice,s as readAiGatewayEnvTags,D as resolveAiGateway,V as streamObject,W as streamText,Y as tool};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const a=(t,o)=>{if(t===void 0)return;const e=t[o];return typeof e=="string"&&e.length>0?e:void 0};let A=!1,d=!1;const f=5,l=t=>{if(t===void 0)return;const o={};typeof t.functionPath=="string"&&t.functionPath.length>0&&(o.functionPath=t.functionPath),typeof t.traceId=="string"&&t.traceId.length>0&&(o.traceId=t.traceId);const e={};for(const[n,s]of Object.entries(t.tags??{}))typeof s=="string"&&s.length>0&&n.length>0&&!Object.hasOwn(o,n)&&(e[n]=s);Object.assign(e,o);const r=Object.keys(e);if(r.length===0)return;if(r.length<=f)return e;const i=r.slice(-f);return Object.fromEntries(i.map(n=>[n,e[n]]))},g=t=>{const o=l(t);return o===void 0?void 0:JSON.stringify(o)},I="LUNORA_AI_DEFAULT_MODEL",y="LUNORA_AI_DEFAULT_EMBEDDING_MODEL",E="LUNORA_AI_GATEWAY_ACCOUNT_ID",h="LUNORA_AI_GATEWAY_ID",_="LUNORA_AI_GATEWAY_TOKEN",u="LUNORA_AI_GATEWAY_TAGS",O=t=>{const o=a(t,u);if(o!==void 0)try{const e=JSON.parse(o);if(typeof e!="object"||e===null||Array.isArray(e))throw new TypeError("expected a JSON object");const r={};for(const[i,n]of Object.entries(e))typeof n=="string"&&n.length>0&&(r[i]=n);return Object.keys(r).length>0?r:void 0}catch{d||(d=!0,console.warn(`[lunora:ai] ${u} is not a flat JSON object of strings — AI Gateway tags from it are ignored.`));return}},T=(t,o,e="byo-provider")=>{const r=a(t,E),i=a(t,h);if(r===void 0||i===void 0)return;const n=a(t,_),s={};n!==void 0&&(s["cf-aig-authorization"]=`Bearer ${n}`,e==="workers-ai-binding"&&!A&&(A=!0,console.warn(`[lunora:ai] ${_} is set, but the Workers AI binding cannot send a gateway auth token — Cloudflare's native gateway option has no authorization field. The token is ignored on this path; use a bring-your-own AI SDK provider (which sends cf-aig-authorization), or make the AI Gateway unauthenticated for Workers AI.`)));const c=g(o);return c!==void 0&&(s["cf-aig-metadata"]=c),{accountId:r,baseURL:`https://gateway.ai.cloudflare.com/v1/${r}/${i}`,gatewayId:i,headers:s}};export{y as AI_DEFAULT_EMBEDDING_MODEL_ENV,I as AI_DEFAULT_MODEL_ENV,E as AI_GATEWAY_ACCOUNT_ID_ENV,h as AI_GATEWAY_ID_ENV,f as AI_GATEWAY_METADATA_MAX_KEYS,u as AI_GATEWAY_TAGS_ENV,_ as AI_GATEWAY_TOKEN_ENV,l as buildAiGatewayMetadataFields,O as readAiGatewayEnvTags,a as readEnv,T as resolveAiGateway};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const p=/^(.*)-\d{4}-\d{2}-\d{2}$/u,a={"@cf/baai/bge-base-en-v1.5":{input:.067},"@cf/baai/bge-large-en-v1.5":{input:.204},"@cf/baai/bge-m3":{input:.012},"@cf/baai/bge-small-en-v1.5":{input:.02},"@cf/meta/llama-3.1-8b-instruct":{input:.28,output:.83},"@cf/meta/llama-3.3-70b-instruct-fp8-fast":{input:.29,output:2.25},"text-embedding-3-large":{input:.13},"text-embedding-3-small":{input:.02},"gpt-4o":{input:2.5,output:10},"gpt-4o-mini":{input:.15,output:.6},"gpt-5":{input:1.25,output:10}},c=e=>{const t=e.trim(),i=t.indexOf("/@"),n=i===-1?t:t.slice(i+1),u=n.lastIndexOf("/");return(u!==-1&&!n.startsWith("@")?[n,n.slice(u+1)]:[n]).flatMap(o=>{const r=p.exec(o)?.[1];return r===void 0?[o]:[o,r]})},b=(e,t=a)=>{for(const i of c(e))if(Object.hasOwn(t,i))return t[i]},d=(e,t,i)=>{if(e===void 0||e.length===0)return;const n=b(e,i);if(n===void 0)return;const u=Number.isFinite(t.inputTokens)?Math.max(0,t.inputTokens):0,s=Number.isFinite(t.outputTokens)?Math.max(0,t.outputTokens):0;if(u<=0&&s<=0)return;const o=(u*n.input+s*(n.output??0))/1e6;return Number.isFinite(o)?o:void 0};export{a as DEFAULT_MODEL_PRICES,d as estimateModelCost,b as lookupModelPrice};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{LunoraError as l}from"@lunora/errors";import{createWorkersAI as M}from"workers-ai-provider";import{readEnv as g,AI_DEFAULT_MODEL_ENV as w,AI_DEFAULT_EMBEDDING_MODEL_ENV as E,readAiGatewayEnvTags as y,buildAiGatewayMetadataFields as N,resolveAiGateway as h}from"./AI_DEFAULT_EMBEDDING_MODEL_ENV-
|
|
1
|
+
import{LunoraError as l}from"@lunora/errors";import{createWorkersAI as M}from"workers-ai-provider";import{readEnv as g,AI_DEFAULT_MODEL_ENV as w,AI_DEFAULT_EMBEDDING_MODEL_ENV as E,readAiGatewayEnvTags as y,buildAiGatewayMetadataFields as N,resolveAiGateway as h}from"./AI_DEFAULT_EMBEDDING_MODEL_ENV-B58f6yo6.mjs";const D=(d,o,n)=>{const v=o===void 0?void 0:y(o),a=v===void 0?n:{...n,tags:{...v,...n?.tags}},t=N(a);if(d!==void 0)return t!==void 0&&d.metadata===void 0?{...d,metadata:t}:d;if(o===void 0)return;const i=h(o,a,"workers-ai-binding");if(i!==void 0)return t===void 0?{id:i.gatewayId}:{id:i.gatewayId,metadata:t}},R=d=>{const{binding:o,defaultEmbeddingModel:n,defaultModel:v,env:a,gateway:t,metadata:i,provider:c}=d;if(!c&&!o)throw new l("INTERNAL","@lunora/ai: createAi requires a `binding` (env.AI) or a pre-built `provider`");const f=D(t,a,i),m=v??g(a,w),b=n??g(a,E),s=c??M({binding:o,gateway:f}),A=e=>{if(e===void 0){if(!m)throw new l("INTERNAL",`@lunora/ai: no model supplied and no default configured — pass a model id, or set ${w} in the Worker env (wrangler \`vars\` / \`.dev.vars\`)`);return s(m)}return typeof e=="string"?s(e):e},p=e=>{const r=s.textEmbeddingModel;if(typeof r!="function")throw new l("INTERNAL","@lunora/ai: the Workers AI provider does not expose `textEmbeddingModel`; pass an AI SDK EmbeddingModel (e.g. from @ai-sdk/openai) to embed()");return r.call(s,e)};return{embeddingModel:e=>{if(typeof e=="object")return e;const r=e??b;if(!r)throw new l("INTERNAL",`@lunora/ai: no embedding model supplied and no default configured — pass an embedding model id or an AI SDK EmbeddingModel, or set ${E} in the Worker env (wrangler \`vars\` / \`.dev.vars\`)`);return p(r)},model:A,run:async(e,r,u)=>{if(!o)throw new l("INTERNAL","@lunora/ai: ai.run requires the `binding` (env.AI) — it is unavailable when only a custom `provider` was supplied");const I=f!==void 0&&u?.gateway===void 0?{...u,gateway:f}:u;return o.run(e,r,I)},workersai:s}};export{R as default};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import{LunoraError as b,isLunoraError as _e}from"@lunora/errors";import{tool as Te,jsonSchema as Ae,embedMany as Me,embed as Ne}from"ai";import{s as De}from"./stable-key-B_BlboiY.mjs";import{estimateModelCost as Re}from"./DEFAULT_MODEL_PRICES-CIJLs1Ah.mjs";import Ce from"./fixedWindowChunks-C461ahRE.mjs";import{c as Be,I as Oe}from"./concurrent-C6nqBv41.mjs";import{contentHash as $e}from"./contentHash-BIn6ECP8.mjs";import{hybridRank as Ke}from"./hybridRank-B4skyCLx.mjs";import{VECTORIZE_CAPABILITIES as ae,vectorizeStore as Ue}from"./VECTORIZE_CAPABILITIES-CUQDoxis.mjs";const Qe=1e3,Ve=200,Fe=5,Le=4,ie=ae.maxMetadataBytes===!1?Number.POSITIVE_INFINITY:ae.maxMetadataBytes,je=2*1024,le="__ragChunk",me="__ragSource",C="__ragText",P="__ragHash",Y="__ragChunks",$="__ragImportance",he="__ragModel",ze=new Set([le,Y,P,$,he,me,C]),Pe=(e,d,h,w)=>{if(w===!1)return;const v=new TextEncoder().encode(JSON.stringify(e)).length;if(v<=w)return;const K=(typeof e[C]=="string"?new TextEncoder().encode(e[C]).length:0)*2>v?"lower `chunkSize` (it counts characters, not bytes — multibyte text costs up to 3 bytes each), or supply `textStore` to move chunk text out of metadata entirely":"attach less per-source `metadata`";throw new b("BAD_REQUEST",`@lunora/ai/rag: chunk ${String(d)} of "${h}" carries ${String(v)} bytes of metadata, over the store's ${String(w)}-byte per-vector ceiling — ${K}`)},Ye=(e,d,h)=>{if(h===!1)return;const w=new TextEncoder().encode(e).length;if(!(w<=h))throw new b("BAD_REQUEST",`@lunora/ai/rag: chunk id "${e}" for source "${d}" is ${String(w)} bytes, over the store's ${String(h)}-byte per-vector id ceiling — shorten the source id (hash long keys before indexing them) or shorten the \`namespace\`, which is prefixed onto every chunk id`)},He=/^[\w.-]{1,40}$/,pe=e=>e===void 0?"":`${encodeURIComponent(e)}#`,R=(e,d,h)=>`${pe(e)}${d}#${String(h)}`,de=(e,d)=>{const h=pe(d),w=h!==""&&e.startsWith(h)?e.slice(h.length):e,v=w.lastIndexOf("#"),A=v===-1?Number.NaN:Number(w.slice(v+1));return v===-1||!Number.isInteger(A)||A<0?{chunkIndex:0,sourceId:w}:{chunkIndex:A,sourceId:w.slice(0,v)}},qe=async e=>$e(new TextEncoder().encode(e)),We=e=>{try{return De([e.text,e.metadata,e.importance])}catch{return}},Ze=(e,d)=>{const h=[],w=[];for(const v of e)v.score>=d?h.push(v):w.push(v.id);return{kept:h,rejectedIds:w}},Xe=e=>e!==void 0&&Object.keys(e).length>0,j=e=>{if(!e)return;const d=Object.entries(e).filter(([h])=>!ze.has(h));return d.length>0?Object.fromEntries(d):void 0},ce=new Set,Je=e=>{ce.has(e)||(ce.add(e),console.warn(`[@lunora/ai/rag] index "${e}" is used without a namespace — in a multi-tenant/sharded
|
|
2
|
+
app this shares one tenant's chunks (text included) with every other tenant, since
|
|
3
|
+
Vectorize indexes are account-global. Pass \`namespace\` (the shard/tenant key) on both
|
|
4
|
+
index() and retrieve(). Single-tenant apps suppress this via { allowSharedNamespace: true }.`))},Ge=e=>e.map(d=>`[source:${d.sourceId}#${String(d.chunkIndex)}]
|
|
5
|
+
${d.text}`).join(`
|
|
6
|
+
|
|
7
|
+
`),ue=(e,d)=>{if(typeof e=="object")return e;if(d===void 0)throw new b("INTERNAL","@lunora/ai/rag: `embeddingModel` is a Workers AI model id (or omitted) but the bound context has no `ai` (env.AI). Pass an AI SDK EmbeddingModel object to embed without Workers AI, or bind a context whose `ctx.ai` is wired.");return d.embeddingModel(e)},z=e=>{const d=e.modelId;return typeof d=="string"&&d.length>0?d:void 0},et=e=>{if(!(typeof e!="object"||e===null)){for(const d of Object.values(e))if(typeof d=="object"&&d!==null){const{cost:h}=d;if(typeof h=="number"&&Number.isFinite(h))return h}}},tt=e=>{if(e===void 0)throw new b("INTERNAL","@lunora/ai/rag: the bound context has no `vectors` (env.VECTORIZE) and no `store` is configured — bind a context whose `ctx.vectors` is wired, or configure `store` (e.g. `sqliteVectorStore`) to back this index without Vectorize.");return e},mt=e=>{if(typeof e.index!="string"||e.index.length===0)throw new b("BAD_REQUEST","@lunora/ai/rag: `index` must be a non-empty Vectorize index name");const d=e.chunkSize??Qe,h=e.chunkOverlap??Ve;if(!Number.isInteger(d)||d<1)throw new b("BAD_REQUEST","@lunora/ai/rag: `chunkSize` must be a positive integer");if(!Number.isInteger(h)||h<0||h>=d)throw new b("BAD_REQUEST","@lunora/ai/rag: `chunkOverlap` must be a non-negative integer smaller than `chunkSize`");const w=ie-je;if(!e.chunk&&!e.textStore&&!e.store&&d>w)throw new b("BAD_REQUEST",`@lunora/ai/rag: \`chunkSize\` of ${String(d)} leaves no room under Vectorize's ${String(ie)}-byte metadata limit, which also carries this chunk's bookkeeping and any \`metadata\` you attach — keep it under ${String(w)}, or supply \`textStore\` to move chunk text out of metadata entirely`);const v=e.topK??Fe;if(!Number.isInteger(v)||v<1)throw new b("BAD_REQUEST","@lunora/ai/rag: `topK` must be a positive integer");if(e.maxEmbeddingDimensions!==void 0&&e.maxEmbeddingDimensions!==!1&&(!Number.isInteger(e.maxEmbeddingDimensions)||e.maxEmbeddingDimensions<1))throw new b("BAD_REQUEST","@lunora/ai/rag: `maxEmbeddingDimensions` must be a positive integer, or `false` to disable the check");if(e.embeddingModelVersion!==void 0&&!He.test(e.embeddingModelVersion))throw new b("BAD_REQUEST",'@lunora/ai/rag: `embeddingModelVersion` must match /^[A-Za-z0-9._-]{1,40}$/ (a short, stable tag like "bge-v1.5")');if(e.candidates!==void 0&&(!Number.isInteger(e.candidates)||e.candidates<1))throw new b("BAD_REQUEST","@lunora/ai/rag: `candidates` must be a positive integer");if(e.cacheEmbeddings!==void 0&&(!Number.isInteger(e.cacheEmbeddings)||e.cacheEmbeddings<0))throw new b("BAD_REQUEST","@lunora/ai/rag: `cacheEmbeddings` must be a non-negative integer");const A=e.cacheEmbeddings??0,K=e.rerank,fe=e.chunk??(p=>Ce(p,d,h)),{textStore:k}=e,B=e.embeddingModelVersion,Q=p=>B===void 0?p:p===void 0?B:`${B}::${p}`;return p=>{const E=e.store?e.store(p):Ue(tt(p.vectors),e.index),H=k?E.capabilities.maxTopK:E.capabilities.maxTopKWithMetadata,U=e.maxEmbeddingDimensions??E.capabilities.maxDimensions;let O;const q=typeof p.trace=="function"?p.trace:void 0;let W=U===!1;const Z=(t,r)=>{if(W||(W=!0,U===!1||t<=U))return;const s=z(r);throw new b("BAD_REQUEST",`@lunora/ai/rag: embedding model${s===void 0?"":` "${s}"`} produces ${String(t)}-dimension vectors, over the ${String(U)}-dimension ceiling of index "${e.index}" — either truncate them with the provider's \`dimensions\` option (Matryoshka models such as text-embedding-3-large support this), or set \`maxEmbeddingDimensions: false\` if this index is not Vectorize-backed`)},N=new Map,ge=(t,r)=>{if(A!==0)for(N.set(t,r);N.size>A;){const s=N.keys().next();if(s.done===!0)break;N.delete(s.value)}},X=async t=>{const r=N.get(t);if(r!==void 0)return r;O??=ue(e.embeddingModel,p.ai);const s=O,a=async n=>{const{embedding:m,providerMetadata:y,usage:l}=await Ne({model:s,value:t});if(Z(m.length,s),n!==void 0){const c=l.tokens;typeof c=="number"&&Number.isFinite(c)&&n.setAttribute("gen_ai.usage.input_tokens",c);const u=et(y),f=u??Re(z(s),{inputTokens:typeof c=="number"?c:void 0});f!==void 0&&(n.setAttribute("gen_ai.usage.cost",f),n.setAttribute("lunora.usage.cost.source",u===void 0?"estimated":"provider"))}return ge(t,m),m};if(q===void 0)return a();const o=z(s),i=typeof p.conversationId=="string"&&p.conversationId.length>0?p.conversationId:void 0;return q("ai.embed",(n,m)=>a(m),{"gen_ai.operation.name":"embeddings",...o===void 0?{}:{"gen_ai.request.model":o},...i===void 0?{}:{"gen_ai.conversation.id":i}})},be=async(t,r,s)=>{if(!e.transformQuery||r?.transformQuery===!1)return[t];const a=typeof p.conversationId=="string"&&p.conversationId.length>0?p.conversationId:void 0,o=await e.transformQuery(t,{conversationId:a,namespace:s}),i=(typeof o=="string"?[o]:[...o]).map(n=>n.trim()).filter(n=>n.length>0);return i.length>0?i:[t]},we=async t=>{const r=new Map,s=[...new Set(t.filter(a=>!N.has(a)))];if(s.length<2)return r;O??=ue(e.embeddingModel,p.ai);try{const{embeddings:a}=await Me({model:O,values:s});if(a.length!==s.length)return r;const[o]=a;o!==void 0&&Z(o.length,O);for(const[i,n]of s.entries())r.set(n,a[i])}catch(a){if(_e(a))throw a}return r},V=t=>{if(t===void 0){if(e.requireNamespace)throw new b("BAD_REQUEST",`@lunora/ai/rag: index "${e.index}" requires a namespace (requireNamespace is set) — pass the tenant/shard key on index()/retrieve()/remove()`);e.allowSharedNamespace||Je(e.index)}},J=async(t,r)=>{const[s]=await E.getByIds([R(r,t,0)],r),a=s?.metadata?.[P],o=s?.metadata?.[Y];return{chunks:typeof o=="number"&&Number.isInteger(o)&&o>0?o:void 0,hash:typeof a=="string"?a:void 0}},G=async(t,r,s,a)=>{const o=Array.from({length:s-r},(i,n)=>R(a,t,r+n));o.length!==0&&(await E.deleteByIds(o,a),await k?.remove?.(o,{namespace:a}),await e.lexicalStore?.remove?.(o,{namespace:a}))},ve=async t=>{if(V(t.namespace),t.importance!==void 0&&(typeof t.importance!="number"||t.importance<0||t.importance>1))throw new b("BAD_REQUEST","@lunora/ai/rag: `importance` must be a number in [0, 1]");const r=Q(t.namespace),s=We(t),a=await qe(s??t.text),o=await J(t.id,r);if(t.reindex!==!0&&s!==void 0&&o.hash===a&&o.chunks!==void 0)return{chunks:o.chunks,ids:Array.from({length:o.chunks},(c,u)=>R(r,t.id,u)),unchanged:!0};const i=fe(t.text),n=i.map((c,u)=>R(r,t.id,u)),m=n.at(-1);if(m!==void 0&&Ye(m,t.id,E.capabilities.maxIdBytes),i.length===0&&t.allowEmptySources===!1)throw new b("BAD_REQUEST",`@lunora/ai/rag: source "${t.id}" produced zero chunks — set allowEmptySources: true to allow this`);if(i.length>0){const c=i.map((u,f)=>({chunkIndex:f,id:n[f],sourceId:t.id,text:u,...t.metadata===void 0?{}:{metadata:t.metadata}}));k&&await k.put(c,{namespace:r}),e.lexicalStore&&await e.lexicalStore.index(c,{namespace:r})}const y=await we(i),l=async c=>y.get(c)??await X(c);return await Be(i,Oe,async(c,u)=>{const f=n[u],x={...t.metadata,[le]:u,[me]:t.id};k||(x[C]=c),t.importance!==void 0&&(x[$]=t.importance),u===0&&(x[P]=a,x[Y]=i.length,B!==void 0&&(x[he]=B)),Pe(x,u,t.id,E.capabilities.maxMetadataBytes),await E.upsert({embed:l,id:f,input:c,metadata:x,namespace:r}),t.onChunk?.({chunkIndex:u,id:f,text:c,total:i.length})}),o.chunks!==void 0&&o.chunks>i.length&&await G(t.id,i.length,o.chunks,r),{chunks:i.length,ids:n,unchanged:!1}},ye=async t=>{V(t.namespace);const r=Q(t.namespace),a=(await J(t.id,r)).chunks??1;await G(t.id,0,a,r)},ee=async(t,r)=>{const s=new Map;if(t.length===0)return s;if(k){const o=await k.getMany(t,{namespace:r});for(const[i,n]of t.entries()){const m=o[i];typeof m=="string"&&s.set(n,m)}return s}const a=await E.getByIds(t,r);for(const o of a){const i=o.metadata?.[C];typeof i=="string"&&s.set(o.id,i)}return s},Ee=async(t,r,s)=>{const a=r?.chunkContext?.before??0,o=r?.chunkContext?.after??0;if(a===0&&o===0)return t;if(!Number.isInteger(a)||a<0||!Number.isInteger(o)||o<0)throw new b("BAD_REQUEST","@lunora/ai/rag: `chunkContext.before`/`chunkContext.after` must be non-negative integers");const i=new Map(t.map(l=>[l.id,l.text])),n=new Set;for(const l of t)for(let c=-a;c<=o;c+=1){const u=l.chunkIndex+c,f=R(s,l.sourceId,u);c!==0&&u>=0&&!i.has(f)&&n.add(f)}const m=await ee([...n],s),y=(l,c)=>{const u=R(s,l,c);return i.get(u)??m.get(u)};return t.map(l=>{const c=[];for(let u=-a;u<=o;u+=1){const f=u===0?l.text:y(l.sourceId,l.chunkIndex+u);f!==void 0&&c.push(f)}return{...l,text:c.join(`
|
|
8
|
+
`)}})},xe=t=>{if(typeof t=="string"){const r=e.filters!==void 0&&Object.hasOwn(e.filters,t)?e.filters[t]:void 0;if(!r)throw new b("NOT_FOUND",`@lunora/ai/rag: unknown named filter "${t}" — must be one of the keys declared in RagConfig.filters`);return r.filter}return t},Ie=(t,r)=>t.matches.map(s=>{const a=s.metadata??{},o=de(s.id,r),i=a[C],n=a[$],m=typeof n=="number"&&n>=0&&n<=1?n:1;return{chunkIndex:o.chunkIndex,id:s.id,importance:m,metadata:j(a),score:s.score*m,sourceId:o.sourceId,text:typeof i=="string"?i:""}}),Se=async(t,r)=>{if(!k)return t;const s=t.map(n=>n.id),[a,o]=await Promise.all([ee(s,r),E.getByIds(s,r)]),i=new Map(o.map(n=>[n.id,n.metadata]));return t.flatMap(n=>{const m=a.get(n.id);if(m===void 0)return[];const y=i.get(n.id),l=y?.[$],c=typeof l=="number"&&l>=0&&l<=1?l:n.importance,f=(n.importance===0?0:n.score/n.importance)*c;return[{...n,importance:c,metadata:j(y)??n.metadata,score:f,text:m}]})},te=async(t,r,s)=>{const a=t.map(n=>n.id).filter(n=>!r.has(n)),o=a.length===0?[]:await E.getByIds(a,s),i=new Map(o.map(n=>[n.id,n.metadata]));return t.map(n=>{const m=de(n.id,s),y=i.get(n.id),l=y?.[$];return{chunkIndex:m.chunkIndex,id:n.id,importance:typeof l=="number"&&l>=0&&l<=1?l:1,metadata:j(y),score:n.score,sourceId:m.sourceId,text:n.text}})},ne=async(t,r)=>{V(r?.namespace);const s=Q(r?.namespace),a=xe(r?.filter),o=e.rlsFilter?await e.rlsFilter(p.auth):void 0,i=o?{...a,...o}:a,n=Math.min(r?.topK??v,H),m=await be(t,r,s),y=m[0],l=K!==void 0&&r?.rerank!==!1,u=l||e.lexicalStore!==void 0||e.graphStore!==void 0||m.length>1?Math.min(e.candidates??n*Le,H):n,f=r?.minScore,x=new Set,re=async g=>{const _=await E.query({embed:X,filter:i,input:g,namespace:s,returnMetadata:k?"indexed":"all",topK:u}),M=await Se(Ie(_,s),s);if(f===void 0)return M;const{kept:S,rejectedIds:T}=Ze(M,f);for(const ke of T)x.add(ke);return S},D=[{chunks:await re(y)}];for(const g of m.slice(1))D.push({chunks:await re(g)});if(e.lexicalStore){const g=await e.lexicalStore.search(y,{filter:i,namespace:s,topK:e.lexicalTopK??u}),_=new Set(D.flatMap(S=>S.chunks).map(S=>S.id)),M=g.filter(S=>!x.has(S.id)||_.has(S.id));D.push({chunks:await te(M,_,s)})}const F=D.flatMap(g=>[...g.chunks]);if(e.graphStore&&F.length>0&&(e.graphStore.enforcesFilter||!Xe(i))){const g=[...new Set(F.map(T=>T.sourceId))],_=await e.graphStore.related(g,{filter:i,namespace:s,topK:e.graphTopK??u}),M=new Set(F.map(T=>T.id)),S=_.filter(T=>!x.has(T.id)||M.has(T.id));D.push({chunks:await te(S,M,s),weight:"proximity"})}const L=D.filter(g=>g.chunks.length>0);let I=L.length>1?[...Ke(L)]:[...L[0]?.chunks??[]];I.sort((g,_)=>_.score-g.score),l&&(I=[...await K(y,I)]),I=I.slice(0,n),I=[...await Ee(I,r,s)];const se=[],oe=new Set;for(const g of I)oe.has(g.sourceId)||(oe.add(g.sourceId),se.push({id:g.sourceId,metadata:g.metadata,weight:g.importance}));return r?.onRetrieve?.({matches:I.length,query:t}),{chunks:I,context:Ge(I),sources:se}};return{asTool:t=>Te({description:t?.description??`Search the "${e.index}" knowledge base for passages relevant to a natural-language query.`,execute:async({query:r})=>ne(r,{namespace:t?.namespace,topK:t?.topK}),inputSchema:Ae({properties:{query:{description:"The natural-language search query.",type:"string"}},required:["query"],type:"object"})}),index:ve,remove:ye,retrieve:ne}}};export{mt as default};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
`)){if(C.test(n)){s=!s,t.push(n);continue}const i=s?void 0:z.exec(n)??void 0;if(i===void 0){t.push(n);continue}c();const l=i[1].length,a=i[2].trim();e=[...e.
|
|
1
|
+
import f from"./fixedWindowChunks-C461ahRE.mjs";const E=1e3,T=200,w=256,b=/(?<=[!.?])\s/u,z=/^(#{1,6})\s(.*)$/u,C=/^ {0,3}(?:`{3,}|~{3,})/u,k=(o,r)=>{const e=o?.size??E,t=o?.overlap??T;if(!Number.isInteger(e)||e<1)throw new RangeError(`${r}: \`size\` must be a positive integer`);if(!Number.isInteger(t)||t<0||t>=e)throw new RangeError(`${r}: \`overlap\` must be a non-negative integer smaller than \`size\``);return{overlap:t,size:e}},d=o=>o.split(b).map(r=>r.trim()).filter(r=>r.length>0),x=(o,r)=>o.length===0?0:o.reduce((e,t)=>e+t.length,0)+r.length*(o.length-1),N=(o,r)=>{if(r.overlap===0)return[];const e=[];for(let t=o.length-1;t>=0&&e.length<o.length-1&&(e.unshift(o[t]),!(r.measure(e)>=r.overlap));t-=1);return e},v=(o,r)=>{const{budget:e,measure:t,separator:u,splitOversized:s}=r,c=[];let n=[];const i=()=>{n.length>0&&c.push(n.join(u))};for(const l of o)if(l.trim().length!==0){if(t([l])>e){i(),n=[],c.push(...s(l));continue}if(n.length>0&&t([...n,l])>e)for(i(),n=N(n,r);n.length>0&&t([...n,l])>e;)n.shift();n.push(l)}return i(),c.filter(l=>l.trim().length>0)},A=o=>{const r=[];let e=[],t=[],u=[],s=!1;const c=()=>{t.some(n=>n.trim().length>0)&&r.push({body:t,trail:u}),t=[]};for(const n of o.split(`
|
|
2
|
+
`)){if(C.test(n)){s=!s,t.push(n);continue}const i=s?void 0:z.exec(n)??void 0;if(i===void 0){t.push(n);continue}c();const l=i[1].length,a=i[2].trim();e=[...e.filter(h=>h.indexOf(" ")<l),`${"#".repeat(l)} ${a}`],u=e}return c(),r},m=o=>{const{overlap:r,size:e}=k(o,"sentenceChunker");return t=>{const u=t.trim();return u.length===0?[]:v(d(u),{budget:e,measure:s=>x(s," "),overlap:r,separator:" ",splitOversized:s=>f(s,e,r)})}},M=o=>{const{overlap:r,size:e}=k(o,"markdownChunker"),t=m({overlap:r,size:e}),u=(s,c)=>{const n=e-c.length;return n<Math.ceil(e/4)?t(s):m({overlap:Math.min(r,n-1),size:n})(s).map(i=>`${c}${i}`)};return s=>{if(s.trim().length===0)return[];const c=[];for(const n of A(s)){const i=n.body.join(`
|
|
3
3
|
`).trim();i.length>0&&c.push(...u(i,n.trail.length>0?`${n.trail.join(" > ")}
|
|
4
4
|
|
|
5
|
-
`:""))}return c}},S=o=>{const{countTokens:r}=o,e=o.maxTokens??w,t=o.overlapTokens??0;if(typeof r!="function")throw new TypeError("tokenChunker: `countTokens` must be a function — pass your model's tokenizer (e.g. js-tiktoken)");if(!Number.isInteger(e)||e<1)throw new RangeError("tokenChunker: `maxTokens` must be a positive integer");if(!Number.isInteger(t)||t<0||t>=e)throw new RangeError("tokenChunker: `overlapTokens` must be a non-negative integer smaller than `maxTokens`");const u=s=>{const c=[],n=[s];for(;n.length>0;){const i=n.pop(),l=r(i);if(l<=e||i.length<=1){c.push(i);continue}const a=Math.floor(i.length*e/Math.max(1,l)),
|
|
5
|
+
`:""))}return c}},S=o=>{const{countTokens:r}=o,e=o.maxTokens??w,t=o.overlapTokens??0;if(typeof r!="function")throw new TypeError("tokenChunker: `countTokens` must be a function — pass your model's tokenizer (e.g. js-tiktoken)");if(!Number.isInteger(e)||e<1)throw new RangeError("tokenChunker: `maxTokens` must be a positive integer");if(!Number.isInteger(t)||t<0||t>=e)throw new RangeError("tokenChunker: `overlapTokens` must be a non-negative integer smaller than `maxTokens`");const u=s=>{const c=[],n=[s];for(;n.length>0;){const i=n.pop(),l=r(i);if(l<=e||i.length<=1){c.push(i);continue}const a=Math.floor(i.length*e/Math.max(1,l)),h=Math.min(i.length-1,Math.max(1,a)),p=f(i,h,0);for(let g=p.length-1;g>=0;g-=1)n.push(p[g])}return c};return s=>{const c=s.trim();return c.length===0?[]:v(d(c),{budget:e,measure:n=>r(n.join(" ")),overlap:t,separator:" ",splitOversized:u})}};export{M as markdownChunker,m as sentenceChunker,S as tokenChunker};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{s as l}from"./stable-key-B_BlboiY.mjs";const v=c=>{const m=c.delayMs??0,y=(d,a)=>c.id===void 0?a:c.id(d),e=d=>d===void 0?void 0:c.text(d),u=d=>c.metadata?.(d),p=d=>c.namespace?.(d),o=(d,a)=>{const t=p(d);return{id:y(d,a),...t===void 0?{}:{namespace:t}}},f=(d,a,t)=>{const s=u(d);return{...o(d,a),...s===void 0?{}:{metadata:s},text:t}},g=(d,a)=>{try{return l(u(d))===l(u(a))}catch{return!1}},r=async(d,a)=>{await d.scheduler.runAfter(m,c.action,a)};return{afterDelete:async(d,a)=>{const t=a.previous??a.doc;await r(d,{deleted:!0,...t===void 0?{id:a.id}:o(t,a.id)})},afterInsert:async(d,a)=>{const t=e(a.doc);t===void 0||a.doc===void 0||await r(d,f(a.doc,a.id,t))},afterUpdate:async(d,a)=>{if(a.doc===void 0)return;const t=e(a.doc),s=e(a.previous),i=o(a.doc,a.id),n=a.previous===void 0?i:o(a.previous,a.id),w=n.id!==i.id||n.namespace!==i.namespace,h=c.metadata!==void 0&&a.previous!==void 0&&!g(a.doc,a.previous);if(w)await r(d,{deleted:!0,...n});else if(t===s&&!h)return;await(t===void 0?r(d,{deleted:!0,...i}):r(d,f(a.doc,a.id,t)))}}};export{v as ragSyncTriggers};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const a=/["\\\u0000-\u001F\uD800-\uDFFF]/,f=t=>a.test(t)?JSON.stringify(t):`"${t}"`,y=t=>{if(t===void 0)return"null";if(typeof t=="bigint")throw new TypeError("stableStringify: cannot use a bigint in a stable JSON cache key — pass it as a string, or use stableWireKey");if(typeof t=="number"){if(Number.isNaN(t))return"nan";if(t===1/0)return"inf";if(t===-1/0)return"-inf";if(Object.is(t,-0))return"-0"}if(typeof t=="string")return f(t);if(t===null||typeof t!="object")return JSON.stringify(t);if(Array.isArray(t)){let r="[";for(let n=0;n<t.length;n++)n>0&&(r+=","),r+=y(t[n]);return r+"]"}const i=Object.getPrototypeOf(t);if(i!==null&&i!==Object.prototype){const r=t.constructor?.name??"value";throw new TypeError(`stableStringify: cannot use a ${r} in a stable JSON cache key — only plain objects, arrays, and JSON primitives are supported (wire-typed values key via stableWireKey)`)}const o=t,c=Object.keys(o).sort();let e="{",s=!0;for(const r of c){const n=o[r];n!==void 0&&(s?s=!1:e+=",",e+=f(r),e+=":",e+=y(n))}return e+"}"};export{y as s};
|
package/dist/rag/index.d.mts
CHANGED
|
@@ -1217,24 +1217,6 @@ interface SqliteVectorStoreOptions {
|
|
|
1217
1217
|
table?: string;
|
|
1218
1218
|
}
|
|
1219
1219
|
declare const sqliteVectorStore: (options: SqliteVectorStoreOptions) => RagVectorStore;
|
|
1220
|
-
/**
|
|
1221
|
-
* Keep a RAG index in step with a table, so the table IS the index.
|
|
1222
|
-
*
|
|
1223
|
-
* `rag.index(...)` is a manual call, which means an app that edits a document
|
|
1224
|
-
* has to remember to re-index it — and a forgotten call is invisible: retrieval
|
|
1225
|
-
* keeps answering, just from stale text. The fix is to hang the re-index off the
|
|
1226
|
-
* write itself.
|
|
1227
|
-
*
|
|
1228
|
-
* Embedding is network I/O, so it cannot run inside the mutation that wrote the
|
|
1229
|
-
* row. The bridge is the two seams that already exist: a table `.triggers()`
|
|
1230
|
-
* handler runs in the write path and can `ctx.scheduler.runAfter(...)`, so the
|
|
1231
|
-
* trigger records the intent and an internal ACTION does the embedding a moment
|
|
1232
|
-
* later. That is what {@link ragSyncTriggers} wires up.
|
|
1233
|
-
*
|
|
1234
|
-
* Re-indexing unchanged text is already cheap — `rag.index` short-circuits on a
|
|
1235
|
-
* content hash — but this skips scheduling entirely when an update didn't touch
|
|
1236
|
-
* the indexed text, so an unrelated column edit costs nothing at all.
|
|
1237
|
-
*/
|
|
1238
1220
|
/**
|
|
1239
1221
|
* A dispatchable function reference — the `internal.docs.reindex` you pass as
|
|
1240
1222
|
* `action`. Typed structurally (rather than `unknown`) so passing the wrong
|
|
@@ -1260,10 +1242,14 @@ interface RagSyncEvent {
|
|
|
1260
1242
|
}
|
|
1261
1243
|
/** What the scheduled action receives — one document to re-index, or one to drop. */
|
|
1262
1244
|
interface RagSyncArgs extends Record<string, unknown> {
|
|
1263
|
-
/** `true` when the source
|
|
1245
|
+
/** `true` when the source's chunks must go: call `rag.remove({ id, namespace })`. */
|
|
1264
1246
|
deleted?: boolean;
|
|
1265
1247
|
/** The source id — the same `id` you pass to `rag.index`/`rag.remove`. */
|
|
1266
1248
|
id: string;
|
|
1249
|
+
/** The {@link RagSyncOptions.metadata} projection, to pass to `rag.index`. Absent on a delete or when there is none. */
|
|
1250
|
+
metadata?: Record<string, unknown>;
|
|
1251
|
+
/** The {@link RagSyncOptions.namespace} projection, to pass to `rag.index`/`rag.remove`. Absent when there is none. */
|
|
1252
|
+
namespace?: string;
|
|
1267
1253
|
/** The text to embed. Absent on a delete. */
|
|
1268
1254
|
text?: string;
|
|
1269
1255
|
}
|
|
@@ -1282,6 +1268,19 @@ interface RagSyncOptions<Document extends Record<string, unknown> = Record<strin
|
|
|
1282
1268
|
delayMs?: number;
|
|
1283
1269
|
/** The source id to index under. Defaults to the row's own id. */
|
|
1284
1270
|
id?: (document: Document) => string;
|
|
1271
|
+
/**
|
|
1272
|
+
* Metadata copied onto every chunk (`rag.index({ metadata })`) — the fields a
|
|
1273
|
+
* `rlsFilter` or `metadataFilter` scopes retrieval by, such as a tenant or
|
|
1274
|
+
* owner id. Compared on update like the text: a row that moves tenant with
|
|
1275
|
+
* unchanged text is re-indexed, so the old tenant stops retrieving it.
|
|
1276
|
+
*/
|
|
1277
|
+
metadata?: (document: Document) => Record<string, unknown> | undefined;
|
|
1278
|
+
/**
|
|
1279
|
+
* The Vectorize namespace to index under (`rag.index({ namespace })`), e.g.
|
|
1280
|
+
* the tenant key. When it changes on update, the chunks in the old namespace
|
|
1281
|
+
* are removed before the row is indexed into the new one.
|
|
1282
|
+
*/
|
|
1283
|
+
namespace?: (document: Document) => string | undefined;
|
|
1285
1284
|
/** The text to embed. Return `undefined` to skip the row (a draft, an empty body). */
|
|
1286
1285
|
text: (document: Document) => string | undefined;
|
|
1287
1286
|
}
|
|
@@ -1303,17 +1302,37 @@ type RagSyncHandler = (context: RagSyncTriggerContext, event: RagSyncEvent) => P
|
|
|
1303
1302
|
* });
|
|
1304
1303
|
* ```
|
|
1305
1304
|
*
|
|
1306
|
-
* The action on the other end
|
|
1305
|
+
* The action on the other end forwards every field it receives:
|
|
1307
1306
|
*
|
|
1308
1307
|
* ```ts
|
|
1309
|
-
* export const reindex = internalAction
|
|
1310
|
-
*
|
|
1308
|
+
* export const reindex = internalAction
|
|
1309
|
+
* .input({
|
|
1310
|
+
* deleted: v.optional(v.boolean()),
|
|
1311
|
+
* id: v.string(),
|
|
1312
|
+
* metadata: v.optional(v.record(v.string(), v.any())),
|
|
1313
|
+
* namespace: v.optional(v.string()),
|
|
1314
|
+
* text: v.optional(v.string()),
|
|
1315
|
+
* })
|
|
1316
|
+
* .action(async ({ args, ctx }) => {
|
|
1311
1317
|
* const rag = docsRag(ctx);
|
|
1318
|
+
* const { id, metadata, namespace, text } = args;
|
|
1312
1319
|
*
|
|
1313
|
-
* await (args.deleted === true ||
|
|
1314
|
-
* }
|
|
1315
|
-
* );
|
|
1320
|
+
* await (args.deleted === true || text === undefined ? rag.remove({ id, namespace }) : rag.index({ id, metadata, namespace, text }));
|
|
1321
|
+
* });
|
|
1316
1322
|
* ```
|
|
1323
|
+
*
|
|
1324
|
+
* A multi-tenant table also passes `metadata` and/or `namespace` (for example
|
|
1325
|
+
* `metadata: (doc) => ({ orgId: doc.orgId })`). Without them, a row that moves
|
|
1326
|
+
* tenant keeps its old scope in the index and stays retrievable by the tenant it
|
|
1327
|
+
* left. An action that drops `namespace` removes from (or indexes into) the
|
|
1328
|
+
* wrong namespace, and one whose `input` omits `metadata` / `namespace` rejects
|
|
1329
|
+
* the scheduled args and fails every job.
|
|
1330
|
+
*
|
|
1331
|
+
* Re-scoping is not atomic: the triggers only SCHEDULE the work, so after a row
|
|
1332
|
+
* changes namespace its chunks stay retrievable in the old one until the
|
|
1333
|
+
* scheduled delete runs (and until the re-index runs, it is missing from the new
|
|
1334
|
+
* one). The same holds for a metadata change. Lower `delayMs` shortens that
|
|
1335
|
+
* window; it does not close it.
|
|
1317
1336
|
*/
|
|
1318
1337
|
declare const ragSyncTriggers: <Document extends Record<string, unknown> = Record<string, unknown>>(options: RagSyncOptions<Document>) => {
|
|
1319
1338
|
afterDelete: RagSyncHandler;
|
package/dist/rag/index.d.ts
CHANGED
|
@@ -1217,24 +1217,6 @@ interface SqliteVectorStoreOptions {
|
|
|
1217
1217
|
table?: string;
|
|
1218
1218
|
}
|
|
1219
1219
|
declare const sqliteVectorStore: (options: SqliteVectorStoreOptions) => RagVectorStore;
|
|
1220
|
-
/**
|
|
1221
|
-
* Keep a RAG index in step with a table, so the table IS the index.
|
|
1222
|
-
*
|
|
1223
|
-
* `rag.index(...)` is a manual call, which means an app that edits a document
|
|
1224
|
-
* has to remember to re-index it — and a forgotten call is invisible: retrieval
|
|
1225
|
-
* keeps answering, just from stale text. The fix is to hang the re-index off the
|
|
1226
|
-
* write itself.
|
|
1227
|
-
*
|
|
1228
|
-
* Embedding is network I/O, so it cannot run inside the mutation that wrote the
|
|
1229
|
-
* row. The bridge is the two seams that already exist: a table `.triggers()`
|
|
1230
|
-
* handler runs in the write path and can `ctx.scheduler.runAfter(...)`, so the
|
|
1231
|
-
* trigger records the intent and an internal ACTION does the embedding a moment
|
|
1232
|
-
* later. That is what {@link ragSyncTriggers} wires up.
|
|
1233
|
-
*
|
|
1234
|
-
* Re-indexing unchanged text is already cheap — `rag.index` short-circuits on a
|
|
1235
|
-
* content hash — but this skips scheduling entirely when an update didn't touch
|
|
1236
|
-
* the indexed text, so an unrelated column edit costs nothing at all.
|
|
1237
|
-
*/
|
|
1238
1220
|
/**
|
|
1239
1221
|
* A dispatchable function reference — the `internal.docs.reindex` you pass as
|
|
1240
1222
|
* `action`. Typed structurally (rather than `unknown`) so passing the wrong
|
|
@@ -1260,10 +1242,14 @@ interface RagSyncEvent {
|
|
|
1260
1242
|
}
|
|
1261
1243
|
/** What the scheduled action receives — one document to re-index, or one to drop. */
|
|
1262
1244
|
interface RagSyncArgs extends Record<string, unknown> {
|
|
1263
|
-
/** `true` when the source
|
|
1245
|
+
/** `true` when the source's chunks must go: call `rag.remove({ id, namespace })`. */
|
|
1264
1246
|
deleted?: boolean;
|
|
1265
1247
|
/** The source id — the same `id` you pass to `rag.index`/`rag.remove`. */
|
|
1266
1248
|
id: string;
|
|
1249
|
+
/** The {@link RagSyncOptions.metadata} projection, to pass to `rag.index`. Absent on a delete or when there is none. */
|
|
1250
|
+
metadata?: Record<string, unknown>;
|
|
1251
|
+
/** The {@link RagSyncOptions.namespace} projection, to pass to `rag.index`/`rag.remove`. Absent when there is none. */
|
|
1252
|
+
namespace?: string;
|
|
1267
1253
|
/** The text to embed. Absent on a delete. */
|
|
1268
1254
|
text?: string;
|
|
1269
1255
|
}
|
|
@@ -1282,6 +1268,19 @@ interface RagSyncOptions<Document extends Record<string, unknown> = Record<strin
|
|
|
1282
1268
|
delayMs?: number;
|
|
1283
1269
|
/** The source id to index under. Defaults to the row's own id. */
|
|
1284
1270
|
id?: (document: Document) => string;
|
|
1271
|
+
/**
|
|
1272
|
+
* Metadata copied onto every chunk (`rag.index({ metadata })`) — the fields a
|
|
1273
|
+
* `rlsFilter` or `metadataFilter` scopes retrieval by, such as a tenant or
|
|
1274
|
+
* owner id. Compared on update like the text: a row that moves tenant with
|
|
1275
|
+
* unchanged text is re-indexed, so the old tenant stops retrieving it.
|
|
1276
|
+
*/
|
|
1277
|
+
metadata?: (document: Document) => Record<string, unknown> | undefined;
|
|
1278
|
+
/**
|
|
1279
|
+
* The Vectorize namespace to index under (`rag.index({ namespace })`), e.g.
|
|
1280
|
+
* the tenant key. When it changes on update, the chunks in the old namespace
|
|
1281
|
+
* are removed before the row is indexed into the new one.
|
|
1282
|
+
*/
|
|
1283
|
+
namespace?: (document: Document) => string | undefined;
|
|
1285
1284
|
/** The text to embed. Return `undefined` to skip the row (a draft, an empty body). */
|
|
1286
1285
|
text: (document: Document) => string | undefined;
|
|
1287
1286
|
}
|
|
@@ -1303,17 +1302,37 @@ type RagSyncHandler = (context: RagSyncTriggerContext, event: RagSyncEvent) => P
|
|
|
1303
1302
|
* });
|
|
1304
1303
|
* ```
|
|
1305
1304
|
*
|
|
1306
|
-
* The action on the other end
|
|
1305
|
+
* The action on the other end forwards every field it receives:
|
|
1307
1306
|
*
|
|
1308
1307
|
* ```ts
|
|
1309
|
-
* export const reindex = internalAction
|
|
1310
|
-
*
|
|
1308
|
+
* export const reindex = internalAction
|
|
1309
|
+
* .input({
|
|
1310
|
+
* deleted: v.optional(v.boolean()),
|
|
1311
|
+
* id: v.string(),
|
|
1312
|
+
* metadata: v.optional(v.record(v.string(), v.any())),
|
|
1313
|
+
* namespace: v.optional(v.string()),
|
|
1314
|
+
* text: v.optional(v.string()),
|
|
1315
|
+
* })
|
|
1316
|
+
* .action(async ({ args, ctx }) => {
|
|
1311
1317
|
* const rag = docsRag(ctx);
|
|
1318
|
+
* const { id, metadata, namespace, text } = args;
|
|
1312
1319
|
*
|
|
1313
|
-
* await (args.deleted === true ||
|
|
1314
|
-
* }
|
|
1315
|
-
* );
|
|
1320
|
+
* await (args.deleted === true || text === undefined ? rag.remove({ id, namespace }) : rag.index({ id, metadata, namespace, text }));
|
|
1321
|
+
* });
|
|
1316
1322
|
* ```
|
|
1323
|
+
*
|
|
1324
|
+
* A multi-tenant table also passes `metadata` and/or `namespace` (for example
|
|
1325
|
+
* `metadata: (doc) => ({ orgId: doc.orgId })`). Without them, a row that moves
|
|
1326
|
+
* tenant keeps its old scope in the index and stays retrievable by the tenant it
|
|
1327
|
+
* left. An action that drops `namespace` removes from (or indexes into) the
|
|
1328
|
+
* wrong namespace, and one whose `input` omits `metadata` / `namespace` rejects
|
|
1329
|
+
* the scheduled args and fails every job.
|
|
1330
|
+
*
|
|
1331
|
+
* Re-scoping is not atomic: the triggers only SCHEDULE the work, so after a row
|
|
1332
|
+
* changes namespace its chunks stay retrievable in the old one until the
|
|
1333
|
+
* scheduled delete runs (and until the re-index runs, it is missing from the new
|
|
1334
|
+
* one). The same holds for a metadata change. Lower `delayMs` shortens that
|
|
1335
|
+
* window; it does not close it.
|
|
1317
1336
|
*/
|
|
1318
1337
|
declare const ragSyncTriggers: <Document extends Record<string, unknown> = Record<string, unknown>>(options: RagSyncOptions<Document>) => {
|
|
1319
1338
|
afterDelete: RagSyncHandler;
|
package/dist/rag/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{default as o}from"../packem_shared/fixedWindowChunks-C461ahRE.mjs";import{markdownChunker as a,sentenceChunker as f,tokenChunker as n}from"../packem_shared/markdownChunker-
|
|
1
|
+
import{default as o}from"../packem_shared/fixedWindowChunks-C461ahRE.mjs";import{markdownChunker as a,sentenceChunker as f,tokenChunker as n}from"../packem_shared/markdownChunker-iW8V4klY.mjs";import{default as x}from"../packem_shared/defineRag-Cs7m4xJJ.mjs";import{contentHash as p,guessMimeTypeFromExtension as i}from"../packem_shared/contentHash-BIn6ECP8.mjs";import{hybridRank as d}from"../packem_shared/hybridRank-B4skyCLx.mjs";import{default as k}from"../packem_shared/bm25LexicalStore-DMUzAL0O.mjs";import{default as h}from"../packem_shared/matchesMetadataFilter-BbIOyA5g.mjs";import{batchReranker as g,scoreReranker as C}from"../packem_shared/batchReranker-Bc38FBLH.mjs";import{defineRagSource as E}from"../packem_shared/defineRagSource-Q3f3niU8.mjs";import{sqlLexicalStore as T}from"../packem_shared/sqlLexicalStore-4C_cIwef.mjs";import{sqliteVectorStore as y}from"../packem_shared/sqliteVectorStore-D32l9lP0.mjs";import{ragSyncTriggers as q}from"../packem_shared/ragSyncTriggers-hgMcA4f2.mjs";import{VECTORIZE_CAPABILITIES as A,vectorizeStore as F}from"../packem_shared/VECTORIZE_CAPABILITIES-CUQDoxis.mjs";export{A as VECTORIZE_CAPABILITIES,g as batchReranker,k as bm25LexicalStore,p as contentHash,x as defineRag,E as defineRagSource,o as fixedWindowChunks,i as guessMimeTypeFromExtension,d as hybridRank,a as markdownChunker,h as matchesMetadataFilter,q as ragSyncTriggers,C as scoreReranker,f as sentenceChunker,T as sqlLexicalStore,y as sqliteVectorStore,n as tokenChunker,F as vectorizeStore};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/ai",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.97",
|
|
4
4
|
"description": "Workers AI helper for Lunora: provider-agnostic AI SDK access from functions, Workers AI by default",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"access": "public"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@lunora/errors": "1.0.0-alpha.
|
|
56
|
+
"@lunora/errors": "1.0.0-alpha.42",
|
|
57
57
|
"ai": "7.0.93",
|
|
58
58
|
"workers-ai-provider": "4.0.0"
|
|
59
59
|
},
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const a=(t,e)=>{if(t===void 0)return;const o=t[e];return typeof o=="string"&&o.length>0?o:void 0};let A=!1,d=!1;const f=5,l=t=>{if(t===void 0)return;const e={};for(const[n,r]of Object.entries(t.tags??{}))typeof r=="string"&&r.length>0&&n.length>0&&(e[n]=r);typeof t.functionPath=="string"&&t.functionPath.length>0&&(e.functionPath=t.functionPath),typeof t.traceId=="string"&&t.traceId.length>0&&(e.traceId=t.traceId);const o=Object.keys(e);if(o.length===0)return;if(o.length<=f)return e;const i=o.slice(-f);return Object.fromEntries(i.map(n=>[n,e[n]]))},E=t=>{const e=l(t);return e===void 0?void 0:JSON.stringify(e)},I="LUNORA_AI_DEFAULT_MODEL",y="LUNORA_AI_DEFAULT_EMBEDDING_MODEL",g="LUNORA_AI_GATEWAY_ACCOUNT_ID",h="LUNORA_AI_GATEWAY_ID",_="LUNORA_AI_GATEWAY_TOKEN",u="LUNORA_AI_GATEWAY_TAGS",T=t=>{const e=a(t,u);if(e!==void 0)try{const o=JSON.parse(e);if(typeof o!="object"||o===null||Array.isArray(o))throw new TypeError("expected a JSON object");const i={};for(const[n,r]of Object.entries(o))typeof r=="string"&&r.length>0&&(i[n]=r);return Object.keys(i).length>0?i:void 0}catch{d||(d=!0,console.warn(`[lunora:ai] ${u} is not a flat JSON object of strings — AI Gateway tags from it are ignored.`));return}},v=(t,e,o="byo-provider")=>{const i=a(t,g),n=a(t,h);if(i===void 0||n===void 0)return;const r=a(t,_),s={};r!==void 0&&(s["cf-aig-authorization"]=`Bearer ${r}`,o==="workers-ai-binding"&&!A&&(A=!0,console.warn(`[lunora:ai] ${_} is set, but the Workers AI binding cannot send a gateway auth token — Cloudflare's native gateway option has no authorization field. The token is ignored on this path; use a bring-your-own AI SDK provider (which sends cf-aig-authorization), or make the AI Gateway unauthenticated for Workers AI.`)));const c=E(e);return c!==void 0&&(s["cf-aig-metadata"]=c),{accountId:i,baseURL:`https://gateway.ai.cloudflare.com/v1/${i}/${n}`,gatewayId:n,headers:s}};export{y as AI_DEFAULT_EMBEDDING_MODEL_ENV,I as AI_DEFAULT_MODEL_ENV,g as AI_GATEWAY_ACCOUNT_ID_ENV,h as AI_GATEWAY_ID_ENV,f as AI_GATEWAY_METADATA_MAX_KEYS,u as AI_GATEWAY_TAGS_ENV,_ as AI_GATEWAY_TOKEN_ENV,l as buildAiGatewayMetadataFields,T as readAiGatewayEnvTags,a as readEnv,v as resolveAiGateway};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const p=/^(.*)-\d{4}-\d{2}-\d{2}$/u,a={"@cf/baai/bge-base-en-v1.5":{input:.067},"@cf/baai/bge-large-en-v1.5":{input:.204},"@cf/baai/bge-m3":{input:.012},"@cf/baai/bge-small-en-v1.5":{input:.02},"@cf/meta/llama-3.1-8b-instruct":{input:.28,output:.83},"@cf/meta/llama-3.3-70b-instruct-fp8-fast":{input:.29,output:2.25},"text-embedding-3-large":{input:.13},"text-embedding-3-small":{input:.02},"gpt-4o":{input:2.5,output:10},"gpt-4o-mini":{input:.15,output:.6},"gpt-5":{input:1.25,output:10}},r=n=>{const t=n.trim(),i=t.lastIndexOf("/");return(i!==-1&&!t.startsWith("@")?[t,t.slice(i+1)]:[t]).flatMap(e=>{const o=p.exec(e)?.[1];return o===void 0?[e]:[e,o]})},c=(n,t=a)=>{for(const i of r(n))if(Object.hasOwn(t,i))return t[i]},b=(n,t,i)=>{if(n===void 0||n.length===0)return;const u=c(n,i);if(u===void 0)return;const e=Number.isFinite(t.inputTokens)?Math.max(0,t.inputTokens):0,o=Number.isFinite(t.outputTokens)?Math.max(0,t.outputTokens):0;if(e<=0&&o<=0)return;const s=(e*u.input+o*(u.output??0))/1e6;return Number.isFinite(s)?s:void 0};export{a as DEFAULT_MODEL_PRICES,b as estimateModelCost,c as lookupModelPrice};
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import{LunoraError as w,isLunoraError as Ne}from"@lunora/errors";import{tool as Ae,jsonSchema as Me,embedMany as Oe,embed as De}from"ai";import{estimateModelCost as Re}from"./DEFAULT_MODEL_PRICES-Q8uxdiuV.mjs";import Ce from"./fixedWindowChunks-C461ahRE.mjs";import{c as $e,I as Be}from"./concurrent-C6nqBv41.mjs";import{contentHash as Ke}from"./contentHash-BIn6ECP8.mjs";import{hybridRank as Ue}from"./hybridRank-B4skyCLx.mjs";import{VECTORIZE_CAPABILITIES as ce,vectorizeStore as Fe}from"./VECTORIZE_CAPABILITIES-CUQDoxis.mjs";const Qe=/["\\\u0000-\u001F\uD800-\uDFFF]/,de=e=>Qe.test(e)?JSON.stringify(e):`"${e}"`,Y=e=>{if(e===void 0)return"null";if(typeof e=="bigint")throw new TypeError("stableStringify: cannot use a bigint in a stable JSON cache key — pass it as a string, or use stableWireKey");if(typeof e=="number"){if(Number.isNaN(e))return"nan";if(e===1/0)return"inf";if(e===-1/0)return"-inf";if(Object.is(e,-0))return"-0"}if(typeof e=="string")return de(e);if(e===null||typeof e!="object")return JSON.stringify(e);if(Array.isArray(e)){let v="[";for(let T=0;T<e.length;T++)T>0&&(v+=","),v+=Y(e[T]);return v+"]"}const c=Object.getPrototypeOf(e);if(c!==null&&c!==Object.prototype){const v=e.constructor?.name??"value";throw new TypeError(`stableStringify: cannot use a ${v} in a stable JSON cache key — only plain objects, arrays, and JSON primitives are supported (wire-typed values key via stableWireKey)`)}const m=e,p=Object.keys(m).sort();let f="{",k=!0;for(const v of p){const T=m[v];T!==void 0&&(k?k=!1:f+=",",f+=de(v),f+=":",f+=Y(T))}return f+"}"},Ve=1e3,je=200,Le=5,ze=4,ue=ce.maxMetadataBytes===!1?Number.POSITIVE_INFINITY:ce.maxMetadataBytes,Pe=2*1024,fe="__ragChunk",pe="__ragSource",$="__ragText",q="__ragHash",H="__ragChunks",U="__ragImportance",ge="__ragModel",Ye=new Set([fe,H,q,U,ge,pe,$]),qe=(e,c,m,p)=>{if(p===!1)return;const f=new TextEncoder().encode(JSON.stringify(e)).length;if(f<=p)return;const v=(typeof e[$]=="string"?new TextEncoder().encode(e[$]).length:0)*2>f?"lower `chunkSize` (it counts characters, not bytes — multibyte text costs up to 3 bytes each), or supply `textStore` to move chunk text out of metadata entirely":"attach less per-source `metadata`";throw new w("BAD_REQUEST",`@lunora/ai/rag: chunk ${String(c)} of "${m}" carries ${String(f)} bytes of metadata, over the store's ${String(p)}-byte per-vector ceiling — ${v}`)},He=(e,c,m)=>{if(m===!1)return;const p=new TextEncoder().encode(e).length;if(!(p<=m))throw new w("BAD_REQUEST",`@lunora/ai/rag: chunk id "${e}" for source "${c}" is ${String(p)} bytes, over the store's ${String(m)}-byte per-vector id ceiling — shorten the source id (hash long keys before indexing them) or shorten the \`namespace\`, which is prefixed onto every chunk id`)},We=/^[\w.-]{1,40}$/,be=e=>e===void 0?"":`${encodeURIComponent(e)}#`,C=(e,c,m)=>`${be(e)}${c}#${String(m)}`,le=(e,c)=>{const m=be(c),p=m!==""&&e.startsWith(m)?e.slice(m.length):e,f=p.lastIndexOf("#"),k=f===-1?Number.NaN:Number(p.slice(f+1));return f===-1||!Number.isInteger(k)||k<0?{chunkIndex:0,sourceId:p}:{chunkIndex:k,sourceId:p.slice(0,f)}},Je=async e=>Ke(new TextEncoder().encode(e)),Ze=e=>{try{return Y([e.text,e.metadata,e.importance])}catch{return}},Xe=(e,c)=>{const m=[],p=[];for(const f of e)f.score>=c?m.push(f):p.push(f.id);return{kept:m,rejectedIds:p}},Ge=e=>e!==void 0&&Object.keys(e).length>0,z=e=>{if(!e)return;const c=Object.entries(e).filter(([m])=>!Ye.has(m));return c.length>0?Object.fromEntries(c):void 0},me=new Set,et=e=>{me.has(e)||(me.add(e),console.warn(`[@lunora/ai/rag] index "${e}" is used without a namespace — in a multi-tenant/sharded
|
|
2
|
-
app this shares one tenant's chunks (text included) with every other tenant, since
|
|
3
|
-
Vectorize indexes are account-global. Pass \`namespace\` (the shard/tenant key) on both
|
|
4
|
-
index() and retrieve(). Single-tenant apps suppress this via { allowSharedNamespace: true }.`))},tt=e=>e.map(c=>`[source:${c.sourceId}#${String(c.chunkIndex)}]
|
|
5
|
-
${c.text}`).join(`
|
|
6
|
-
|
|
7
|
-
`),he=(e,c)=>{if(typeof e=="object")return e;if(c===void 0)throw new w("INTERNAL","@lunora/ai/rag: `embeddingModel` is a Workers AI model id (or omitted) but the bound context has no `ai` (env.AI). Pass an AI SDK EmbeddingModel object to embed without Workers AI, or bind a context whose `ctx.ai` is wired.");return c.embeddingModel(e)},P=e=>{const c=e.modelId;return typeof c=="string"&&c.length>0?c:void 0},nt=e=>{if(!(typeof e!="object"||e===null)){for(const c of Object.values(e))if(typeof c=="object"&&c!==null){const{cost:m}=c;if(typeof m=="number"&&Number.isFinite(m))return m}}},rt=e=>{if(e===void 0)throw new w("INTERNAL","@lunora/ai/rag: the bound context has no `vectors` (env.VECTORIZE) and no `store` is configured — bind a context whose `ctx.vectors` is wired, or configure `store` (e.g. `sqliteVectorStore`) to back this index without Vectorize.");return e},ht=e=>{if(typeof e.index!="string"||e.index.length===0)throw new w("BAD_REQUEST","@lunora/ai/rag: `index` must be a non-empty Vectorize index name");const c=e.chunkSize??Ve,m=e.chunkOverlap??je;if(!Number.isInteger(c)||c<1)throw new w("BAD_REQUEST","@lunora/ai/rag: `chunkSize` must be a positive integer");if(!Number.isInteger(m)||m<0||m>=c)throw new w("BAD_REQUEST","@lunora/ai/rag: `chunkOverlap` must be a non-negative integer smaller than `chunkSize`");const p=ue-Pe;if(!e.chunk&&!e.textStore&&!e.store&&c>p)throw new w("BAD_REQUEST",`@lunora/ai/rag: \`chunkSize\` of ${String(c)} leaves no room under Vectorize's ${String(ue)}-byte metadata limit, which also carries this chunk's bookkeeping and any \`metadata\` you attach — keep it under ${String(p)}, or supply \`textStore\` to move chunk text out of metadata entirely`);const f=e.topK??Le;if(!Number.isInteger(f)||f<1)throw new w("BAD_REQUEST","@lunora/ai/rag: `topK` must be a positive integer");if(e.maxEmbeddingDimensions!==void 0&&e.maxEmbeddingDimensions!==!1&&(!Number.isInteger(e.maxEmbeddingDimensions)||e.maxEmbeddingDimensions<1))throw new w("BAD_REQUEST","@lunora/ai/rag: `maxEmbeddingDimensions` must be a positive integer, or `false` to disable the check");if(e.embeddingModelVersion!==void 0&&!We.test(e.embeddingModelVersion))throw new w("BAD_REQUEST",'@lunora/ai/rag: `embeddingModelVersion` must match /^[A-Za-z0-9._-]{1,40}$/ (a short, stable tag like "bge-v1.5")');if(e.candidates!==void 0&&(!Number.isInteger(e.candidates)||e.candidates<1))throw new w("BAD_REQUEST","@lunora/ai/rag: `candidates` must be a positive integer");if(e.cacheEmbeddings!==void 0&&(!Number.isInteger(e.cacheEmbeddings)||e.cacheEmbeddings<0))throw new w("BAD_REQUEST","@lunora/ai/rag: `cacheEmbeddings` must be a non-negative integer");const k=e.cacheEmbeddings??0,v=e.rerank,T=e.chunk??(g=>Ce(g,c,m)),{textStore:N}=e,B=e.embeddingModelVersion,Q=g=>B===void 0?g:g===void 0?B:`${B}::${g}`;return g=>{const x=e.store?e.store(g):Fe(rt(g.vectors),e.index),W=N?x.capabilities.maxTopK:x.capabilities.maxTopKWithMetadata,F=e.maxEmbeddingDimensions??x.capabilities.maxDimensions;let K;const J=typeof g.trace=="function"?g.trace:void 0;let Z=F===!1;const X=(t,r)=>{if(Z||(Z=!0,F===!1||t<=F))return;const s=P(r);throw new w("BAD_REQUEST",`@lunora/ai/rag: embedding model${s===void 0?"":` "${s}"`} produces ${String(t)}-dimension vectors, over the ${String(F)}-dimension ceiling of index "${e.index}" — either truncate them with the provider's \`dimensions\` option (Matryoshka models such as text-embedding-3-large support this), or set \`maxEmbeddingDimensions: false\` if this index is not Vectorize-backed`)},D=new Map,ye=(t,r)=>{if(k!==0)for(D.set(t,r);D.size>k;){const s=D.keys().next();if(s.done===!0)break;D.delete(s.value)}},G=async t=>{const r=D.get(t);if(r!==void 0)return r;K??=he(e.embeddingModel,g.ai);const s=K,a=async n=>{const{embedding:h,providerMetadata:E,usage:l}=await De({model:s,value:t});if(X(h.length,s),n!==void 0){const d=l.tokens;typeof d=="number"&&Number.isFinite(d)&&n.setAttribute("gen_ai.usage.input_tokens",d);const u=nt(E),b=u??Re(P(s),{inputTokens:typeof d=="number"?d:void 0});b!==void 0&&(n.setAttribute("gen_ai.usage.cost",b),n.setAttribute("lunora.usage.cost.source",u===void 0?"estimated":"provider"))}return ye(t,h),h};if(J===void 0)return a();const o=P(s),i=typeof g.conversationId=="string"&&g.conversationId.length>0?g.conversationId:void 0;return J("ai.embed",(n,h)=>a(h),{"gen_ai.operation.name":"embeddings",...o===void 0?{}:{"gen_ai.request.model":o},...i===void 0?{}:{"gen_ai.conversation.id":i}})},we=async(t,r,s)=>{if(!e.transformQuery||r?.transformQuery===!1)return[t];const a=typeof g.conversationId=="string"&&g.conversationId.length>0?g.conversationId:void 0,o=await e.transformQuery(t,{conversationId:a,namespace:s}),i=(typeof o=="string"?[o]:[...o]).map(n=>n.trim()).filter(n=>n.length>0);return i.length>0?i:[t]},Ee=async t=>{const r=new Map,s=[...new Set(t.filter(a=>!D.has(a)))];if(s.length<2)return r;K??=he(e.embeddingModel,g.ai);try{const{embeddings:a}=await Oe({model:K,values:s});if(a.length!==s.length)return r;const[o]=a;o!==void 0&&X(o.length,K);for(const[i,n]of s.entries())r.set(n,a[i])}catch(a){if(Ne(a))throw a}return r},V=t=>{if(t===void 0){if(e.requireNamespace)throw new w("BAD_REQUEST",`@lunora/ai/rag: index "${e.index}" requires a namespace (requireNamespace is set) — pass the tenant/shard key on index()/retrieve()/remove()`);e.allowSharedNamespace||et(e.index)}},ee=async(t,r)=>{const[s]=await x.getByIds([C(r,t,0)],r),a=s?.metadata?.[q],o=s?.metadata?.[H];return{chunks:typeof o=="number"&&Number.isInteger(o)&&o>0?o:void 0,hash:typeof a=="string"?a:void 0}},te=async(t,r,s,a)=>{const o=Array.from({length:s-r},(i,n)=>C(a,t,r+n));o.length!==0&&(await x.deleteByIds(o,a),await N?.remove?.(o,{namespace:a}),await e.lexicalStore?.remove?.(o,{namespace:a}))},ve=async t=>{if(V(t.namespace),t.importance!==void 0&&(typeof t.importance!="number"||t.importance<0||t.importance>1))throw new w("BAD_REQUEST","@lunora/ai/rag: `importance` must be a number in [0, 1]");const r=Q(t.namespace),s=Ze(t),a=await Je(s??t.text),o=await ee(t.id,r);if(t.reindex!==!0&&s!==void 0&&o.hash===a&&o.chunks!==void 0)return{chunks:o.chunks,ids:Array.from({length:o.chunks},(d,u)=>C(r,t.id,u)),unchanged:!0};const i=T(t.text),n=i.map((d,u)=>C(r,t.id,u)),h=n.at(-1);if(h!==void 0&&He(h,t.id,x.capabilities.maxIdBytes),i.length===0&&t.allowEmptySources===!1)throw new w("BAD_REQUEST",`@lunora/ai/rag: source "${t.id}" produced zero chunks — set allowEmptySources: true to allow this`);if(i.length>0){const d=i.map((u,b)=>({chunkIndex:b,id:n[b],sourceId:t.id,text:u,...t.metadata===void 0?{}:{metadata:t.metadata}}));N&&await N.put(d,{namespace:r}),e.lexicalStore&&await e.lexicalStore.index(d,{namespace:r})}const E=await Ee(i),l=async d=>E.get(d)??await G(d);return await $e(i,Be,async(d,u)=>{const b=n[u],S={...t.metadata,[fe]:u,[pe]:t.id};N||(S[$]=d),t.importance!==void 0&&(S[U]=t.importance),u===0&&(S[q]=a,S[H]=i.length,B!==void 0&&(S[ge]=B)),qe(S,u,t.id,x.capabilities.maxMetadataBytes),await x.upsert({embed:l,id:b,input:d,metadata:S,namespace:r}),t.onChunk?.({chunkIndex:u,id:b,text:d,total:i.length})}),o.chunks!==void 0&&o.chunks>i.length&&await te(t.id,i.length,o.chunks,r),{chunks:i.length,ids:n,unchanged:!1}},xe=async t=>{V(t.namespace);const r=Q(t.namespace),a=(await ee(t.id,r)).chunks??1;await te(t.id,0,a,r)},ne=async(t,r)=>{const s=new Map;if(t.length===0)return s;if(N){const o=await N.getMany(t,{namespace:r});for(const[i,n]of t.entries()){const h=o[i];typeof h=="string"&&s.set(n,h)}return s}const a=await x.getByIds(t,r);for(const o of a){const i=o.metadata?.[$];typeof i=="string"&&s.set(o.id,i)}return s},Se=async(t,r,s)=>{const a=r?.chunkContext?.before??0,o=r?.chunkContext?.after??0;if(a===0&&o===0)return t;if(!Number.isInteger(a)||a<0||!Number.isInteger(o)||o<0)throw new w("BAD_REQUEST","@lunora/ai/rag: `chunkContext.before`/`chunkContext.after` must be non-negative integers");const i=new Map(t.map(l=>[l.id,l.text])),n=new Set;for(const l of t)for(let d=-a;d<=o;d+=1){const u=l.chunkIndex+d,b=C(s,l.sourceId,u);d!==0&&u>=0&&!i.has(b)&&n.add(b)}const h=await ne([...n],s),E=(l,d)=>{const u=C(s,l,d);return i.get(u)??h.get(u)};return t.map(l=>{const d=[];for(let u=-a;u<=o;u+=1){const b=u===0?l.text:E(l.sourceId,l.chunkIndex+u);b!==void 0&&d.push(b)}return{...l,text:d.join(`
|
|
8
|
-
`)}})},Ie=t=>{if(typeof t=="string"){const r=e.filters!==void 0&&Object.hasOwn(e.filters,t)?e.filters[t]:void 0;if(!r)throw new w("NOT_FOUND",`@lunora/ai/rag: unknown named filter "${t}" — must be one of the keys declared in RagConfig.filters`);return r.filter}return t},ke=(t,r)=>t.matches.map(s=>{const a=s.metadata??{},o=le(s.id,r),i=a[$],n=a[U],h=typeof n=="number"&&n>=0&&n<=1?n:1;return{chunkIndex:o.chunkIndex,id:s.id,importance:h,metadata:z(a),score:s.score*h,sourceId:o.sourceId,text:typeof i=="string"?i:""}}),_e=async(t,r)=>{if(!N)return t;const s=t.map(n=>n.id),[a,o]=await Promise.all([ne(s,r),x.getByIds(s,r)]),i=new Map(o.map(n=>[n.id,n.metadata]));return t.flatMap(n=>{const h=a.get(n.id);if(h===void 0)return[];const E=i.get(n.id),l=E?.[U],d=typeof l=="number"&&l>=0&&l<=1?l:n.importance,b=(n.importance===0?0:n.score/n.importance)*d;return[{...n,importance:d,metadata:z(E)??n.metadata,score:b,text:h}]})},re=async(t,r,s)=>{const a=t.map(n=>n.id).filter(n=>!r.has(n)),o=a.length===0?[]:await x.getByIds(a,s),i=new Map(o.map(n=>[n.id,n.metadata]));return t.map(n=>{const h=le(n.id,s),E=i.get(n.id),l=E?.[U];return{chunkIndex:h.chunkIndex,id:n.id,importance:typeof l=="number"&&l>=0&&l<=1?l:1,metadata:z(E),score:n.score,sourceId:h.sourceId,text:n.text}})},se=async(t,r)=>{V(r?.namespace);const s=Q(r?.namespace),a=Ie(r?.filter),o=e.rlsFilter?await e.rlsFilter(g.auth):void 0,i=o?{...a,...o}:a,n=Math.min(r?.topK??f,W),h=await we(t,r,s),E=h[0],l=v!==void 0&&r?.rerank!==!1,u=l||e.lexicalStore!==void 0||e.graphStore!==void 0||h.length>1?Math.min(e.candidates??n*ze,W):n,b=r?.minScore,S=new Set,oe=async y=>{const A=await x.query({embed:G,filter:i,input:y,namespace:s,returnMetadata:N?"indexed":"all",topK:u}),O=await _e(ke(A,s),s);if(b===void 0)return O;const{kept:_,rejectedIds:M}=Xe(O,b);for(const Te of M)S.add(Te);return _},R=[{chunks:await oe(E)}];for(const y of h.slice(1))R.push({chunks:await oe(y)});if(e.lexicalStore){const y=await e.lexicalStore.search(E,{filter:i,namespace:s,topK:e.lexicalTopK??u}),A=new Set(R.flatMap(_=>_.chunks).map(_=>_.id)),O=y.filter(_=>!S.has(_.id)||A.has(_.id));R.push({chunks:await re(O,A,s)})}const j=R.flatMap(y=>[...y.chunks]);if(e.graphStore&&j.length>0&&(e.graphStore.enforcesFilter||!Ge(i))){const y=[...new Set(j.map(M=>M.sourceId))],A=await e.graphStore.related(y,{filter:i,namespace:s,topK:e.graphTopK??u}),O=new Set(j.map(M=>M.id)),_=A.filter(M=>!S.has(M.id)||O.has(M.id));R.push({chunks:await re(_,O,s),weight:"proximity"})}const L=R.filter(y=>y.chunks.length>0);let I=L.length>1?[...Ue(L)]:[...L[0]?.chunks??[]];I.sort((y,A)=>A.score-y.score),l&&(I=[...await v(E,I)]),I=I.slice(0,n),I=[...await Se(I,r,s)];const ae=[],ie=new Set;for(const y of I)ie.has(y.sourceId)||(ie.add(y.sourceId),ae.push({id:y.sourceId,metadata:y.metadata,weight:y.importance}));return r?.onRetrieve?.({matches:I.length,query:t}),{chunks:I,context:tt(I),sources:ae}};return{asTool:t=>Ae({description:t?.description??`Search the "${e.index}" knowledge base for passages relevant to a natural-language query.`,execute:async({query:r})=>se(r,{namespace:t?.namespace,topK:t?.topK}),inputSchema:Me({properties:{query:{description:"The natural-language search query.",type:"string"}},required:["query"],type:"object"})}),index:ve,remove:xe,retrieve:se}}};export{ht as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const l=o=>{const u=o.delayMs??0,s=(i,d)=>o.id===void 0?d:o.id(i),t=i=>i===void 0?void 0:o.text(i),c=async(i,d)=>{await i.scheduler.runAfter(u,o.action,d)};return{afterDelete:async(i,d)=>{const r=d.previous??d.doc;await c(i,{deleted:!0,id:r===void 0?d.id:s(r,d.id)})},afterInsert:async(i,d)=>{const r=t(d.doc);r===void 0||d.doc===void 0||await c(i,{id:s(d.doc,d.id),text:r})},afterUpdate:async(i,d)=>{if(d.doc===void 0)return;const r=t(d.doc),f=t(d.previous),a=s(d.doc,d.id),e=d.previous===void 0?a:s(d.previous,d.id);if(e!==a)await c(i,{deleted:!0,id:e});else if(r===f)return;await(r===void 0?c(i,{deleted:!0,id:a}):c(i,{id:a,text:r}))}}};export{l as ragSyncTriggers};
|