mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-06-30 17:09:38 +02:00
Add typed flow spec accessors
This commit is contained in:
parent
abb6f3aed0
commit
44110c5bb4
19 changed files with 457 additions and 223 deletions
|
|
@ -12,13 +12,13 @@ Verified source roots:
|
|||
- Effect v4 subtree: `/home/elpresidank/YeeBois/projects/beep-effect2/.repos/effect-v4`
|
||||
- Installed Effect beta used by this workspace: `ts/node_modules/effect`
|
||||
|
||||
Current signal counts from `ts/packages` after the 2026-06-02 Base parameter
|
||||
spec accessor slice:
|
||||
Current signal counts from `ts/packages` after the 2026-06-02 Base
|
||||
producer/requestor spec accessor slice:
|
||||
|
||||
| Signal | Count |
|
||||
| --- | ---: |
|
||||
| `Effect.runPromise` | 168 |
|
||||
| `Map<` | 82 |
|
||||
| `Map<` | 84 |
|
||||
| `WebSocket` | 62 |
|
||||
| `new Map` | 62 |
|
||||
| `toPromiseRequestor` | 0 |
|
||||
|
|
@ -83,6 +83,11 @@ Notes:
|
|||
`flow.parameter(spec)`. Bare string parameter lookup remains available as an
|
||||
`unknown` compatibility escape, while typed parameter access now decodes
|
||||
through Schema and fails with a tagged `FlowParameterDecodeError`.
|
||||
- The base producer/requestor spec accessor slice added typed spec-object
|
||||
accessors for `ProducerSpec<T>` and `RequestResponseSpec<TReq, TRes>`, then
|
||||
migrated flow service producer/requestor lookups off caller-chosen generic
|
||||
string calls. Spec object handles are scoped per `Flow` through WeakMaps and
|
||||
finalizers delete only the handle they registered.
|
||||
- `Record<string, any>` and `throwLibrarianServiceError` are now clean in
|
||||
`ts/packages`.
|
||||
|
||||
|
|
@ -647,6 +652,42 @@ Notes:
|
|||
- `cd ts && bun run test`
|
||||
- `git diff --check`
|
||||
|
||||
### 2026-06-02: Base Producer And Requestor Spec Accessor Slice
|
||||
|
||||
- Status: migrated and root-verified.
|
||||
- Completed:
|
||||
- `ts/packages/base/src/spec/producer-spec.ts` now exposes
|
||||
`ProducerSpec<T>.producerEffect(flow)` and stores typed producer handles in
|
||||
a per-spec WeakMap keyed by `Flow`.
|
||||
- `ts/packages/base/src/spec/request-response-spec.ts` now exposes
|
||||
`RequestResponseSpec<TReq, TRes>.requestorEffect(flow)` and stores typed
|
||||
requestor handles in a per-spec WeakMap keyed by `Flow`.
|
||||
- Spec finalizers remove only the exact handle they registered, avoiding
|
||||
stale finalizers deleting newer registrations for the same flow/spec pair.
|
||||
- `ts/packages/base/src/processor/flow.ts` now supports
|
||||
`flow.producerEffect(spec)`, `flow.requestorEffect(spec)`,
|
||||
`flow.producer(spec)`, and `flow.requestor(spec)` while keeping string
|
||||
accessors as untyped compatibility escapes.
|
||||
- Base service adapters and flow service handlers now reuse the same hoisted
|
||||
producer/requestor spec object in their spec arrays and handler lookups.
|
||||
- `ts/packages/base/src/__tests__/flow-spec-runtime.test.ts` covers typed
|
||||
spec-object lookups, duplicate spec identity failures, and scoped
|
||||
finalizer cleanup for producer and requestor handles.
|
||||
- Remaining:
|
||||
- Bare string `Flow` producer/requestor accessors remain compatibility
|
||||
escapes for external/legacy callers, but new Effect service code should use
|
||||
spec objects.
|
||||
- Verification:
|
||||
- `bun run --cwd ts/packages/base test -- src/__tests__/flow-spec-runtime.test.ts`
|
||||
- `bun run --cwd ts/packages/base build`
|
||||
- `bun run --cwd ts/packages/flow build`
|
||||
- `bun run --cwd ts/packages/base test`
|
||||
- `bun run --cwd ts/packages/flow test`
|
||||
- `cd ts && bun run check`
|
||||
- `cd ts && bun run build`
|
||||
- `cd ts && bun run test`
|
||||
- `git diff --check`
|
||||
|
||||
## Subagent Findings To Preserve
|
||||
|
||||
- MCP/workbench:
|
||||
|
|
@ -673,10 +714,10 @@ Notes:
|
|||
layers.
|
||||
- Existing constructor shims preserve callable-plus-newable public exports;
|
||||
removing them needs a public API split or real class redesign.
|
||||
- Typed string registries in `Flow` now have Schema-backed parameter specs.
|
||||
Producer and requestor typed spec-object accessors remain. Effect
|
||||
`HashMap`/`MutableHashMap` can improve lookup ergonomics with `Option`, but
|
||||
it does not remove the string-key type hole by itself.
|
||||
- Typed string registries in `Flow` now have Schema-backed parameter specs
|
||||
and typed producer/requestor spec-object accessors. New service handlers
|
||||
should hoist spec objects and use those accessors; bare string accessors
|
||||
remain compatibility escapes.
|
||||
- Gateway/client:
|
||||
- `EffectRpcClient` now owns its socket/RPC layer with `ManagedRuntime`.
|
||||
Socket errors/JSON parsing now use tagged errors and Schema decoding.
|
||||
|
|
@ -695,26 +736,6 @@ Notes:
|
|||
|
||||
## Ranked Findings
|
||||
|
||||
### P1: Base Typed Producer And Requestor Spec Accessors
|
||||
|
||||
- TrustGraph evidence:
|
||||
- `ts/packages/base/src/processor/flow.ts`
|
||||
- `ts/packages/base/src/spec/producer-spec.ts`
|
||||
- `ts/packages/base/src/spec/request-response-spec.ts`
|
||||
- Effect primitives:
|
||||
- Typed spec-object registries, `Context`, `Layer`, `Effect.fn`, `Option`,
|
||||
`Predicate`, `HashMap`/`MutableHashMap`.
|
||||
- Rewrite shape:
|
||||
- Parameter specs are now Schema-backed and support
|
||||
`flow.parameterEffect(spec)` / `flow.parameter(spec)`.
|
||||
- Add typed spec-object accessors for producers and requestors so call sites
|
||||
stop spelling generic string lookups.
|
||||
- Do not add assertions to quiet Effect channel inference problems.
|
||||
- Tests:
|
||||
- `cd ts && bun run --cwd packages/base test`
|
||||
- Root `cd ts && bun run check` because this surface easily pollutes Effect
|
||||
error and requirement channels.
|
||||
|
||||
### P1: Make SDK, Storage, And Provider Layers Managed Resources
|
||||
|
||||
- TrustGraph evidence:
|
||||
|
|
@ -765,10 +786,9 @@ Notes:
|
|||
|
||||
## Recommended PR Order
|
||||
|
||||
1. Complete base typed producer/requestor spec accessors.
|
||||
2. Gateway RPC callback and client streaming completion cleanup.
|
||||
3. Storage/provider managed resource cleanup.
|
||||
4. MCP parity/deletion decision and workbench platform polish.
|
||||
1. Gateway RPC callback and client streaming completion cleanup.
|
||||
2. Storage/provider managed resource cleanup.
|
||||
3. MCP parity/deletion decision and workbench platform polish.
|
||||
|
||||
## No-Op Rules
|
||||
|
||||
|
|
|
|||
|
|
@ -153,12 +153,14 @@ describe("Effect-native flow specifications", () => {
|
|||
"starts producer specs through Effect factories and exposes typed accessors",
|
||||
Effect.fnUntraced(function* () {
|
||||
const backend = new RuntimeBackend(new ScriptedConsumer<unknown>());
|
||||
const outputProducerSpec = makeProducerSpec<string>("output");
|
||||
const duplicateOutputProducerSpec = makeProducerSpec<string>("output");
|
||||
const flow = new Flow(
|
||||
"default",
|
||||
"processor",
|
||||
backend,
|
||||
{ topics: { output: "actual-output" } },
|
||||
[makeProducerSpec<string>("output")],
|
||||
[outputProducerSpec],
|
||||
);
|
||||
|
||||
yield* Effect.scoped(
|
||||
|
|
@ -166,17 +168,21 @@ describe("Effect-native flow specifications", () => {
|
|||
backend,
|
||||
Effect.gen(function* () {
|
||||
yield* flow.startEffect();
|
||||
const producer = yield* flow.producerEffect<string>("output");
|
||||
const producer = yield* flow.producerEffect(outputProducerSpec);
|
||||
const duplicateSpecError = yield* flow.producerEffect(duplicateOutputProducerSpec).pipe(Effect.flip);
|
||||
expect(duplicateSpecError._tag).toBe("FlowResourceNotFoundError");
|
||||
yield* producer.send("request-1", "hello");
|
||||
}),
|
||||
),
|
||||
);
|
||||
const closedProducerError = yield* flow.producerEffect(outputProducerSpec).pipe(Effect.flip);
|
||||
|
||||
expect(backend.producerOptions).toEqual({ topic: "actual-output" });
|
||||
expect(backend.producer.sent).toEqual([
|
||||
{ message: "hello", properties: { id: "request-1" } },
|
||||
]);
|
||||
expect(backend.producer.closeCount).toBe(1);
|
||||
expect(closedProducerError._tag).toBe("FlowResourceNotFoundError");
|
||||
}),
|
||||
);
|
||||
|
||||
|
|
@ -229,6 +235,8 @@ describe("Effect-native flow specifications", () => {
|
|||
responseConsumer.push(createMessage("response", { id: properties?.id ?? "" }));
|
||||
},
|
||||
);
|
||||
const requestResponseSpec = makeRequestResponseSpec<string, string>("rr", "request", "response");
|
||||
const duplicateRequestResponseSpec = makeRequestResponseSpec<string, string>("rr", "request", "response");
|
||||
const flow = new Flow(
|
||||
"default",
|
||||
"processor",
|
||||
|
|
@ -239,7 +247,7 @@ describe("Effect-native flow specifications", () => {
|
|||
response: "actual-response",
|
||||
},
|
||||
},
|
||||
[makeRequestResponseSpec<string, string>("rr", "request", "response")],
|
||||
[requestResponseSpec],
|
||||
);
|
||||
|
||||
const response = yield* Effect.scoped(
|
||||
|
|
@ -247,7 +255,9 @@ describe("Effect-native flow specifications", () => {
|
|||
backend,
|
||||
Effect.gen(function* () {
|
||||
yield* flow.startEffect();
|
||||
const requestor = flow.requestor<string, string>("rr");
|
||||
const duplicateSpecError = yield* flow.requestorEffect(duplicateRequestResponseSpec).pipe(Effect.flip);
|
||||
expect(duplicateSpecError._tag).toBe("FlowResourceNotFoundError");
|
||||
const requestor = flow.requestor(requestResponseSpec);
|
||||
const fiber = yield* Effect.promise(() =>
|
||||
requestor.request("request", { timeoutMs: 250 }),
|
||||
).pipe(Effect.forkChild);
|
||||
|
|
@ -256,10 +266,12 @@ describe("Effect-native flow specifications", () => {
|
|||
}),
|
||||
),
|
||||
);
|
||||
const closedRequestorError = yield* flow.requestorEffect(requestResponseSpec).pipe(Effect.flip);
|
||||
|
||||
expect(response).toBe("response");
|
||||
expect(backend.producerOptions).toEqual({ topic: "actual-request" });
|
||||
expect(responseConsumer.acknowledged.length).toBe(1);
|
||||
expect(closedRequestorError._tag).toBe("FlowResourceNotFoundError");
|
||||
}),
|
||||
);
|
||||
|
||||
|
|
@ -282,7 +294,7 @@ describe("Effect-native flow specifications", () => {
|
|||
backend,
|
||||
Effect.gen(function* () {
|
||||
yield* flow.startEffect();
|
||||
const producerError = yield* flow.producerEffect<string>("missing-producer").pipe(Effect.flip);
|
||||
const producerError = yield* flow.producerEffect("missing-producer").pipe(Effect.flip);
|
||||
const parameter = yield* flow.parameterEffect(presentParameter);
|
||||
const legacyParameter = yield* flow.parameterEffect("present");
|
||||
const parameterError = yield* flow.parameterEffect("missing-parameter").pipe(Effect.flip);
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ import {
|
|||
} from "../messaging/runtime.js";
|
||||
import { loadMessagingRuntimeConfig } from "../runtime/messaging-config.js";
|
||||
import type { ParameterSpec } from "../spec/parameter-spec.js";
|
||||
import type { ProducerSpec } from "../spec/producer-spec.js";
|
||||
import type { RequestResponseSpec } from "../spec/request-response-spec.js";
|
||||
import type { Spec, SpecRuntimeRequirements } from "../spec/types.js";
|
||||
|
||||
export interface FlowDefinition {
|
||||
|
|
@ -131,6 +133,93 @@ export function makeFlow<Requirements = never>(
|
|||
throw flowParameterDecodeError(name, spec.name, "Parameter value does not match schema");
|
||||
};
|
||||
|
||||
const getProducerEffect = (
|
||||
producerName: string,
|
||||
): Effect.Effect<EffectProducer<never>, FlowResourceNotFoundError> => {
|
||||
const producer = producers.get(producerName);
|
||||
return producer === undefined
|
||||
? Effect.fail(flowResourceNotFoundError(name, "producer", producerName))
|
||||
: Effect.succeed(producer);
|
||||
};
|
||||
|
||||
const getProducer = (producerName: string): EffectProducer<never> => {
|
||||
const producer = producers.get(producerName);
|
||||
if (producer === undefined) throw flowResourceNotFoundError(name, "producer", producerName);
|
||||
return producer;
|
||||
};
|
||||
|
||||
const getRequestorEffect = (
|
||||
requestorName: string,
|
||||
): Effect.Effect<EffectRequestResponse<never, unknown>, FlowResourceNotFoundError> => {
|
||||
const requestor = requestors.get(requestorName);
|
||||
return requestor === undefined
|
||||
? Effect.fail(flowResourceNotFoundError(name, "requestor", requestorName))
|
||||
: Effect.succeed(requestor);
|
||||
};
|
||||
|
||||
const getRequestor = (
|
||||
requestorName: string,
|
||||
): EffectRequestResponse<never, unknown> => {
|
||||
const requestor = requestors.get(requestorName);
|
||||
if (requestor === undefined) throw flowResourceNotFoundError(name, "requestor", requestorName);
|
||||
return requestor;
|
||||
};
|
||||
|
||||
const toFlowProducer = <T>(producer: EffectProducer<T>): FlowProducer<T> => ({
|
||||
send: (id, message) => compatibilityRuntime.runPromise(producer.send(id, message)),
|
||||
flush: () => compatibilityRuntime.runPromise(producer.flush),
|
||||
stop: () => compatibilityRuntime.runPromise(producer.flush.pipe(Effect.flatMap(() => producer.close))),
|
||||
});
|
||||
|
||||
const toFlowRequestor = <TReq, TRes>(
|
||||
requestor: EffectRequestResponse<TReq, TRes>,
|
||||
): FlowRequestor<TReq, TRes> => ({
|
||||
request: (request, options) =>
|
||||
compatibilityRuntime.runPromise(
|
||||
requestor.request(
|
||||
request,
|
||||
toEffectRequestOptions(options),
|
||||
),
|
||||
),
|
||||
stop: () => compatibilityRuntime.runPromise(requestor.stop),
|
||||
});
|
||||
|
||||
function producerEffect<T>(
|
||||
producerSpec: ProducerSpec<T>,
|
||||
): Effect.Effect<EffectProducer<T>, FlowResourceNotFoundError>;
|
||||
function producerEffect(
|
||||
producerName: string,
|
||||
): Effect.Effect<EffectProducer<never>, FlowResourceNotFoundError>;
|
||||
function producerEffect<T>(
|
||||
producer: string | ProducerSpec<T>,
|
||||
) {
|
||||
if (typeof producer === "string") {
|
||||
return getProducerEffect(producer);
|
||||
}
|
||||
if (!producers.has(producer.name)) {
|
||||
return Effect.fail(flowResourceNotFoundError(name, "producer", producer.name));
|
||||
}
|
||||
return producer.producerEffect(flow);
|
||||
}
|
||||
|
||||
function requestorEffect<TReq, TRes>(
|
||||
requestorSpec: RequestResponseSpec<TReq, TRes>,
|
||||
): Effect.Effect<EffectRequestResponse<TReq, TRes>, FlowResourceNotFoundError>;
|
||||
function requestorEffect(
|
||||
requestorName: string,
|
||||
): Effect.Effect<EffectRequestResponse<never, unknown>, FlowResourceNotFoundError>;
|
||||
function requestorEffect<TReq, TRes>(
|
||||
requestor: string | RequestResponseSpec<TReq, TRes>,
|
||||
) {
|
||||
if (typeof requestor === "string") {
|
||||
return getRequestorEffect(requestor);
|
||||
}
|
||||
if (!requestors.has(requestor.name)) {
|
||||
return Effect.fail(flowResourceNotFoundError(name, "requestor", requestor.name));
|
||||
}
|
||||
return requestor.requestorEffect(flow);
|
||||
}
|
||||
|
||||
function parameterEffect<T>(
|
||||
parameterSpec: ParameterSpec<T>,
|
||||
): Effect.Effect<T, FlowParameterError>;
|
||||
|
|
@ -158,6 +247,34 @@ export function makeFlow<Requirements = never>(
|
|||
return decodeParameter(parameter, value);
|
||||
}
|
||||
|
||||
function producer<T>(producerSpec: ProducerSpec<T>): FlowProducer<T>;
|
||||
function producer(producerName: string): FlowProducer<never>;
|
||||
function producer<T>(producer: string | ProducerSpec<T>) {
|
||||
if (typeof producer === "string") {
|
||||
return toFlowProducer(getProducer(producer));
|
||||
}
|
||||
if (!producers.has(producer.name)) {
|
||||
throw flowResourceNotFoundError(name, "producer", producer.name);
|
||||
}
|
||||
return toFlowProducer(compatibilityRuntime.runSync(producer.producerEffect(flow)));
|
||||
}
|
||||
|
||||
function requestor<TReq, TRes>(
|
||||
requestorSpec: RequestResponseSpec<TReq, TRes>,
|
||||
): FlowRequestor<TReq, TRes>;
|
||||
function requestor(requestorName: string): FlowRequestor<never, unknown>;
|
||||
function requestor<TReq, TRes>(
|
||||
requestor: string | RequestResponseSpec<TReq, TRes>,
|
||||
) {
|
||||
if (typeof requestor === "string") {
|
||||
return toFlowRequestor(getRequestor(requestor));
|
||||
}
|
||||
if (!requestors.has(requestor.name)) {
|
||||
throw flowResourceNotFoundError(name, "requestor", requestor.name);
|
||||
}
|
||||
return toFlowRequestor(compatibilityRuntime.runSync(requestor.requestorEffect(flow)));
|
||||
}
|
||||
|
||||
const flow = {
|
||||
name,
|
||||
processorId,
|
||||
|
|
@ -239,36 +356,16 @@ export function makeFlow<Requirements = never>(
|
|||
setParameter(parameterName: string, value: unknown): void {
|
||||
parameters.set(parameterName, value);
|
||||
},
|
||||
producerEffect<T>(producerName: string): Effect.Effect<EffectProducer<T>, FlowResourceNotFoundError> {
|
||||
const p = producers.get(producerName);
|
||||
return p === undefined
|
||||
? Effect.fail(flowResourceNotFoundError(name, "producer", producerName))
|
||||
: Effect.succeed(p as EffectProducer<T>);
|
||||
},
|
||||
producerEffect,
|
||||
consumerEffect(consumerName: string): Effect.Effect<EffectConsumer, FlowResourceNotFoundError> {
|
||||
const c = consumers.get(consumerName);
|
||||
return c === undefined
|
||||
? Effect.fail(flowResourceNotFoundError(name, "consumer", consumerName))
|
||||
: Effect.succeed(c);
|
||||
},
|
||||
requestorEffect<TReq, TRes>(
|
||||
requestorName: string,
|
||||
): Effect.Effect<EffectRequestResponse<TReq, TRes>, FlowResourceNotFoundError> {
|
||||
const rr = requestors.get(requestorName);
|
||||
return rr === undefined
|
||||
? Effect.fail(flowResourceNotFoundError(name, "requestor", requestorName))
|
||||
: Effect.succeed(rr as EffectRequestResponse<TReq, TRes>);
|
||||
},
|
||||
requestorEffect,
|
||||
parameterEffect,
|
||||
producer<T>(producerName: string): FlowProducer<T> {
|
||||
const p = producers.get(producerName);
|
||||
if (p === undefined) throw flowResourceNotFoundError(name, "producer", producerName);
|
||||
return {
|
||||
send: (id, message) => compatibilityRuntime.runPromise((p as EffectProducer<T>).send(id, message)),
|
||||
flush: () => compatibilityRuntime.runPromise(p.flush),
|
||||
stop: () => compatibilityRuntime.runPromise(p.flush.pipe(Effect.flatMap(() => p.close))),
|
||||
};
|
||||
},
|
||||
producer,
|
||||
consumer(consumerName: string): FlowConsumer {
|
||||
const c = consumers.get(consumerName);
|
||||
if (c === undefined) throw flowResourceNotFoundError(name, "consumer", consumerName);
|
||||
|
|
@ -276,20 +373,7 @@ export function makeFlow<Requirements = never>(
|
|||
stop: () => compatibilityRuntime.runPromise(c.stop),
|
||||
};
|
||||
},
|
||||
requestor<TReq, TRes>(requestorName: string): FlowRequestor<TReq, TRes> {
|
||||
const rr = requestors.get(requestorName);
|
||||
if (rr === undefined) throw flowResourceNotFoundError(name, "requestor", requestorName);
|
||||
return {
|
||||
request: (request, options) =>
|
||||
compatibilityRuntime.runPromise(
|
||||
(rr as EffectRequestResponse<TReq, TRes>).request(
|
||||
request,
|
||||
toEffectRequestOptions(options),
|
||||
),
|
||||
),
|
||||
stop: () => compatibilityRuntime.runPromise(rr.stop),
|
||||
};
|
||||
},
|
||||
requestor,
|
||||
parameter,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ export class Embeddings extends Context.Service<Embeddings, EmbeddingsServiceSha
|
|||
"@trustgraph/base/services/embeddings-service/Embeddings",
|
||||
) {}
|
||||
|
||||
const EmbeddingsResponseProducer = makeProducerSpec<EmbeddingsResponse>("embeddings-response");
|
||||
|
||||
const onEmbeddingsRequest = Effect.fn("EmbeddingsService.onRequest")(function* (
|
||||
msg: EmbeddingsRequest,
|
||||
properties: Record<string, string>,
|
||||
|
|
@ -41,7 +43,7 @@ const onEmbeddingsRequest = Effect.fn("EmbeddingsService.onRequest")(function* (
|
|||
return;
|
||||
}
|
||||
|
||||
const responseProducer = yield* flowCtx.flow.producerEffect<EmbeddingsResponse>("embeddings-response");
|
||||
const responseProducer = yield* flowCtx.flow.producerEffect(EmbeddingsResponseProducer);
|
||||
const embeddings = yield* Embeddings;
|
||||
const response = yield* embeddings.embed(msg.text, msg.model).pipe(
|
||||
Effect.map((vectors) => ({ vectors }) satisfies EmbeddingsResponse),
|
||||
|
|
@ -70,7 +72,7 @@ export const makeEmbeddingsSpecs = (): ReadonlyArray<Spec<Embeddings>> => [
|
|||
"embeddings-request",
|
||||
onEmbeddingsRequest,
|
||||
),
|
||||
makeProducerSpec<EmbeddingsResponse>("embeddings-response"),
|
||||
EmbeddingsResponseProducer,
|
||||
makeParameterSpec("model"),
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -124,6 +124,8 @@ const llmErrorResponse = (error: LlmServiceError): TextCompletionResponse => ({
|
|||
endOfStream: true,
|
||||
});
|
||||
|
||||
const TextCompletionResponseProducer = makeProducerSpec<TextCompletionResponse>("text-completion-response");
|
||||
|
||||
const sendStreamingResponse = Effect.fn("LlmService.sendStreamingResponse")(function* (
|
||||
llm: LlmServiceShape,
|
||||
requestId: string,
|
||||
|
|
@ -158,9 +160,7 @@ const onLlmRequest = Effect.fn("LlmService.onRequest")(function* (
|
|||
const requestId = properties.id;
|
||||
if (requestId === undefined || requestId.length === 0) return;
|
||||
|
||||
const responseProducer = yield* flowCtx.flow.producerEffect<TextCompletionResponse>(
|
||||
"text-completion-response",
|
||||
);
|
||||
const responseProducer = yield* flowCtx.flow.producerEffect(TextCompletionResponseProducer);
|
||||
const llm = yield* Llm;
|
||||
|
||||
if (msg.streaming === true && llm.supportsStreaming()) {
|
||||
|
|
@ -210,7 +210,7 @@ export const makeLlmSpecs = (): ReadonlyArray<Spec<Llm>> => [
|
|||
"text-completion-request",
|
||||
onLlmRequest,
|
||||
),
|
||||
makeProducerSpec<TextCompletionResponse>("text-completion-response"),
|
||||
TextCompletionResponseProducer,
|
||||
makeParameterSpec("model"),
|
||||
makeParameterSpec("temperature"),
|
||||
];
|
||||
|
|
|
|||
|
|
@ -8,6 +8,11 @@ import { Effect } from "effect";
|
|||
import type { Spec } from "./types.js";
|
||||
import type { Flow, FlowDefinition } from "../processor/flow.js";
|
||||
import {
|
||||
flowResourceNotFoundError,
|
||||
type FlowResourceNotFoundError,
|
||||
} from "../errors.js";
|
||||
import {
|
||||
type EffectProducer,
|
||||
ProducerFactory,
|
||||
} from "../messaging/runtime.js";
|
||||
|
||||
|
|
@ -15,9 +20,41 @@ declare const ProducerSpecType: unique symbol;
|
|||
|
||||
export interface ProducerSpec<T> extends Spec {
|
||||
readonly [ProducerSpecType]?: (_: T) => T;
|
||||
readonly producerEffect: <Requirements = never>(
|
||||
flow: Flow<Requirements>,
|
||||
) => Effect.Effect<EffectProducer<T>, FlowResourceNotFoundError>;
|
||||
}
|
||||
|
||||
export function makeProducerSpec<T>(name: string): ProducerSpec<T> {
|
||||
const producers = new WeakMap<object, EffectProducer<T>>();
|
||||
|
||||
const registerProducer = <Requirements>(
|
||||
flow: Flow<Requirements>,
|
||||
producer: EffectProducer<T>,
|
||||
) =>
|
||||
Effect.sync(() => {
|
||||
producers.set(flow, producer);
|
||||
});
|
||||
|
||||
const unregisterProducer = <Requirements>(
|
||||
flow: Flow<Requirements>,
|
||||
producer: EffectProducer<T>,
|
||||
) =>
|
||||
Effect.sync(() => {
|
||||
if (producers.get(flow) === producer) {
|
||||
producers.delete(flow);
|
||||
}
|
||||
});
|
||||
|
||||
const producerEffect = <Requirements>(
|
||||
flow: Flow<Requirements>,
|
||||
): Effect.Effect<EffectProducer<T>, FlowResourceNotFoundError> => {
|
||||
const producer = producers.get(flow);
|
||||
return producer === undefined
|
||||
? Effect.fail(flowResourceNotFoundError(flow.name, "producer", name))
|
||||
: Effect.succeed(producer);
|
||||
};
|
||||
|
||||
const addEffect = Effect.fn("ProducerSpec.addEffect")(function* (
|
||||
flow: Flow,
|
||||
definition: FlowDefinition,
|
||||
|
|
@ -26,10 +63,13 @@ export function makeProducerSpec<T>(name: string): ProducerSpec<T> {
|
|||
const factory = yield* ProducerFactory;
|
||||
const producer = yield* factory.make<T>({ topic });
|
||||
flow.registerProducer(name, producer);
|
||||
yield* registerProducer(flow, producer);
|
||||
yield* Effect.addFinalizer(() => unregisterProducer(flow, producer));
|
||||
});
|
||||
|
||||
return {
|
||||
name,
|
||||
producerEffect,
|
||||
addEffect,
|
||||
add: (flow, pubsub, definition, context) =>
|
||||
flow.runInCompatibilityScope(addEffect(flow, definition), pubsub, context),
|
||||
|
|
|
|||
|
|
@ -11,6 +11,11 @@ import { Effect } from "effect";
|
|||
import type { Spec } from "./types.js";
|
||||
import type { Flow, FlowDefinition } from "../processor/flow.js";
|
||||
import {
|
||||
flowResourceNotFoundError,
|
||||
type FlowResourceNotFoundError,
|
||||
} from "../errors.js";
|
||||
import {
|
||||
type EffectRequestResponse,
|
||||
RequestResponseFactory,
|
||||
} from "../messaging/runtime.js";
|
||||
|
||||
|
|
@ -21,6 +26,9 @@ export interface RequestResponseSpec<TReq, TRes> extends Spec {
|
|||
readonly request: TReq;
|
||||
readonly response: TRes;
|
||||
};
|
||||
readonly requestorEffect: <Requirements = never>(
|
||||
flow: Flow<Requirements>,
|
||||
) => Effect.Effect<EffectRequestResponse<TReq, TRes>, FlowResourceNotFoundError>;
|
||||
}
|
||||
|
||||
export function makeRequestResponseSpec<TReq, TRes>(
|
||||
|
|
@ -28,6 +36,35 @@ export function makeRequestResponseSpec<TReq, TRes>(
|
|||
requestTopicName: string,
|
||||
responseTopicName: string,
|
||||
): RequestResponseSpec<TReq, TRes> {
|
||||
const requestors = new WeakMap<object, EffectRequestResponse<TReq, TRes>>();
|
||||
|
||||
const registerRequestor = <Requirements>(
|
||||
flow: Flow<Requirements>,
|
||||
requestor: EffectRequestResponse<TReq, TRes>,
|
||||
) =>
|
||||
Effect.sync(() => {
|
||||
requestors.set(flow, requestor);
|
||||
});
|
||||
|
||||
const unregisterRequestor = <Requirements>(
|
||||
flow: Flow<Requirements>,
|
||||
requestor: EffectRequestResponse<TReq, TRes>,
|
||||
) =>
|
||||
Effect.sync(() => {
|
||||
if (requestors.get(flow) === requestor) {
|
||||
requestors.delete(flow);
|
||||
}
|
||||
});
|
||||
|
||||
const requestorEffect = <Requirements>(
|
||||
flow: Flow<Requirements>,
|
||||
): Effect.Effect<EffectRequestResponse<TReq, TRes>, FlowResourceNotFoundError> => {
|
||||
const requestor = requestors.get(flow);
|
||||
return requestor === undefined
|
||||
? Effect.fail(flowResourceNotFoundError(flow.name, "requestor", name))
|
||||
: Effect.succeed(requestor);
|
||||
};
|
||||
|
||||
const addEffect = Effect.fn("RequestResponseSpec.addEffect")(function* (
|
||||
flow: Flow,
|
||||
definition: FlowDefinition,
|
||||
|
|
@ -41,10 +78,13 @@ export function makeRequestResponseSpec<TReq, TRes>(
|
|||
subscription: `${flow.processorId}-${flow.name}-${name}`,
|
||||
});
|
||||
flow.registerRequestor(name, requestor);
|
||||
yield* registerRequestor(flow, requestor);
|
||||
yield* Effect.addFinalizer(() => unregisterRequestor(flow, requestor));
|
||||
});
|
||||
|
||||
return {
|
||||
name,
|
||||
requestorEffect,
|
||||
addEffect,
|
||||
add: (flow, pubsub, definition, context) =>
|
||||
flow.runInCompatibilityScope(addEffect(flow, definition), pubsub, context),
|
||||
|
|
|
|||
|
|
@ -71,6 +71,8 @@ export class McpToolRuntime extends Context.Service<
|
|||
McpToolRuntimeService
|
||||
>()("@trustgraph/flow/agent/mcp-tool/service/McpToolRuntime") {}
|
||||
|
||||
const McpToolResponseProducer = makeProducerSpec<ToolResponse>("mcp-tool-response");
|
||||
|
||||
const mcpToolError = (
|
||||
operation: string,
|
||||
cause: unknown,
|
||||
|
|
@ -246,7 +248,7 @@ const onMcpToolRequest = Effect.fn("McpToolService.onRequest")(function* (
|
|||
const requestId = properties.id;
|
||||
if (requestId === undefined || requestId.length === 0) return;
|
||||
|
||||
const responseProducer = yield* flowCtx.flow.producerEffect<ToolResponse>("mcp-tool-response");
|
||||
const responseProducer = yield* flowCtx.flow.producerEffect(McpToolResponseProducer);
|
||||
const runtime = yield* McpToolRuntime;
|
||||
|
||||
const result = yield* parametersFromJson(msg.name, msg.parameters).pipe(
|
||||
|
|
@ -284,7 +286,7 @@ export const makeMcpToolSpecs = (): ReadonlyArray<Spec<McpToolRuntime>> => [
|
|||
"mcp-tool-request",
|
||||
onMcpToolRequest,
|
||||
),
|
||||
makeProducerSpec<ToolResponse>("mcp-tool-response"),
|
||||
McpToolResponseProducer,
|
||||
];
|
||||
|
||||
export const makeMcpToolConfigHandlers = (): ReadonlyArray<
|
||||
|
|
|
|||
|
|
@ -71,6 +71,33 @@ class AgentToolExecutionError extends S.TaggedErrorClass<AgentToolExecutionError
|
|||
},
|
||||
) {}
|
||||
|
||||
const AgentResponseProducer = makeProducerSpec<AgentResponse>("agent-response");
|
||||
const AgentLlmClient = makeRequestResponseSpec<TextCompletionRequest, TextCompletionResponse>(
|
||||
"llm",
|
||||
"text-completion-request",
|
||||
"text-completion-response",
|
||||
);
|
||||
const AgentGraphRagClient = makeRequestResponseSpec<GraphRagRequest, GraphRagResponse>(
|
||||
"graph-rag",
|
||||
"graph-rag-request",
|
||||
"graph-rag-response",
|
||||
);
|
||||
const AgentDocRagClient = makeRequestResponseSpec<DocumentRagRequest, DocumentRagResponse>(
|
||||
"doc-rag",
|
||||
"document-rag-request",
|
||||
"document-rag-response",
|
||||
);
|
||||
const AgentTriplesClient = makeRequestResponseSpec<TriplesQueryRequest, TriplesQueryResponse>(
|
||||
"triples",
|
||||
"triples-request",
|
||||
"triples-response",
|
||||
);
|
||||
const AgentMcpToolClient = makeRequestResponseSpec<ToolRequest, ToolResponse>(
|
||||
"mcp-tool",
|
||||
"mcp-tool-request",
|
||||
"mcp-tool-response",
|
||||
);
|
||||
|
||||
const UnknownRecord = S.Record(S.String, S.Unknown);
|
||||
const ToolArgumentConfig = S.StructWithRest(
|
||||
S.Struct({
|
||||
|
|
@ -248,10 +275,10 @@ const wireTools = Effect.fn("AgentService.wireTools")(function* (
|
|||
collection: string | undefined,
|
||||
onExplain: (data: ExplainData) => void,
|
||||
) {
|
||||
const graphRag = yield* flowCtx.flow.requestorEffect<GraphRagRequest, GraphRagResponse>("graph-rag");
|
||||
const docRag = yield* flowCtx.flow.requestorEffect<DocumentRagRequest, DocumentRagResponse>("doc-rag");
|
||||
const triples = yield* flowCtx.flow.requestorEffect<TriplesQueryRequest, TriplesQueryResponse>("triples");
|
||||
const mcpTool = yield* flowCtx.flow.requestorEffect<ToolRequest, ToolResponse>("mcp-tool");
|
||||
const graphRag = yield* flowCtx.flow.requestorEffect(AgentGraphRagClient);
|
||||
const docRag = yield* flowCtx.flow.requestorEffect(AgentDocRagClient);
|
||||
const triples = yield* flowCtx.flow.requestorEffect(AgentTriplesClient);
|
||||
const mcpTool = yield* flowCtx.flow.requestorEffect(AgentMcpToolClient);
|
||||
|
||||
return tools.map((tool) => {
|
||||
const rawImplType = tool.config?.type;
|
||||
|
|
@ -300,9 +327,9 @@ const defaultTools = Effect.fn("AgentService.defaultTools")(function* (
|
|||
collection: string | undefined,
|
||||
onExplain: (data: ExplainData) => void,
|
||||
) {
|
||||
const graphRag = yield* flowCtx.flow.requestorEffect<GraphRagRequest, GraphRagResponse>("graph-rag");
|
||||
const docRag = yield* flowCtx.flow.requestorEffect<DocumentRagRequest, DocumentRagResponse>("doc-rag");
|
||||
const triples = yield* flowCtx.flow.requestorEffect<TriplesQueryRequest, TriplesQueryResponse>("triples");
|
||||
const graphRag = yield* flowCtx.flow.requestorEffect(AgentGraphRagClient);
|
||||
const docRag = yield* flowCtx.flow.requestorEffect(AgentDocRagClient);
|
||||
const triples = yield* flowCtx.flow.requestorEffect(AgentTriplesClient);
|
||||
|
||||
return [
|
||||
createKnowledgeQueryTool(
|
||||
|
|
@ -346,7 +373,7 @@ const onAgentRequest = Effect.fn("AgentService.onRequest")(function* (
|
|||
const requestId = properties.id;
|
||||
if (requestId === undefined || requestId.length === 0) return;
|
||||
|
||||
const responseProducer = yield* flowCtx.flow.producerEffect<AgentResponse>("agent-response");
|
||||
const responseProducer = yield* flowCtx.flow.producerEffect(AgentResponseProducer);
|
||||
|
||||
yield* Effect.gen(function* () {
|
||||
const runtime = yield* AgentRuntime;
|
||||
|
|
@ -367,10 +394,7 @@ const onAgentRequest = Effect.fn("AgentService.onRequest")(function* (
|
|||
msg.question,
|
||||
);
|
||||
|
||||
const llmClient = yield* flowCtx.flow.requestorEffect<
|
||||
TextCompletionRequest,
|
||||
TextCompletionResponse
|
||||
>("llm");
|
||||
const llmClient = yield* flowCtx.flow.requestorEffect(AgentLlmClient);
|
||||
|
||||
let conversation = initialPrompt;
|
||||
|
||||
|
|
@ -472,32 +496,12 @@ export const makeAgentSpecs = (): ReadonlyArray<Spec<AgentRuntime>> => [
|
|||
"agent-request",
|
||||
onAgentRequest,
|
||||
),
|
||||
makeProducerSpec<AgentResponse>("agent-response"),
|
||||
makeRequestResponseSpec<TextCompletionRequest, TextCompletionResponse>(
|
||||
"llm",
|
||||
"text-completion-request",
|
||||
"text-completion-response",
|
||||
),
|
||||
makeRequestResponseSpec<GraphRagRequest, GraphRagResponse>(
|
||||
"graph-rag",
|
||||
"graph-rag-request",
|
||||
"graph-rag-response",
|
||||
),
|
||||
makeRequestResponseSpec<DocumentRagRequest, DocumentRagResponse>(
|
||||
"doc-rag",
|
||||
"document-rag-request",
|
||||
"document-rag-response",
|
||||
),
|
||||
makeRequestResponseSpec<TriplesQueryRequest, TriplesQueryResponse>(
|
||||
"triples",
|
||||
"triples-request",
|
||||
"triples-response",
|
||||
),
|
||||
makeRequestResponseSpec<ToolRequest, ToolResponse>(
|
||||
"mcp-tool",
|
||||
"mcp-tool-request",
|
||||
"mcp-tool-response",
|
||||
),
|
||||
AgentResponseProducer,
|
||||
AgentLlmClient,
|
||||
AgentGraphRagClient,
|
||||
AgentDocRagClient,
|
||||
AgentTriplesClient,
|
||||
AgentMcpToolClient,
|
||||
];
|
||||
|
||||
export const makeAgentConfigHandlers = (): ReadonlyArray<
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ const DEFAULT_CHUNK_SIZE = 2000;
|
|||
const DEFAULT_CHUNK_OVERLAP = 100;
|
||||
const ChunkSizeParameter = makeParameterSpec("chunk-size", S.Number);
|
||||
const ChunkOverlapParameter = makeParameterSpec("chunk-overlap", S.Number);
|
||||
const ChunkOutputProducer = makeProducerSpec<Chunk>("chunk-output");
|
||||
const ChunkTriplesProducer = makeProducerSpec<Triples>("chunk-triples");
|
||||
|
||||
const onChunkMessage = Effect.fn("ChunkingService.onMessage")(function* (
|
||||
msg: TextDocument,
|
||||
|
|
@ -62,7 +64,7 @@ const onChunkMessage = Effect.fn("ChunkingService.onMessage")(function* (
|
|||
`[ChunkingService] Split document ${msg.documentId} into ${chunks.length} chunks (size=${chunkSize}, overlap=${chunkOverlap})`,
|
||||
);
|
||||
|
||||
const outputProducer = yield* flowCtx.flow.producerEffect<Chunk>("chunk-output");
|
||||
const outputProducer = yield* flowCtx.flow.producerEffect(ChunkOutputProducer);
|
||||
|
||||
yield* Effect.forEach(
|
||||
chunks,
|
||||
|
|
@ -83,8 +85,8 @@ export const makeChunkingSpecs = (): ReadonlyArray<
|
|||
"chunk-input",
|
||||
onChunkMessage,
|
||||
),
|
||||
makeProducerSpec<Chunk>("chunk-output"),
|
||||
makeProducerSpec<Triples>("chunk-triples"),
|
||||
ChunkOutputProducer,
|
||||
ChunkTriplesProducer,
|
||||
ChunkSizeParameter,
|
||||
ChunkOverlapParameter,
|
||||
];
|
||||
|
|
|
|||
|
|
@ -96,6 +96,14 @@ const loadPageText = Effect.fn("loadPageText")(function*(
|
|||
.join(" ");
|
||||
});
|
||||
|
||||
const DecodeOutputProducer = makeProducerSpec<TextDocument>("decode-output");
|
||||
const DecodeTriplesProducer = makeProducerSpec<Triples>("decode-triples");
|
||||
const LibrarianClient = makeRequestResponseSpec<LibrarianRequest, LibrarianResponse>(
|
||||
"librarian-client",
|
||||
"librarian-request",
|
||||
"librarian-response",
|
||||
);
|
||||
|
||||
const onPdfDecodeMessage = Effect.fn("PdfDecoderService.onMessage")(function* (
|
||||
msg: Document,
|
||||
properties: Record<string, string>,
|
||||
|
|
@ -107,9 +115,7 @@ const onPdfDecodeMessage = Effect.fn("PdfDecoderService.onMessage")(function* (
|
|||
const { documentId } = msg;
|
||||
const user = msg.metadata.user;
|
||||
|
||||
const librarian = yield* flowCtx.flow.requestorEffect<LibrarianRequest, LibrarianResponse>(
|
||||
"librarian-client",
|
||||
);
|
||||
const librarian = yield* flowCtx.flow.requestorEffect(LibrarianClient);
|
||||
|
||||
const metadataResp = yield* librarian.request({
|
||||
operation: "get-document-metadata",
|
||||
|
|
@ -152,8 +158,8 @@ const onPdfDecodeMessage = Effect.fn("PdfDecoderService.onMessage")(function* (
|
|||
|
||||
yield* Effect.log(`[PdfDecoder] Document ${documentId}: ${pdf.numPages} pages`);
|
||||
|
||||
const outputProducer = yield* flowCtx.flow.producerEffect<TextDocument>("decode-output");
|
||||
const triplesProducer = yield* flowCtx.flow.producerEffect<Triples>("decode-triples");
|
||||
const outputProducer = yield* flowCtx.flow.producerEffect(DecodeOutputProducer);
|
||||
const triplesProducer = yield* flowCtx.flow.producerEffect(DecodeTriplesProducer);
|
||||
|
||||
for (let i = 1; i <= pdf.numPages; i++) {
|
||||
const pageText = yield* loadPageText(documentId, i, pdf);
|
||||
|
|
@ -219,13 +225,9 @@ const onPdfDecodeMessage = Effect.fn("PdfDecoderService.onMessage")(function* (
|
|||
|
||||
export const makePdfDecoderSpecs = (): ReadonlyArray<Spec<never>> => [
|
||||
makeConsumerSpec<Document, PdfDecoderHandlerError>("decode-input", onPdfDecodeMessage),
|
||||
makeProducerSpec<TextDocument>("decode-output"),
|
||||
makeProducerSpec<Triples>("decode-triples"),
|
||||
makeRequestResponseSpec<LibrarianRequest, LibrarianResponse>(
|
||||
"librarian-client",
|
||||
"librarian-request",
|
||||
"librarian-response",
|
||||
),
|
||||
DecodeOutputProducer,
|
||||
DecodeTriplesProducer,
|
||||
LibrarianClient,
|
||||
];
|
||||
|
||||
export type PdfDecoderService = FlowProcessorRuntime;
|
||||
|
|
|
|||
|
|
@ -69,6 +69,19 @@ type KnowledgeExtractHandlerError =
|
|||
type PromptClient = EffectRequestResponse<PromptRequest, PromptResponse>;
|
||||
type LlmClient = EffectRequestResponse<TextCompletionRequest, TextCompletionResponse>;
|
||||
|
||||
const ExtractTriplesProducer = makeProducerSpec<Triples>("extract-triples");
|
||||
const ExtractEntityContextsProducer = makeProducerSpec<EntityContexts>("extract-entity-contexts");
|
||||
const PromptClientSpec = makeRequestResponseSpec<PromptRequest, PromptResponse>(
|
||||
"prompt-client",
|
||||
"prompt-request",
|
||||
"prompt-response",
|
||||
);
|
||||
const LlmClientSpec = makeRequestResponseSpec<TextCompletionRequest, TextCompletionResponse>(
|
||||
"llm-client",
|
||||
"text-completion-request",
|
||||
"text-completion-response",
|
||||
);
|
||||
|
||||
const requestPrompt = Effect.fn("KnowledgeExtract.requestPrompt")(function* (
|
||||
promptClient: PromptClient,
|
||||
name: string,
|
||||
|
|
@ -153,10 +166,10 @@ const onKnowledgeExtractMessage = Effect.fn("KnowledgeExtractService.onMessage")
|
|||
const text = msg.chunk;
|
||||
if (text.trim().length === 0) return;
|
||||
|
||||
const promptClient = yield* flowCtx.flow.requestorEffect<PromptRequest, PromptResponse>("prompt-client");
|
||||
const llmClient = yield* flowCtx.flow.requestorEffect<TextCompletionRequest, TextCompletionResponse>("llm-client");
|
||||
const triplesProducer = yield* flowCtx.flow.producerEffect<Triples>("extract-triples");
|
||||
const entityContextsProducer = yield* flowCtx.flow.producerEffect<EntityContexts>("extract-entity-contexts");
|
||||
const promptClient = yield* flowCtx.flow.requestorEffect(PromptClientSpec);
|
||||
const llmClient = yield* flowCtx.flow.requestorEffect(LlmClientSpec);
|
||||
const triplesProducer = yield* flowCtx.flow.producerEffect(ExtractTriplesProducer);
|
||||
const entityContextsProducer = yield* flowCtx.flow.producerEffect(ExtractEntityContextsProducer);
|
||||
|
||||
const allTriples: Triple[] = [];
|
||||
const allEntityContexts: EntityContext[] = [];
|
||||
|
|
@ -270,18 +283,10 @@ export const makeKnowledgeExtractSpecs = (): ReadonlyArray<Spec<never>> => [
|
|||
"extract-input",
|
||||
onKnowledgeExtractMessage,
|
||||
),
|
||||
makeProducerSpec<Triples>("extract-triples"),
|
||||
makeProducerSpec<EntityContexts>("extract-entity-contexts"),
|
||||
makeRequestResponseSpec<PromptRequest, PromptResponse>(
|
||||
"prompt-client",
|
||||
"prompt-request",
|
||||
"prompt-response",
|
||||
),
|
||||
makeRequestResponseSpec<TextCompletionRequest, TextCompletionResponse>(
|
||||
"llm-client",
|
||||
"text-completion-request",
|
||||
"text-completion-response",
|
||||
),
|
||||
ExtractTriplesProducer,
|
||||
ExtractEntityContextsProducer,
|
||||
PromptClientSpec,
|
||||
LlmClientSpec,
|
||||
];
|
||||
|
||||
export type KnowledgeExtractService = FlowProcessorRuntime;
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ const programRuntimes = new WeakMap<PromptTemplateConfig, PromptTemplateRuntime>
|
|||
const makePromptTemplateRuntime = (config: PromptTemplateConfig): PromptTemplateRuntime => {
|
||||
const templates = new Map<string, PromptTemplate>();
|
||||
const configKey = config.configKey ?? "prompt";
|
||||
const PromptResponseProducer = makeProducerSpec<PromptResponse>("prompt-response");
|
||||
|
||||
const onPromptConfig = Effect.fn("PromptTemplateService.onConfig")(function* (
|
||||
pushedConfig: Record<string, unknown>,
|
||||
|
|
@ -114,7 +115,7 @@ const makePromptTemplateRuntime = (config: PromptTemplateConfig): PromptTemplate
|
|||
const requestId = properties.id;
|
||||
if (requestId === undefined || requestId.length === 0) return;
|
||||
|
||||
const responseProducer = yield* flowCtx.flow.producerEffect<PromptResponse>("prompt-response");
|
||||
const responseProducer = yield* flowCtx.flow.producerEffect(PromptResponseProducer);
|
||||
const template = templates.get(msg.name);
|
||||
if (template === undefined) {
|
||||
yield* responseProducer.send(requestId, {
|
||||
|
|
@ -142,7 +143,7 @@ const makePromptTemplateRuntime = (config: PromptTemplateConfig): PromptTemplate
|
|||
"prompt-request",
|
||||
onRequest,
|
||||
),
|
||||
makeProducerSpec<PromptResponse>("prompt-response"),
|
||||
PromptResponseProducer,
|
||||
],
|
||||
configHandlers: [onPromptConfig],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ import {
|
|||
type QdrantDocQueryConfig,
|
||||
} from "./qdrant-doc.js";
|
||||
|
||||
const DocumentEmbeddingsResponseProducer = makeProducerSpec<DocumentEmbeddingsResponse>("document-embeddings-response");
|
||||
|
||||
const onDocEmbeddingsQueryMessage = Effect.fn("DocEmbeddingsQueryService.onMessage")(function* (
|
||||
msg: DocumentEmbeddingsRequest,
|
||||
properties: Record<string, string>,
|
||||
|
|
@ -38,7 +40,7 @@ const onDocEmbeddingsQueryMessage = Effect.fn("DocEmbeddingsQueryService.onMessa
|
|||
const requestId = properties.id;
|
||||
if (requestId === undefined || requestId.length === 0) return;
|
||||
|
||||
const producer = yield* flowCtx.flow.producerEffect<DocumentEmbeddingsResponse>("document-embeddings-response");
|
||||
const producer = yield* flowCtx.flow.producerEffect(DocumentEmbeddingsResponseProducer);
|
||||
const query = yield* QdrantDocEmbeddingsQueryService;
|
||||
const collection = msg.collection ?? "default";
|
||||
const allChunks: DocumentEmbeddingsResponse["chunks"] = [];
|
||||
|
|
@ -85,7 +87,7 @@ export const makeDocEmbeddingsQuerySpecs = (): ReadonlyArray<Spec<QdrantDocEmbed
|
|||
FlowResourceNotFoundError | MessagingDeliveryError,
|
||||
QdrantDocEmbeddingsQueryService
|
||||
>("document-embeddings-request", onDocEmbeddingsQueryMessage),
|
||||
makeProducerSpec<DocumentEmbeddingsResponse>("document-embeddings-response"),
|
||||
DocumentEmbeddingsResponseProducer,
|
||||
];
|
||||
|
||||
export type DocEmbeddingsQueryService = FlowProcessorRuntime<QdrantDocEmbeddingsQueryService>;
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ import {
|
|||
type QdrantGraphQueryConfig,
|
||||
} from "./qdrant-graph.js";
|
||||
|
||||
const GraphEmbeddingsResponseProducer = makeProducerSpec<GraphEmbeddingsResponse>("graph-embeddings-response");
|
||||
|
||||
const onGraphEmbeddingsQueryMessage = Effect.fn("GraphEmbeddingsQueryService.onMessage")(function* (
|
||||
msg: GraphEmbeddingsRequest,
|
||||
properties: Record<string, string>,
|
||||
|
|
@ -38,7 +40,7 @@ const onGraphEmbeddingsQueryMessage = Effect.fn("GraphEmbeddingsQueryService.onM
|
|||
const requestId = properties.id;
|
||||
if (requestId === undefined || requestId.length === 0) return;
|
||||
|
||||
const producer = yield* flowCtx.flow.producerEffect<GraphEmbeddingsResponse>("graph-embeddings-response");
|
||||
const producer = yield* flowCtx.flow.producerEffect(GraphEmbeddingsResponseProducer);
|
||||
const query = yield* QdrantGraphEmbeddingsQueryService;
|
||||
const user = msg.user ?? "default";
|
||||
const collection = msg.collection ?? "default";
|
||||
|
|
@ -86,7 +88,7 @@ export const makeGraphEmbeddingsQuerySpecs = (): ReadonlyArray<Spec<QdrantGraphE
|
|||
FlowResourceNotFoundError | MessagingDeliveryError,
|
||||
QdrantGraphEmbeddingsQueryService
|
||||
>("graph-embeddings-request", onGraphEmbeddingsQueryMessage),
|
||||
makeProducerSpec<GraphEmbeddingsResponse>("graph-embeddings-response"),
|
||||
GraphEmbeddingsResponseProducer,
|
||||
];
|
||||
|
||||
export type GraphEmbeddingsQueryService = FlowProcessorRuntime<QdrantGraphEmbeddingsQueryService>;
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ import {
|
|||
type FalkorDBQueryConfig,
|
||||
} from "./falkordb.js";
|
||||
|
||||
const TriplesResponseProducer = makeProducerSpec<TriplesQueryResponse>("triples-response");
|
||||
|
||||
const onTriplesQueryMessage = Effect.fn("TriplesQueryService.onMessage")(function* (
|
||||
msg: TriplesQueryRequest,
|
||||
properties: Record<string, string>,
|
||||
|
|
@ -38,7 +40,7 @@ const onTriplesQueryMessage = Effect.fn("TriplesQueryService.onMessage")(functio
|
|||
const requestId = properties.id;
|
||||
if (requestId === undefined || requestId.length === 0) return;
|
||||
|
||||
const producer = yield* flowCtx.flow.producerEffect<TriplesQueryResponse>("triples-response");
|
||||
const producer = yield* flowCtx.flow.producerEffect(TriplesResponseProducer);
|
||||
const query = yield* FalkorDBTriplesQueryService;
|
||||
const triples = yield* query.queryTriples(
|
||||
msg.s,
|
||||
|
|
@ -72,7 +74,7 @@ export const makeTriplesQuerySpecs = (): ReadonlyArray<Spec<FalkorDBTriplesQuery
|
|||
FlowResourceNotFoundError | MessagingDeliveryError,
|
||||
FalkorDBTriplesQueryService
|
||||
>("triples-request", onTriplesQueryMessage),
|
||||
makeProducerSpec<TriplesQueryResponse>("triples-response"),
|
||||
TriplesResponseProducer,
|
||||
];
|
||||
|
||||
export type TriplesQueryService = FlowProcessorRuntime<FalkorDBTriplesQueryService>;
|
||||
|
|
|
|||
|
|
@ -40,6 +40,28 @@ import {
|
|||
type DocumentRagClients,
|
||||
} from "./document-rag.js";
|
||||
|
||||
const DocumentRagResponseProducer = makeProducerSpec<DocumentRagResponse>("document-rag-response");
|
||||
const DocumentRagLlmClient = makeRequestResponseSpec<TextCompletionRequest, TextCompletionResponse>(
|
||||
"llm",
|
||||
"text-completion-request",
|
||||
"text-completion-response",
|
||||
);
|
||||
const DocumentRagEmbeddingsClient = makeRequestResponseSpec<EmbeddingsRequest, EmbeddingsResponse>(
|
||||
"embeddings",
|
||||
"embeddings-request",
|
||||
"embeddings-response",
|
||||
);
|
||||
const DocumentRagDocEmbeddingsClient = makeRequestResponseSpec<DocumentEmbeddingsRequest, DocumentEmbeddingsResponse>(
|
||||
"doc-embeddings",
|
||||
"document-embeddings-request",
|
||||
"document-embeddings-response",
|
||||
);
|
||||
const DocumentRagPromptClient = makeRequestResponseSpec<PromptRequest, PromptResponse>(
|
||||
"prompt",
|
||||
"prompt-request",
|
||||
"prompt-response",
|
||||
);
|
||||
|
||||
const onDocumentRagRequest = Effect.fn("DocumentRagService.onRequest")(function* (
|
||||
msg: DocumentRagRequest,
|
||||
properties: Record<string, string>,
|
||||
|
|
@ -48,14 +70,14 @@ const onDocumentRagRequest = Effect.fn("DocumentRagService.onRequest")(function*
|
|||
const requestId = properties.id;
|
||||
if (requestId === undefined || requestId.length === 0) return;
|
||||
|
||||
const producer = yield* flowCtx.flow.producerEffect<DocumentRagResponse>("document-rag-response");
|
||||
const producer = yield* flowCtx.flow.producerEffect(DocumentRagResponseProducer);
|
||||
const engine = yield* DocumentRagEngine;
|
||||
|
||||
const clients: DocumentRagClients = {
|
||||
llm: yield* flowCtx.flow.requestorEffect<TextCompletionRequest, TextCompletionResponse>("llm"),
|
||||
embeddings: yield* flowCtx.flow.requestorEffect<EmbeddingsRequest, EmbeddingsResponse>("embeddings"),
|
||||
docEmbeddings: yield* flowCtx.flow.requestorEffect<DocumentEmbeddingsRequest, DocumentEmbeddingsResponse>("doc-embeddings"),
|
||||
prompt: yield* flowCtx.flow.requestorEffect<PromptRequest, PromptResponse>("prompt"),
|
||||
llm: yield* flowCtx.flow.requestorEffect(DocumentRagLlmClient),
|
||||
embeddings: yield* flowCtx.flow.requestorEffect(DocumentRagEmbeddingsClient),
|
||||
docEmbeddings: yield* flowCtx.flow.requestorEffect(DocumentRagDocEmbeddingsClient),
|
||||
prompt: yield* flowCtx.flow.requestorEffect(DocumentRagPromptClient),
|
||||
};
|
||||
|
||||
const response = yield* engine.query(
|
||||
|
|
@ -90,27 +112,11 @@ export const makeDocumentRagSpecs = (): ReadonlyArray<Spec<DocumentRagEngine>> =
|
|||
"document-rag-request",
|
||||
onDocumentRagRequest,
|
||||
),
|
||||
makeProducerSpec<DocumentRagResponse>("document-rag-response"),
|
||||
makeRequestResponseSpec<TextCompletionRequest, TextCompletionResponse>(
|
||||
"llm",
|
||||
"text-completion-request",
|
||||
"text-completion-response",
|
||||
),
|
||||
makeRequestResponseSpec<EmbeddingsRequest, EmbeddingsResponse>(
|
||||
"embeddings",
|
||||
"embeddings-request",
|
||||
"embeddings-response",
|
||||
),
|
||||
makeRequestResponseSpec<DocumentEmbeddingsRequest, DocumentEmbeddingsResponse>(
|
||||
"doc-embeddings",
|
||||
"document-embeddings-request",
|
||||
"document-embeddings-response",
|
||||
),
|
||||
makeRequestResponseSpec<PromptRequest, PromptResponse>(
|
||||
"prompt",
|
||||
"prompt-request",
|
||||
"prompt-response",
|
||||
),
|
||||
DocumentRagResponseProducer,
|
||||
DocumentRagLlmClient,
|
||||
DocumentRagEmbeddingsClient,
|
||||
DocumentRagDocEmbeddingsClient,
|
||||
DocumentRagPromptClient,
|
||||
];
|
||||
|
||||
export type DocumentRagService = FlowProcessorRuntime<DocumentRagEngine>;
|
||||
|
|
|
|||
|
|
@ -43,6 +43,33 @@ import {
|
|||
type GraphRagConfig,
|
||||
} from "./graph-rag.js";
|
||||
|
||||
const GraphRagResponseProducer = makeProducerSpec<GraphRagResponse>("graph-rag-response");
|
||||
const GraphRagLlmClient = makeRequestResponseSpec<TextCompletionRequest, TextCompletionResponse>(
|
||||
"llm",
|
||||
"text-completion-request",
|
||||
"text-completion-response",
|
||||
);
|
||||
const GraphRagEmbeddingsClient = makeRequestResponseSpec<EmbeddingsRequest, EmbeddingsResponse>(
|
||||
"embeddings",
|
||||
"embeddings-request",
|
||||
"embeddings-response",
|
||||
);
|
||||
const GraphRagGraphEmbeddingsClient = makeRequestResponseSpec<GraphEmbeddingsRequest, GraphEmbeddingsResponse>(
|
||||
"graph-embeddings",
|
||||
"graph-embeddings-request",
|
||||
"graph-embeddings-response",
|
||||
);
|
||||
const GraphRagTriplesClient = makeRequestResponseSpec<TriplesQueryRequest, TriplesQueryResponse>(
|
||||
"triples",
|
||||
"triples-request",
|
||||
"triples-response",
|
||||
);
|
||||
const GraphRagPromptClient = makeRequestResponseSpec<PromptRequest, PromptResponse>(
|
||||
"prompt",
|
||||
"prompt-request",
|
||||
"prompt-response",
|
||||
);
|
||||
|
||||
const graphRagConfigFromRequest = (msg: GraphRagRequest): GraphRagConfig => ({
|
||||
...(msg.entityLimit !== undefined ? { entityLimit: msg.entityLimit } : {}),
|
||||
...(msg.tripleLimit !== undefined ? { tripleLimit: msg.tripleLimit } : {}),
|
||||
|
|
@ -58,17 +85,17 @@ const onGraphRagRequest = Effect.fn("GraphRagService.onRequest")(function* (
|
|||
const requestId = properties.id;
|
||||
if (requestId === undefined || requestId.length === 0) return;
|
||||
|
||||
const producer = yield* flowCtx.flow.producerEffect<GraphRagResponse>("graph-rag-response");
|
||||
const producer = yield* flowCtx.flow.producerEffect(GraphRagResponseProducer);
|
||||
const engine = yield* GraphRagEngine;
|
||||
|
||||
yield* Effect.log(`[GraphRagService] Received request ${requestId}: "${msg.query?.slice(0, 60)}..." collection=${msg.collection}`);
|
||||
|
||||
const clients: GraphRagClients = {
|
||||
llm: yield* flowCtx.flow.requestorEffect<TextCompletionRequest, TextCompletionResponse>("llm"),
|
||||
embeddings: yield* flowCtx.flow.requestorEffect<EmbeddingsRequest, EmbeddingsResponse>("embeddings"),
|
||||
graphEmbeddings: yield* flowCtx.flow.requestorEffect<GraphEmbeddingsRequest, GraphEmbeddingsResponse>("graph-embeddings"),
|
||||
triples: yield* flowCtx.flow.requestorEffect<TriplesQueryRequest, TriplesQueryResponse>("triples"),
|
||||
prompt: yield* flowCtx.flow.requestorEffect<PromptRequest, PromptResponse>("prompt"),
|
||||
llm: yield* flowCtx.flow.requestorEffect(GraphRagLlmClient),
|
||||
embeddings: yield* flowCtx.flow.requestorEffect(GraphRagEmbeddingsClient),
|
||||
graphEmbeddings: yield* flowCtx.flow.requestorEffect(GraphRagGraphEmbeddingsClient),
|
||||
triples: yield* flowCtx.flow.requestorEffect(GraphRagTriplesClient),
|
||||
prompt: yield* flowCtx.flow.requestorEffect(GraphRagPromptClient),
|
||||
};
|
||||
|
||||
const result = yield* engine.query(
|
||||
|
|
@ -118,32 +145,12 @@ export const makeGraphRagSpecs = (): ReadonlyArray<Spec<GraphRagEngine>> => [
|
|||
"graph-rag-request",
|
||||
onGraphRagRequest,
|
||||
),
|
||||
makeProducerSpec<GraphRagResponse>("graph-rag-response"),
|
||||
makeRequestResponseSpec<TextCompletionRequest, TextCompletionResponse>(
|
||||
"llm",
|
||||
"text-completion-request",
|
||||
"text-completion-response",
|
||||
),
|
||||
makeRequestResponseSpec<EmbeddingsRequest, EmbeddingsResponse>(
|
||||
"embeddings",
|
||||
"embeddings-request",
|
||||
"embeddings-response",
|
||||
),
|
||||
makeRequestResponseSpec<GraphEmbeddingsRequest, GraphEmbeddingsResponse>(
|
||||
"graph-embeddings",
|
||||
"graph-embeddings-request",
|
||||
"graph-embeddings-response",
|
||||
),
|
||||
makeRequestResponseSpec<TriplesQueryRequest, TriplesQueryResponse>(
|
||||
"triples",
|
||||
"triples-request",
|
||||
"triples-response",
|
||||
),
|
||||
makeRequestResponseSpec<PromptRequest, PromptResponse>(
|
||||
"prompt",
|
||||
"prompt-request",
|
||||
"prompt-response",
|
||||
),
|
||||
GraphRagResponseProducer,
|
||||
GraphRagLlmClient,
|
||||
GraphRagEmbeddingsClient,
|
||||
GraphRagGraphEmbeddingsClient,
|
||||
GraphRagTriplesClient,
|
||||
GraphRagPromptClient,
|
||||
];
|
||||
|
||||
export type GraphRagService = FlowProcessorRuntime<GraphRagEngine>;
|
||||
|
|
|
|||
|
|
@ -42,6 +42,12 @@ type GraphEmbeddingsStoreError =
|
|||
| MessagingTimeoutError
|
||||
| QdrantGraphEmbeddingsStoreError;
|
||||
|
||||
const EmbeddingsClient = makeRequestResponseSpec<EmbeddingsRequest, EmbeddingsResponse>(
|
||||
"embeddings-client",
|
||||
"embeddings-request",
|
||||
"embeddings-response",
|
||||
);
|
||||
|
||||
const onGraphEmbeddingsStoreMessage = Effect.fn("GraphEmbeddingsStoreService.onMessage")(function* (
|
||||
msg: EntityContexts,
|
||||
_properties: Record<string, string>,
|
||||
|
|
@ -49,8 +55,7 @@ const onGraphEmbeddingsStoreMessage = Effect.fn("GraphEmbeddingsStoreService.onM
|
|||
): Effect.fn.Return<void, GraphEmbeddingsStoreError, GraphEmbeddingsStoreRequirements> {
|
||||
if (msg.entities.length === 0) return;
|
||||
|
||||
const embeddingsClient =
|
||||
yield* flowCtx.flow.requestorEffect<EmbeddingsRequest, EmbeddingsResponse>("embeddings-client");
|
||||
const embeddingsClient = yield* flowCtx.flow.requestorEffect(EmbeddingsClient);
|
||||
|
||||
const user = msg.metadata?.user ?? "default";
|
||||
const collection = msg.metadata?.collection ?? "default";
|
||||
|
|
@ -83,11 +88,7 @@ export const makeGraphEmbeddingsStoreSpecs = (): ReadonlyArray<Spec<GraphEmbeddi
|
|||
"store-graph-embeddings-input",
|
||||
onGraphEmbeddingsStoreMessage,
|
||||
),
|
||||
makeRequestResponseSpec<EmbeddingsRequest, EmbeddingsResponse>(
|
||||
"embeddings-client",
|
||||
"embeddings-request",
|
||||
"embeddings-response",
|
||||
),
|
||||
EmbeddingsClient,
|
||||
];
|
||||
|
||||
export type GraphEmbeddingsStoreService = FlowProcessorRuntime<GraphEmbeddingsStoreRequirements>;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue