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