trustgraph/ts/packages/flow/src/retrieval/document-rag.ts

137 lines
3.7 KiB
TypeScript
Raw Normal View History

2026-04-05 21:09:33 -05:00
/**
* Document RAG retrieval pipeline.
*
* Python reference: trustgraph-flow/trustgraph/retrieval/document_rag/
*/
import type {
DocumentEmbeddingsRequest,
DocumentEmbeddingsResponse,
2026-06-01 16:22:25 -05:00
EmbeddingsRequest,
EmbeddingsResponse,
FlowRequestor,
2026-04-05 21:09:33 -05:00
PromptRequest,
PromptResponse,
2026-06-01 16:22:25 -05:00
TextCompletionRequest,
TextCompletionResponse,
2026-04-05 21:09:33 -05:00
} from "@trustgraph/base";
2026-06-01 16:22:25 -05:00
import { errorMessage } from "@trustgraph/base";
import { Context, Effect, Layer } from "effect";
import * as S from "effect/Schema";
2026-04-05 21:09:33 -05:00
export interface DocumentRagClients {
2026-05-12 08:06:58 -05:00
llm: FlowRequestor<TextCompletionRequest, TextCompletionResponse>;
embeddings: FlowRequestor<EmbeddingsRequest, EmbeddingsResponse>;
docEmbeddings: FlowRequestor<DocumentEmbeddingsRequest, DocumentEmbeddingsResponse>;
prompt: FlowRequestor<PromptRequest, PromptResponse>;
2026-04-05 21:09:33 -05:00
}
export type ChunkCallback = (text: string, endOfStream: boolean) => Promise<void>;
2026-06-01 16:22:25 -05:00
export interface DocumentRagQueryOptions {
readonly collection?: string;
readonly streaming?: boolean;
readonly chunkCallback?: ChunkCallback;
}
export class DocumentRagEngineError extends S.TaggedErrorClass<DocumentRagEngineError>()(
"DocumentRagEngineError",
{
message: S.String,
operation: S.String,
cause: S.DefectWithStack,
},
) {}
export interface DocumentRagEngineShape {
readonly query: (
clients: DocumentRagClients,
queryText: string,
options?: DocumentRagQueryOptions,
) => Effect.Effect<string, DocumentRagEngineError>;
}
export class DocumentRagEngine extends Context.Service<DocumentRagEngine, DocumentRagEngineShape>()(
"@trustgraph/flow/retrieval/document-rag/DocumentRagEngine",
) {}
const documentRagError = (operation: string, cause: unknown) =>
new DocumentRagEngineError({
operation,
cause,
message: errorMessage(cause),
});
export function makeDocumentRagEngine(): DocumentRagEngineShape {
return {
query: Effect.fn("DocumentRagEngine.query")((
clients: DocumentRagClients,
queryText: string,
options?: DocumentRagQueryOptions,
) =>
Effect.tryPromise({
try: () => queryDocumentRag(clients, queryText, options),
catch: (cause) => documentRagError("query", cause),
}),
),
};
}
export const DocumentRagLive: Layer.Layer<DocumentRagEngine> = Layer.succeed(
DocumentRagEngine,
DocumentRagEngine.of(makeDocumentRagEngine()),
);
2026-06-01 20:26:47 -05:00
export interface DocumentRag {
readonly query: (
2026-04-05 21:09:33 -05:00
queryText: string,
2026-06-01 16:22:25 -05:00
options?: DocumentRagQueryOptions,
2026-06-01 20:26:47 -05:00
) => Promise<string>;
}
export function makeDocumentRag(clients: DocumentRagClients): DocumentRag {
const engine = makeDocumentRagEngine();
return {
query: (queryText, options) =>
Effect.runPromise(engine.query(clients, queryText, options)),
};
2026-04-05 21:09:33 -05:00
}
2026-06-01 16:22:25 -05:00
async function queryDocumentRag(
clients: DocumentRagClients,
queryText: string,
options?: DocumentRagQueryOptions,
): Promise<string> {
const collection = options?.collection ?? "default";
const embResp = await clients.embeddings.request({ text: [queryText] });
const vectors = embResp.vectors;
const docResp = await clients.docEmbeddings.request({
vectors,
limit: 10,
collection,
user: "default",
});
const chunks = docResp.chunks ?? [];
console.log(`[DocumentRag] Found ${chunks.length} matching chunks`);
const context = chunks
.flatMap((chunk) =>
chunk.content !== undefined && chunk.content.length > 0 ? [chunk.content] : [],
)
.join("\n\n---\n\n");
const promptResp = await clients.prompt.request({
name: "document-rag-synthesize",
variables: { query: queryText, context },
});
const resp = await clients.llm.request({
system: promptResp.system,
prompt: promptResp.prompt,
});
return resp.response;
}