/** * Document RAG service. * * Consumes DocumentRagRequest messages, runs the document retrieval pipeline, * and emits DocumentRagResponse. * * Python reference: trustgraph-flow/trustgraph/retrieval/document_rag/ */ import { makeConsumerSpec, makeFlowProcessor, makeProducerSpec, makeRequestResponseSpec, makeFlowProcessorProgram, type DocumentEmbeddingsRequest, type DocumentEmbeddingsResponse, type DocumentRagRequest, type DocumentRagResponse, type EffectRequestOptions, type EffectRequestResponse, type EmbeddingsRequest, type EmbeddingsResponse, type FlowContext, type FlowProcessorRuntime, type FlowRequestOptions, type FlowRequestor, type FlowResourceNotFoundError, type MessagingDeliveryError, type ProcessorConfig, type PromptRequest, type PromptResponse, type Spec, type TextCompletionRequest, type TextCompletionResponse, } from "@trustgraph/base"; import { Effect } from "effect"; import { DocumentRagEngine, DocumentRagEngineError, DocumentRagLive, makeDocumentRagEngine, type DocumentRagClients, } from "./document-rag.js"; const toEffectRequestOptions = ( options: FlowRequestOptions | undefined, ): EffectRequestOptions | undefined => { if (options === undefined) return undefined; return { ...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }), ...(options.recipient === undefined ? {} : { recipient: (response: TRes) => Effect.promise(() => options.recipient?.(response) ?? Promise.resolve(true)), }), }; }; const toPromiseRequestor = ( requestor: EffectRequestResponse, ): FlowRequestor => ({ request: (request, options) => Effect.runPromise(requestor.request(request, toEffectRequestOptions(options))), stop: () => Effect.runPromise(requestor.stop), }); const onDocumentRagRequest = Effect.fn("DocumentRagService.onRequest")(function* ( msg: DocumentRagRequest, properties: Record, flowCtx: FlowContext, ) { const requestId = properties.id; if (requestId === undefined || requestId.length === 0) return; const producer = yield* flowCtx.flow.producerEffect("document-rag-response"); const engine = yield* DocumentRagEngine; const clients: DocumentRagClients = { llm: toPromiseRequestor(yield* flowCtx.flow.requestorEffect("llm")), embeddings: toPromiseRequestor(yield* flowCtx.flow.requestorEffect("embeddings")), docEmbeddings: toPromiseRequestor( yield* flowCtx.flow.requestorEffect("doc-embeddings"), ), prompt: toPromiseRequestor(yield* flowCtx.flow.requestorEffect("prompt")), }; const response = yield* engine.query( clients, msg.query, { ...(msg.collection !== undefined ? { collection: msg.collection } : {}), }, ).pipe( Effect.catch((error: DocumentRagEngineError) => Effect.logError("[DocumentRag] Query failed", { error: error.message, operation: error.operation, }).pipe( Effect.flatMap(() => producer.send(requestId, { response: "", error: { type: "rag-error", message: error.message }, }), ), Effect.as(undefined), ), ), ); if (response === undefined) return; yield* producer.send(requestId, { response, endOfStream: true }); }); export const makeDocumentRagSpecs = (): ReadonlyArray> => [ makeConsumerSpec( "document-rag-request", onDocumentRagRequest, ), makeProducerSpec("document-rag-response"), makeRequestResponseSpec( "llm", "text-completion-request", "text-completion-response", ), makeRequestResponseSpec( "embeddings", "embeddings-request", "embeddings-response", ), makeRequestResponseSpec( "doc-embeddings", "document-embeddings-request", "document-embeddings-response", ), makeRequestResponseSpec( "prompt", "prompt-request", "prompt-response", ), ]; export type DocumentRagService = FlowProcessorRuntime; export function makeDocumentRagService(config: ProcessorConfig): DocumentRagService { return makeFlowProcessor(config, { specifications: makeDocumentRagSpecs(), provide: (effect) => effect.pipe( Effect.provideService(DocumentRagEngine, DocumentRagEngine.of(makeDocumentRagEngine())), ), }); } export const DocumentRagService = makeDocumentRagService; export const program = makeFlowProcessorProgram({ id: "document-rag", specs: makeDocumentRagSpecs, layer: () => DocumentRagLive, }); export function run(): Promise { return Effect.runPromise(program); }