mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-07-01 01:19:38 +02:00
Make Ollama embeddings layer effectful
This commit is contained in:
parent
32788ec0e4
commit
9d3f745fb0
3 changed files with 112 additions and 27 deletions
|
|
@ -12,15 +12,15 @@ 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 client streaming
|
Current signal counts from `ts/packages` after the 2026-06-02 Ollama
|
||||||
facade normalization slice:
|
embeddings effectful layer slice:
|
||||||
|
|
||||||
| Signal | Count |
|
| Signal | Count |
|
||||||
| --- | ---: |
|
| --- | ---: |
|
||||||
| `Effect.runPromise` | 172 |
|
| `Effect.runPromise` | 172 |
|
||||||
| `Effect.runPromiseWith` | 0 |
|
| `Effect.runPromiseWith` | 0 |
|
||||||
| `Effect.cached` | 0 |
|
| `Effect.cached` | 0 |
|
||||||
| `Layer.succeed` | 19 |
|
| `Layer.succeed` | 18 |
|
||||||
| `Map<` | 82 |
|
| `Map<` | 82 |
|
||||||
| `WebSocket` | 62 |
|
| `WebSocket` | 62 |
|
||||||
| `new Map` | 60 |
|
| `new Map` | 60 |
|
||||||
|
|
@ -32,7 +32,7 @@ facade normalization slice:
|
||||||
| `new Promise` | 10 |
|
| `new Promise` | 10 |
|
||||||
| `JSON.parse` | 4 |
|
| `JSON.parse` | 4 |
|
||||||
| `localStorage` | 8 |
|
| `localStorage` | 8 |
|
||||||
| `JSON.stringify` | 6 |
|
| `JSON.stringify` | 7 |
|
||||||
| `setTimeout` | 4 |
|
| `setTimeout` | 4 |
|
||||||
| `process.env` | 3 |
|
| `process.env` | 3 |
|
||||||
|
|
||||||
|
|
@ -119,6 +119,10 @@ Notes:
|
||||||
decode in `trustgraph-socket.ts`, uses Schema plus `effect/Predicate`
|
decode in `trustgraph-socket.ts`, uses Schema plus `effect/Predicate`
|
||||||
property narrowing for streaming payload reads, and leaves service-specific
|
property narrowing for streaming payload reads, and leaves service-specific
|
||||||
legacy completion markers only where they preserve public callback behavior.
|
legacy completion markers only where they preserve public callback behavior.
|
||||||
|
- The Ollama embeddings effectful layer slice dropped one `Layer.succeed`
|
||||||
|
match by making `OllamaEmbeddingsLive` effectful and mapping config/load
|
||||||
|
failures to `EmbeddingsError`. The `JSON.stringify` count increased by one
|
||||||
|
because the new layer test uses a JSON response fixture.
|
||||||
- `Record<string, any>` and `throwLibrarianServiceError` are now clean in
|
- `Record<string, any>` and `throwLibrarianServiceError` are now clean in
|
||||||
`ts/packages`.
|
`ts/packages`.
|
||||||
|
|
||||||
|
|
@ -211,6 +215,7 @@ Notes:
|
||||||
- `cd ts && bun run build`
|
- `cd ts && bun run build`
|
||||||
- `cd ts && bun run test`
|
- `cd ts && bun run test`
|
||||||
- `git diff --check`
|
- `git diff --check`
|
||||||
|
- `git diff --check`
|
||||||
|
|
||||||
### 2026-06-02: RAG And Agent Requestor Bridge Slice
|
### 2026-06-02: RAG And Agent Requestor Bridge Slice
|
||||||
|
|
||||||
|
|
@ -802,6 +807,32 @@ Notes:
|
||||||
- `cd ts && bun run check`
|
- `cd ts && bun run check`
|
||||||
- `cd ts && bun run build`
|
- `cd ts && bun run build`
|
||||||
- `cd ts && bun run test`
|
- `cd ts && bun run test`
|
||||||
|
- `git diff --check`
|
||||||
|
|
||||||
|
### 2026-06-02: Ollama Embeddings Effectful Layer Slice
|
||||||
|
|
||||||
|
- Status: migrated and root-verified.
|
||||||
|
- Completed:
|
||||||
|
- `ts/packages/flow/src/embeddings/ollama.ts` now exposes
|
||||||
|
`makeOllamaEmbeddingsEffect` for effectful config loading and service
|
||||||
|
construction.
|
||||||
|
- `OllamaEmbeddingsLive` now uses `Layer.effect` and maps config/load
|
||||||
|
failures into `EmbeddingsError` instead of preconstructing the service with
|
||||||
|
`Layer.succeed`.
|
||||||
|
- The direct `makeOllamaEmbeddings(config)` factory remains as a
|
||||||
|
compatibility facade, while the canonical `program` entrypoint preserves
|
||||||
|
the provider tagged error channel.
|
||||||
|
- Ollama response JSON parsing no longer uses a Promise type assertion.
|
||||||
|
- The focused embeddings tests now cover both direct factory use and the
|
||||||
|
effectful `OllamaEmbeddingsLive` layer.
|
||||||
|
- Verification:
|
||||||
|
- `bunx --bun vitest run src/__tests__/ollama-embeddings.test.ts`
|
||||||
|
- `bun run --cwd ts/packages/flow build`
|
||||||
|
- `cd ts && bun run check:tsgo`
|
||||||
|
- `bun run --cwd ts/packages/flow test`
|
||||||
|
- `cd ts && bun run check`
|
||||||
|
- `cd ts && bun run build`
|
||||||
|
- `cd ts && bun run test`
|
||||||
|
|
||||||
### 2026-06-02: FalkorDB Scoped Client Lifecycle Slice
|
### 2026-06-02: FalkorDB Scoped Client Lifecycle Slice
|
||||||
|
|
||||||
|
|
@ -913,6 +944,14 @@ Notes:
|
||||||
remaining `ts/packages` matches.
|
remaining `ts/packages` matches.
|
||||||
- Provider SDKs and storage clients should become managed resources where
|
- Provider SDKs and storage clients should become managed resources where
|
||||||
they have meaningful lifecycle.
|
they have meaningful lifecycle.
|
||||||
|
- Ollama embeddings now has an effectful canonical layer. There is no
|
||||||
|
installed Effect AI Ollama provider package, so future Ollama work should
|
||||||
|
focus on local Effect wrappers/adapters rather than provider replacement.
|
||||||
|
- Full text-completion provider swaps need parity tests first. OpenAI and
|
||||||
|
Azure currently use Chat Completions while `@effect/ai-openai` is Responses
|
||||||
|
API oriented, and no installed Azure/Mistral/Ollama Effect AI provider is
|
||||||
|
available. Anthropic is the closest direct provider swap, but must preserve
|
||||||
|
text, token counts, streaming final usage, and rate-limit mapping.
|
||||||
- FalkorDB scoped lifecycle is complete for triples query/store. Use the
|
- FalkorDB scoped lifecycle is complete for triples query/store. Use the
|
||||||
fakeable client/graph factory pattern from that slice for future storage
|
fakeable client/graph factory pattern from that slice for future storage
|
||||||
client tests.
|
client tests.
|
||||||
|
|
@ -920,8 +959,8 @@ Notes:
|
||||||
store/query modules. Qdrant still has no close/disconnect surface in the
|
store/query modules. Qdrant still has no close/disconnect surface in the
|
||||||
installed client, so do not reopen it as an `acquireRelease` close slice
|
installed client, so do not reopen it as an `acquireRelease` close slice
|
||||||
without new SDK evidence.
|
without new SDK evidence.
|
||||||
- Ollama/OpenAI-compatible/provider surfaces still need config, schema, and
|
- The next safe provider cleanup is shared text-completion stream iteration
|
||||||
provider-layer audits.
|
and assertion removal, not a direct SDK swap.
|
||||||
|
|
||||||
## Ranked Findings
|
## Ranked Findings
|
||||||
|
|
||||||
|
|
@ -929,14 +968,18 @@ Notes:
|
||||||
|
|
||||||
- TrustGraph evidence:
|
- TrustGraph evidence:
|
||||||
- `ts/packages/flow/src/model/text-completion/*.ts`
|
- `ts/packages/flow/src/model/text-completion/*.ts`
|
||||||
- `ts/packages/flow/src/embeddings/ollama.ts`
|
|
||||||
- Effect primitives:
|
- Effect primitives:
|
||||||
- `Config`, `ConfigProvider`, `Metric`, `Logger`,
|
- `Config`, `ConfigProvider`, `Metric`, `Logger`,
|
||||||
`effect/unstable/ai/LanguageModel`, `effect/unstable/ai/EmbeddingModel`,
|
`effect/unstable/ai/LanguageModel`, `effect/unstable/ai/EmbeddingModel`,
|
||||||
Effect AI OpenAI/Anthropic provider layers.
|
Effect AI OpenAI/Anthropic provider layers.
|
||||||
- Rewrite shape:
|
- Rewrite shape:
|
||||||
- Migrate provider config into Effect layers.
|
- Consolidate duplicated text-completion stream iterator plumbing and error
|
||||||
- Use Effect AI provider layers where parity is proven.
|
conversion in a shared helper.
|
||||||
|
- Remove remaining provider assertions such as the Mistral content cast using
|
||||||
|
guards or Schema-backed normalization.
|
||||||
|
- Add an Effect AI adapter layer beside the current `LlmProvider` contract
|
||||||
|
before flipping any public provider interface.
|
||||||
|
- Use Effect AI provider layers only where parity is proven.
|
||||||
- Keep OpenAI-compatible/Azure-compatible behavior behind parity tests
|
- Keep OpenAI-compatible/Azure-compatible behavior behind parity tests
|
||||||
because current code uses chat-completions style APIs while the Effect
|
because current code uses chat-completions style APIs while the Effect
|
||||||
OpenAI language model is Responses API backed.
|
OpenAI language model is Responses API backed.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { describe, expect, it } from "@effect/vitest";
|
import { describe, expect, it } from "@effect/vitest";
|
||||||
import { Effect } from "effect";
|
import { Effect } from "effect";
|
||||||
import { makeOllamaEmbeddings } from "../embeddings/ollama.js";
|
import { Embeddings } from "@trustgraph/base";
|
||||||
|
import { makeOllamaEmbeddings, OllamaEmbeddingsLive } from "../embeddings/ollama.js";
|
||||||
|
|
||||||
describe("Ollama embeddings provider", () => {
|
describe("Ollama embeddings provider", () => {
|
||||||
it.effect(
|
it.effect(
|
||||||
|
|
@ -79,4 +80,32 @@ describe("Ollama embeddings provider", () => {
|
||||||
expect(error.message).toContain("Ollama embeddings request failed (404): not found");
|
expect(error.message).toContain("Ollama embeddings request failed (404): not found");
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
it.effect(
|
||||||
|
"provides embeddings through the effectful layer",
|
||||||
|
Effect.fnUntraced(function* () {
|
||||||
|
const fetchImpl = (() =>
|
||||||
|
Promise.resolve(
|
||||||
|
new Response(JSON.stringify({ embeddings: [[4, 5, 6]] }), {
|
||||||
|
status: 200,
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
}),
|
||||||
|
)) as typeof fetch;
|
||||||
|
|
||||||
|
const vectors = yield* Effect.gen(function* () {
|
||||||
|
const embeddings = yield* Embeddings;
|
||||||
|
return yield* embeddings.embed(["beta"]);
|
||||||
|
}).pipe(
|
||||||
|
Effect.provide(
|
||||||
|
OllamaEmbeddingsLive({
|
||||||
|
id: "embeddings",
|
||||||
|
ollamaHost: "http://ollama.local",
|
||||||
|
fetch: fetchImpl,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(vectors).toEqual([[4, 5, 6]]);
|
||||||
|
}),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -71,14 +71,14 @@ const loadOllamaEmbeddingsConfig = Effect.fn("OllamaEmbeddings.loadConfig")(func
|
||||||
} satisfies ResolvedOllamaEmbeddingsConfig;
|
} satisfies ResolvedOllamaEmbeddingsConfig;
|
||||||
});
|
});
|
||||||
|
|
||||||
export function makeOllamaEmbeddings(config: OllamaEmbeddingsConfig): EmbeddingsServiceShape {
|
const responseJson = (response: Response): Promise<unknown> =>
|
||||||
const {
|
response.json();
|
||||||
defaultModel,
|
|
||||||
ollamaHost,
|
|
||||||
fetchImpl,
|
|
||||||
} = Effect.runSync(loadOllamaEmbeddingsConfig(config)) satisfies ResolvedOllamaEmbeddingsConfig;
|
|
||||||
|
|
||||||
return {
|
const makeOllamaEmbeddingsFromConfig = ({
|
||||||
|
defaultModel,
|
||||||
|
ollamaHost,
|
||||||
|
fetchImpl,
|
||||||
|
}: ResolvedOllamaEmbeddingsConfig): EmbeddingsServiceShape => ({
|
||||||
embed: Effect.fn("OllamaEmbeddings.embed")((texts: ReadonlyArray<string>, model?: string) => {
|
embed: Effect.fn("OllamaEmbeddings.embed")((texts: ReadonlyArray<string>, model?: string) => {
|
||||||
if (texts.length === 0) {
|
if (texts.length === 0) {
|
||||||
return Effect.succeed([]);
|
return Effect.succeed([]);
|
||||||
|
|
@ -117,7 +117,7 @@ export function makeOllamaEmbeddings(config: OllamaEmbeddingsConfig): Embeddings
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = yield* Effect.tryPromise({
|
const data = yield* Effect.tryPromise({
|
||||||
try: () => response.json() as Promise<unknown>,
|
try: () => responseJson(response),
|
||||||
catch: (error) => ollamaEmbeddingsError("ollama.response-json", error),
|
catch: (error) => ollamaEmbeddingsError("ollama.response-json", error),
|
||||||
});
|
});
|
||||||
const decoded = yield* S.decodeUnknownEffect(OllamaEmbedResponse)(data).pipe(
|
const decoded = yield* S.decodeUnknownEffect(OllamaEmbedResponse)(data).pipe(
|
||||||
|
|
@ -126,13 +126,30 @@ export function makeOllamaEmbeddings(config: OllamaEmbeddingsConfig): Embeddings
|
||||||
return Array.from(decoded.embeddings, (vector) => Array.from(vector));
|
return Array.from(decoded.embeddings, (vector) => Array.from(vector));
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
};
|
});
|
||||||
|
|
||||||
|
export const makeOllamaEmbeddingsEffect = Effect.fn("makeOllamaEmbeddingsEffect")(function* (
|
||||||
|
config: OllamaEmbeddingsConfig,
|
||||||
|
) {
|
||||||
|
const resolved = yield* loadOllamaEmbeddingsConfig(config).pipe(
|
||||||
|
Effect.mapError((cause) => ollamaEmbeddingsError("ollama.load-config", cause)),
|
||||||
|
);
|
||||||
|
yield* Effect.log(
|
||||||
|
`[OllamaEmbeddings] Initialized (host=${resolved.ollamaHost}, model=${resolved.defaultModel})`,
|
||||||
|
);
|
||||||
|
return makeOllamaEmbeddingsFromConfig(resolved);
|
||||||
|
});
|
||||||
|
|
||||||
|
export function makeOllamaEmbeddings(config: OllamaEmbeddingsConfig): EmbeddingsServiceShape {
|
||||||
|
return Effect.runSync(makeOllamaEmbeddingsEffect(config));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function OllamaEmbeddingsLive(config: OllamaEmbeddingsConfig): Layer.Layer<Embeddings> {
|
export function OllamaEmbeddingsLive(config: OllamaEmbeddingsConfig): Layer.Layer<Embeddings, EmbeddingsError> {
|
||||||
return Layer.succeed(
|
return Layer.effect(
|
||||||
Embeddings,
|
Embeddings,
|
||||||
Embeddings.of(makeOllamaEmbeddings(config)),
|
makeOllamaEmbeddingsEffect(config).pipe(
|
||||||
|
Effect.map((embeddings) => Embeddings.of(embeddings)),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -140,16 +157,12 @@ export type OllamaEmbeddingsProcessor = ReturnType<typeof makeOllamaEmbeddingsPr
|
||||||
|
|
||||||
export function makeOllamaEmbeddingsProcessor(config: OllamaEmbeddingsConfig) {
|
export function makeOllamaEmbeddingsProcessor(config: OllamaEmbeddingsConfig) {
|
||||||
const embeddings = makeOllamaEmbeddings(config);
|
const embeddings = makeOllamaEmbeddings(config);
|
||||||
const resolved = Effect.runSync(loadOllamaEmbeddingsConfig(config)) satisfies ResolvedOllamaEmbeddingsConfig;
|
|
||||||
Effect.runSync(Effect.log(
|
|
||||||
`[OllamaEmbeddings] Initialized (host=${resolved.ollamaHost}, model=${resolved.defaultModel})`,
|
|
||||||
));
|
|
||||||
return makeEmbeddingsService(config, embeddings);
|
return makeEmbeddingsService(config, embeddings);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const OllamaEmbeddingsProcessor = makeOllamaEmbeddingsProcessor;
|
export const OllamaEmbeddingsProcessor = makeOllamaEmbeddingsProcessor;
|
||||||
|
|
||||||
export const program = makeFlowProcessorProgram<OllamaEmbeddingsConfig, never, Embeddings>({
|
export const program = makeFlowProcessorProgram<OllamaEmbeddingsConfig, EmbeddingsError, Embeddings>({
|
||||||
id: "embeddings",
|
id: "embeddings",
|
||||||
specs: () => makeEmbeddingsSpecs(),
|
specs: () => makeEmbeddingsSpecs(),
|
||||||
layer: (config) => OllamaEmbeddingsLive(config),
|
layer: (config) => OllamaEmbeddingsLive(config),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue