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

167 lines
5.1 KiB
TypeScript
Raw Normal View History

/**
2026-06-01 16:22:25 -05:00
* Document RAG service.
*
2026-06-01 16:22:25 -05:00
* Consumes DocumentRagRequest messages, runs the document retrieval pipeline,
* and emits DocumentRagResponse.
*
* Python reference: trustgraph-flow/trustgraph/retrieval/document_rag/
*/
import {
2026-06-01 20:26:47 -05:00
makeConsumerSpec,
makeFlowProcessor,
makeProducerSpec,
makeRequestResponseSpec,
2026-06-01 16:22:25 -05:00
makeFlowProcessorProgram,
type DocumentEmbeddingsRequest,
type DocumentEmbeddingsResponse,
type DocumentRagRequest,
type DocumentRagResponse,
2026-06-01 16:22:25 -05:00
type EffectRequestOptions,
type EffectRequestResponse,
type EmbeddingsRequest,
type EmbeddingsResponse,
2026-06-01 16:22:25 -05:00
type FlowContext,
2026-06-01 20:26:47 -05:00
type FlowProcessorRuntime,
2026-06-01 16:22:25 -05:00
type FlowRequestOptions,
type FlowRequestor,
type FlowResourceNotFoundError,
type MessagingDeliveryError,
type ProcessorConfig,
type PromptRequest,
type PromptResponse,
2026-06-01 16:22:25 -05:00
type Spec,
type TextCompletionRequest,
type TextCompletionResponse,
} from "@trustgraph/base";
2026-06-01 16:22:25 -05:00
import { Effect } from "effect";
import {
DocumentRagEngine,
DocumentRagEngineError,
DocumentRagLive,
makeDocumentRagEngine,
type DocumentRagClients,
} from "./document-rag.js";
2026-06-01 16:22:25 -05:00
const toEffectRequestOptions = <TRes>(
options: FlowRequestOptions<TRes> | undefined,
): EffectRequestOptions<TRes> | 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)),
}),
};
};
2026-06-01 16:22:25 -05:00
const toPromiseRequestor = <TReq, TRes>(
requestor: EffectRequestResponse<TReq, TRes>,
): FlowRequestor<TReq, TRes> => ({
request: (request, options) =>
Effect.runPromise(requestor.request(request, toEffectRequestOptions(options))),
stop: () => Effect.runPromise(requestor.stop),
});
2026-06-01 16:22:25 -05:00
const onDocumentRagRequest = Effect.fn("DocumentRagService.onRequest")(function* (
msg: DocumentRagRequest,
properties: Record<string, string>,
flowCtx: FlowContext<DocumentRagEngine>,
) {
const requestId = properties.id;
if (requestId === undefined || requestId.length === 0) return;
2026-06-01 16:22:25 -05:00
const producer = yield* flowCtx.flow.producerEffect<DocumentRagResponse>("document-rag-response");
const engine = yield* DocumentRagEngine;
2026-06-01 16:22:25 -05:00
const clients: DocumentRagClients = {
llm: toPromiseRequestor(yield* flowCtx.flow.requestorEffect<TextCompletionRequest, TextCompletionResponse>("llm")),
embeddings: toPromiseRequestor(yield* flowCtx.flow.requestorEffect<EmbeddingsRequest, EmbeddingsResponse>("embeddings")),
docEmbeddings: toPromiseRequestor(
yield* flowCtx.flow.requestorEffect<DocumentEmbeddingsRequest, DocumentEmbeddingsResponse>("doc-embeddings"),
),
prompt: toPromiseRequestor(yield* flowCtx.flow.requestorEffect<PromptRequest, PromptResponse>("prompt")),
};
2026-06-01 16:22:25 -05:00
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),
),
),
);
2026-06-01 16:22:25 -05:00
if (response === undefined) return;
yield* producer.send(requestId, { response, endOfStream: true });
});
2026-06-01 16:22:25 -05:00
export const makeDocumentRagSpecs = (): ReadonlyArray<Spec<DocumentRagEngine>> => [
2026-06-01 20:26:47 -05:00
makeConsumerSpec<DocumentRagRequest, FlowResourceNotFoundError | MessagingDeliveryError, DocumentRagEngine>(
2026-06-01 16:22:25 -05:00
"document-rag-request",
onDocumentRagRequest,
),
2026-06-01 20:26:47 -05:00
makeProducerSpec<DocumentRagResponse>("document-rag-response"),
makeRequestResponseSpec<TextCompletionRequest, TextCompletionResponse>(
2026-06-01 16:22:25 -05:00
"llm",
"text-completion-request",
"text-completion-response",
),
2026-06-01 20:26:47 -05:00
makeRequestResponseSpec<EmbeddingsRequest, EmbeddingsResponse>(
2026-06-01 16:22:25 -05:00
"embeddings",
"embeddings-request",
"embeddings-response",
),
2026-06-01 20:26:47 -05:00
makeRequestResponseSpec<DocumentEmbeddingsRequest, DocumentEmbeddingsResponse>(
2026-06-01 16:22:25 -05:00
"doc-embeddings",
"document-embeddings-request",
"document-embeddings-response",
),
2026-06-01 20:26:47 -05:00
makeRequestResponseSpec<PromptRequest, PromptResponse>(
2026-06-01 16:22:25 -05:00
"prompt",
"prompt-request",
"prompt-response",
),
];
2026-06-01 20:26:47 -05:00
export type DocumentRagService = FlowProcessorRuntime<DocumentRagEngine>;
2026-06-01 16:22:25 -05:00
2026-06-01 20:26:47 -05:00
export function makeDocumentRagService(config: ProcessorConfig): DocumentRagService {
return makeFlowProcessor(config, {
specifications: makeDocumentRagSpecs(),
provide: (effect) =>
effect.pipe(
Effect.provideService(DocumentRagEngine, DocumentRagEngine.of(makeDocumentRagEngine())),
),
});
}
2026-06-01 20:26:47 -05:00
export const DocumentRagService = makeDocumentRagService;
2026-06-01 16:22:25 -05:00
export const program = makeFlowProcessorProgram({
2026-05-12 08:06:58 -05:00
id: "document-rag",
2026-06-01 16:22:25 -05:00
specs: makeDocumentRagSpecs,
layer: () => DocumentRagLive,
2026-05-12 08:06:58 -05:00
});
2026-06-01 23:19:54 -05:00
export function run(): Promise<void> {
return Effect.runPromise(program);
}