mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-06-30 17:09:38 +02:00
Make text completion provider layers effectful
This commit is contained in:
parent
36f629b341
commit
fe4f5777c9
9 changed files with 393 additions and 116 deletions
|
|
@ -12,15 +12,15 @@ 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 client streaming
|
||||
callback Effect boundary slice:
|
||||
Current signal counts from `ts/packages` after the 2026-06-02 text completion
|
||||
provider effectful layer slice:
|
||||
|
||||
| Signal | Count |
|
||||
| --- | ---: |
|
||||
| `Effect.runPromise` | 172 |
|
||||
| `Effect.runPromiseWith` | 0 |
|
||||
| `Effect.cached` | 0 |
|
||||
| `Layer.succeed` | 18 |
|
||||
| `Layer.succeed` | 12 |
|
||||
| `Map<` | 88 |
|
||||
| `WebSocket` | 74 |
|
||||
| `new Map` | 60 |
|
||||
|
|
@ -151,6 +151,12 @@ Notes:
|
|||
centralizing legacy callback request failures in `runLegacyStreamingRequest`.
|
||||
The public callback facades still return/ignore Promises where required, but
|
||||
failure mapping now uses `Effect.tryPromise` and `Effect.catch`.
|
||||
- The text completion provider effectful layer slice dropped six
|
||||
`Layer.succeed` matches by moving OpenAI, OpenAI-compatible, Azure OpenAI,
|
||||
Claude, Mistral, and Ollama processor layers onto
|
||||
`makeTextCompletionLayer(makeXProviderEffect(config))`. SDK construction and
|
||||
config lookup now live in Effect; sync `makeXProvider` exports remain
|
||||
compatibility facades.
|
||||
- `Record<string, any>` and `throwLibrarianServiceError` are now clean in
|
||||
`ts/packages`.
|
||||
|
||||
|
|
@ -1065,6 +1071,33 @@ Notes:
|
|||
- `cd ts && bun run test`
|
||||
- `git diff --check`
|
||||
|
||||
### 2026-06-02: Text Completion Provider Effectful Layer Slice
|
||||
|
||||
- Status: migrated and root-verified.
|
||||
- Completed:
|
||||
- Added shared `makeTextCompletionLayer` for constructing `Llm` from an
|
||||
effectful `LlmProvider`.
|
||||
- Added `makeOpenAIProviderEffect`,
|
||||
`makeOpenAICompatibleProviderEffect`, `makeAzureOpenAIProviderEffect`,
|
||||
`makeClaudeProviderEffect`, `makeMistralProviderEffect`, and
|
||||
`makeOllamaProviderEffect`.
|
||||
- Processor `program.layer` definitions now use `Layer.effect` via the
|
||||
shared helper instead of constructing providers inside `Layer.succeed`.
|
||||
- Provider object assembly is split into pure `makeXProviderFromClient`
|
||||
helpers so Promise-returning provider methods remain external
|
||||
compatibility facades and do not trigger `effect(runEffectInsideEffect)`.
|
||||
- Added tests for explicit provider config, shared `Llm` layer provisioning,
|
||||
and tagged missing-config errors.
|
||||
- Verification:
|
||||
- `bunx --bun vitest run src/__tests__/text-completion-providers.test.ts src/__tests__/text-completion-common.test.ts`
|
||||
- `bun run --cwd ts/packages/flow build`
|
||||
- `bun run --cwd ts/packages/flow test`
|
||||
- `cd ts && bun run check:tsgo`
|
||||
- `cd ts && bun run check`
|
||||
- `cd ts && bun run build`
|
||||
- `cd ts && bun run test`
|
||||
- `git diff --check`
|
||||
|
||||
## Subagent Findings To Preserve
|
||||
|
||||
- MCP/workbench:
|
||||
|
|
@ -1124,7 +1157,9 @@ Notes:
|
|||
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.
|
||||
text, token counts, streaming final usage, and rate-limit mapping. The
|
||||
local provider layer-construction cleanup is complete; remaining provider
|
||||
work is adapter/parity work, not `Layer.succeed` cleanup.
|
||||
- FalkorDB scoped lifecycle is complete for triples query/store. Use the
|
||||
fakeable client/graph factory pattern from that slice for future storage
|
||||
client tests.
|
||||
|
|
@ -1138,13 +1173,53 @@ Notes:
|
|||
|
||||
## Ranked Findings
|
||||
|
||||
### P2: Provider Layer And Effect AI Cleanup
|
||||
### P0: Broker Backend Effect-Native Runtime
|
||||
|
||||
- TrustGraph evidence:
|
||||
- `ts/packages/base/src/backend/types.ts`
|
||||
- `ts/packages/base/src/backend/nats.ts`
|
||||
- `ts/packages/base/src/messaging/runtime.ts`
|
||||
- `ts/packages/base/src/processor/flow-processor.ts`
|
||||
- Effect primitives:
|
||||
- `Layer`, `Scope`, `Stream`, `Schedule`, `Queue`,
|
||||
`Effect.acquireRelease`, and `Effect.tryPromise`.
|
||||
- Rewrite shape:
|
||||
- Introduce an Effect-native broker service/layer with scoped NATS
|
||||
acquisition and stream/schedule-based consumer loops.
|
||||
- Keep `PubSubBackend` as the compatibility adapter boundary; Effect native
|
||||
`PubSub` remains in-process only.
|
||||
- Tests:
|
||||
- Fake backend ack/nak/backoff/stop tests, NATS close finalizer tests, and
|
||||
config-push stream tests.
|
||||
|
||||
### P1: Gateway Dispatcher Ownership And Serialization
|
||||
|
||||
- TrustGraph evidence:
|
||||
- `ts/packages/flow/src/gateway/dispatch/manager.ts`
|
||||
- `ts/packages/flow/src/gateway/dispatch/serialize.ts`
|
||||
- `ts/packages/flow/src/gateway/server.ts`
|
||||
- Effect primitives:
|
||||
- `Layer`, `Scope`, `Effect.acquireUseRelease`, `Effect.try`, `Result.try`,
|
||||
and typed dispatch errors.
|
||||
- Rewrite shape:
|
||||
- Track whether the dispatcher owns `PubSubBackend` so injected backends are
|
||||
not closed.
|
||||
- Use `Effect.acquireUseRelease` for one-shot gateway producers so producer
|
||||
close runs even when send fails.
|
||||
- Replace throwing gateway serialization helpers with Effect/Result-returning
|
||||
helpers mapped to typed dispatch or wire errors.
|
||||
- Longer term, move `createGateway` to a scoped `createGatewayEffect` while
|
||||
keeping Fastify route `Effect.runPromise` calls as host boundaries.
|
||||
- Tests:
|
||||
- Injected pubsub is not closed, one-shot producer closes on send failure,
|
||||
and malformed gateway payloads return typed dispatch errors.
|
||||
|
||||
### P2: Effect AI Provider Adapter Cleanup
|
||||
|
||||
- TrustGraph evidence:
|
||||
- `ts/packages/flow/src/model/text-completion/*.ts`
|
||||
- Effect primitives:
|
||||
- `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.
|
||||
- Rewrite shape:
|
||||
- Add an Effect AI adapter layer beside the current `LlmProvider` contract
|
||||
|
|
@ -1190,8 +1265,10 @@ Notes:
|
|||
|
||||
## Recommended PR Order
|
||||
|
||||
1. Provider layer and Effect AI cleanup.
|
||||
2. MCP parity/deletion decision and workbench platform polish.
|
||||
1. Gateway dispatcher ownership and serialization.
|
||||
2. Broker backend Effect-native runtime.
|
||||
3. Effect AI provider adapter cleanup.
|
||||
4. MCP parity/deletion decision and workbench platform polish.
|
||||
|
||||
## No-Op Rules
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
import { describe, expect, it } from "@effect/vitest";
|
||||
import { Llm } from "@trustgraph/base";
|
||||
import { ConfigProvider, Effect } from "effect";
|
||||
import { makeAzureOpenAIProviderEffect } from "../model/text-completion/azure-openai.js";
|
||||
import { makeClaudeProviderEffect } from "../model/text-completion/claude.js";
|
||||
import { makeTextCompletionLayer } from "../model/text-completion/common.js";
|
||||
import { makeMistralProviderEffect } from "../model/text-completion/mistral.js";
|
||||
import { makeOllamaProviderEffect } from "../model/text-completion/ollama.js";
|
||||
import { makeOpenAICompatibleProviderEffect } from "../model/text-completion/openai-compatible.js";
|
||||
import { makeOpenAIProviderEffect } from "../model/text-completion/openai.js";
|
||||
|
||||
const emptyConfig = ConfigProvider.layer(
|
||||
ConfigProvider.fromEnv({ env: {} }),
|
||||
);
|
||||
|
||||
describe("text completion provider construction", () => {
|
||||
it.effect(
|
||||
"constructs providers from explicit config through Effect",
|
||||
Effect.fnUntraced(function* () {
|
||||
const providers = yield* Effect.all([
|
||||
makeOpenAIProviderEffect({ id: "openai", apiKey: "test-key" }),
|
||||
makeOpenAICompatibleProviderEffect({
|
||||
id: "openai-compatible",
|
||||
baseUrl: "http://localhost:1234/v1",
|
||||
}),
|
||||
makeAzureOpenAIProviderEffect({
|
||||
id: "azure-openai",
|
||||
apiKey: "test-key",
|
||||
endpoint: "https://example.openai.azure.com",
|
||||
}),
|
||||
makeClaudeProviderEffect({ id: "claude", apiKey: "test-key" }),
|
||||
makeMistralProviderEffect({ id: "mistral", apiKey: "test-key" }),
|
||||
makeOllamaProviderEffect({
|
||||
id: "ollama",
|
||||
ollamaUrl: "http://localhost:11434",
|
||||
}),
|
||||
]).pipe(
|
||||
Effect.provide(emptyConfig),
|
||||
);
|
||||
|
||||
expect(providers.map((provider) => provider.supportsStreaming())).toEqual([
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
]);
|
||||
}),
|
||||
);
|
||||
|
||||
it.effect(
|
||||
"provides Llm through the shared Effect layer helper",
|
||||
Effect.fnUntraced(function* () {
|
||||
const llm = yield* Effect.gen(function* () {
|
||||
return yield* Llm;
|
||||
}).pipe(
|
||||
Effect.provide(
|
||||
makeTextCompletionLayer(makeOllamaProviderEffect({
|
||||
id: "ollama",
|
||||
ollamaUrl: "http://localhost:11434",
|
||||
})),
|
||||
),
|
||||
Effect.provide(emptyConfig),
|
||||
);
|
||||
|
||||
expect(llm.supportsStreaming()).toBe(true);
|
||||
}),
|
||||
);
|
||||
|
||||
it.effect(
|
||||
"fails missing required config as a tagged config error",
|
||||
Effect.fnUntraced(function* () {
|
||||
const error = yield* makeOpenAIProviderEffect({ id: "openai" }).pipe(
|
||||
Effect.flip,
|
||||
Effect.provide(emptyConfig),
|
||||
);
|
||||
|
||||
expect(error).toMatchObject({
|
||||
_tag: "TextCompletionConfigError",
|
||||
provider: "OpenAI",
|
||||
key: "OPENAI_TOKEN",
|
||||
});
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
|
@ -11,11 +11,10 @@
|
|||
import { AzureOpenAI } from "openai";
|
||||
import { NodeRuntime } from "@effect/platform-node";
|
||||
import {
|
||||
Llm,
|
||||
makeLlmService,
|
||||
makeFlowProcessorProgram,
|
||||
makeLlmServiceShape,
|
||||
makeLlmSpecs,
|
||||
type Llm,
|
||||
type LlmProvider,
|
||||
type ProcessorConfig,
|
||||
type LlmResult,
|
||||
|
|
@ -24,11 +23,13 @@ import {
|
|||
import { Effect, Layer, ManagedRuntime, Stream } from "effect";
|
||||
import {
|
||||
llmStreamPart,
|
||||
makeTextCompletionLayer,
|
||||
optionalStringConfig,
|
||||
providerStatusError,
|
||||
requiredString,
|
||||
streamTextCompletionChunks,
|
||||
toAsyncGenerator,
|
||||
type TextCompletionConfigError,
|
||||
type TextCompletionRuntimeError,
|
||||
} from "./common.ts";
|
||||
|
||||
|
|
@ -79,24 +80,21 @@ const loadAzureOpenAIConfig = Effect.fn("loadAzureOpenAIConfig")(function* (
|
|||
apiKey,
|
||||
endpoint,
|
||||
apiVersion,
|
||||
};
|
||||
} satisfies ResolvedAzureOpenAIConfig;
|
||||
});
|
||||
|
||||
const mapAzureOpenAIError = (error: unknown): TextCompletionRuntimeError =>
|
||||
providerStatusError("AzureOpenAI", error);
|
||||
|
||||
export function makeAzureOpenAIProvider(config: AzureOpenAIProcessorConfig): LlmProvider {
|
||||
const makeAzureOpenAIProviderFromClient = (
|
||||
resolved: ResolvedAzureOpenAIConfig,
|
||||
client: AzureOpenAI,
|
||||
): LlmProvider => {
|
||||
const {
|
||||
defaultModel,
|
||||
defaultTemperature,
|
||||
maxOutput,
|
||||
apiKey,
|
||||
endpoint,
|
||||
apiVersion,
|
||||
} = Effect.runSync(loadAzureOpenAIConfig(config)) satisfies ResolvedAzureOpenAIConfig;
|
||||
const client = new AzureOpenAI({ apiKey, apiVersion, endpoint });
|
||||
|
||||
Effect.runSync(Effect.log("[AzureOpenAI] LLM service initialized"));
|
||||
} = resolved;
|
||||
|
||||
return {
|
||||
generateContent: (
|
||||
|
|
@ -174,9 +172,31 @@ export function makeAzureOpenAIProvider(config: AzureOpenAIProcessorConfig): Llm
|
|||
|
||||
return toAsyncGenerator(Stream.toAsyncIterable(stream), mapAzureOpenAIError);
|
||||
},
|
||||
};
|
||||
} satisfies LlmProvider;
|
||||
};
|
||||
|
||||
export function makeAzureOpenAIProvider(config: AzureOpenAIProcessorConfig): LlmProvider {
|
||||
return Effect.runSync(makeAzureOpenAIProviderEffect(config));
|
||||
}
|
||||
|
||||
export const makeAzureOpenAIProviderEffect = Effect.fn("makeAzureOpenAIProvider")(function*(
|
||||
config: AzureOpenAIProcessorConfig,
|
||||
) {
|
||||
const resolved = yield* loadAzureOpenAIConfig(config);
|
||||
const client = yield* Effect.try({
|
||||
try: () =>
|
||||
new AzureOpenAI({
|
||||
apiKey: resolved.apiKey,
|
||||
apiVersion: resolved.apiVersion,
|
||||
endpoint: resolved.endpoint,
|
||||
}),
|
||||
catch: mapAzureOpenAIError,
|
||||
});
|
||||
|
||||
yield* Effect.log("[AzureOpenAI] LLM service initialized");
|
||||
return makeAzureOpenAIProviderFromClient(resolved, client);
|
||||
});
|
||||
|
||||
export type AzureOpenAIProcessor = ReturnType<typeof makeAzureOpenAIProcessor>;
|
||||
|
||||
export function makeAzureOpenAIProcessor(
|
||||
|
|
@ -187,14 +207,14 @@ export function makeAzureOpenAIProcessor(
|
|||
|
||||
export const AzureOpenAIProcessor = makeAzureOpenAIProcessor;
|
||||
|
||||
export const program = makeFlowProcessorProgram<ProcessorConfig, never, Llm>({
|
||||
export const program = makeFlowProcessorProgram<
|
||||
AzureOpenAIProcessorConfig,
|
||||
TextCompletionConfigError | TextCompletionRuntimeError,
|
||||
Llm
|
||||
>({
|
||||
id: "text-completion",
|
||||
specs: () => makeLlmSpecs(),
|
||||
layer: (config) =>
|
||||
Layer.succeed(
|
||||
Llm,
|
||||
Llm.of(makeLlmServiceShape(makeAzureOpenAIProvider(config))),
|
||||
),
|
||||
layer: (config) => makeTextCompletionLayer(makeAzureOpenAIProviderEffect(config)),
|
||||
});
|
||||
|
||||
const azureOpenAITextCompletionRuntime = ManagedRuntime.make(Layer.empty);
|
||||
|
|
|
|||
|
|
@ -7,11 +7,10 @@
|
|||
import Anthropic from "@anthropic-ai/sdk";
|
||||
import { NodeRuntime } from "@effect/platform-node";
|
||||
import {
|
||||
Llm,
|
||||
makeLlmService,
|
||||
makeFlowProcessorProgram,
|
||||
makeLlmServiceShape,
|
||||
makeLlmSpecs,
|
||||
type Llm,
|
||||
type LlmProvider,
|
||||
type ProcessorConfig,
|
||||
type LlmResult,
|
||||
|
|
@ -20,11 +19,13 @@ import {
|
|||
import { Effect, Layer, ManagedRuntime, Stream } from "effect";
|
||||
import {
|
||||
llmStreamPart,
|
||||
makeTextCompletionLayer,
|
||||
optionalStringConfig,
|
||||
providerStatusError,
|
||||
requiredString,
|
||||
streamTextCompletionChunks,
|
||||
toAsyncGenerator,
|
||||
type TextCompletionConfigError,
|
||||
type TextCompletionRuntimeError,
|
||||
} from "./common.ts";
|
||||
|
||||
|
|
@ -61,17 +62,15 @@ const loadClaudeConfig = Effect.fn("loadClaudeConfig")(function*(config: ClaudeP
|
|||
const mapClaudeError = (error: unknown): TextCompletionRuntimeError =>
|
||||
providerStatusError("Claude", error);
|
||||
|
||||
export function makeClaudeProvider(config: ClaudeProcessorConfig): LlmProvider {
|
||||
const makeClaudeProviderFromClient = (
|
||||
resolved: ResolvedClaudeConfig,
|
||||
client: Anthropic,
|
||||
): LlmProvider => {
|
||||
const {
|
||||
defaultModel,
|
||||
defaultTemperature,
|
||||
maxOutput,
|
||||
apiKey,
|
||||
} = Effect.runSync(loadClaudeConfig(config)) satisfies ResolvedClaudeConfig;
|
||||
|
||||
const client = new Anthropic({ apiKey });
|
||||
|
||||
Effect.runSync(Effect.log("[Claude] LLM service initialized"));
|
||||
} = resolved;
|
||||
|
||||
return {
|
||||
generateContent: (
|
||||
|
|
@ -161,9 +160,26 @@ export function makeClaudeProvider(config: ClaudeProcessorConfig): LlmProvider {
|
|||
|
||||
return toAsyncGenerator(Stream.toAsyncIterable(stream), mapClaudeError);
|
||||
},
|
||||
};
|
||||
} satisfies LlmProvider;
|
||||
};
|
||||
|
||||
export function makeClaudeProvider(config: ClaudeProcessorConfig): LlmProvider {
|
||||
return Effect.runSync(makeClaudeProviderEffect(config));
|
||||
}
|
||||
|
||||
export const makeClaudeProviderEffect = Effect.fn("makeClaudeProvider")(function*(
|
||||
config: ClaudeProcessorConfig,
|
||||
) {
|
||||
const resolved = yield* loadClaudeConfig(config);
|
||||
const client = yield* Effect.try({
|
||||
try: () => new Anthropic({ apiKey: resolved.apiKey }),
|
||||
catch: mapClaudeError,
|
||||
});
|
||||
|
||||
yield* Effect.log("[Claude] LLM service initialized");
|
||||
return makeClaudeProviderFromClient(resolved, client);
|
||||
});
|
||||
|
||||
export type ClaudeProcessor = ReturnType<typeof makeClaudeProcessor>;
|
||||
|
||||
export function makeClaudeProcessor(
|
||||
|
|
@ -174,14 +190,14 @@ export function makeClaudeProcessor(
|
|||
|
||||
export const ClaudeProcessor = makeClaudeProcessor;
|
||||
|
||||
export const program = makeFlowProcessorProgram<ProcessorConfig, never, Llm>({
|
||||
export const program = makeFlowProcessorProgram<
|
||||
ClaudeProcessorConfig,
|
||||
TextCompletionConfigError | TextCompletionRuntimeError,
|
||||
Llm
|
||||
>({
|
||||
id: "text-completion",
|
||||
specs: () => makeLlmSpecs(),
|
||||
layer: (config) =>
|
||||
Layer.succeed(
|
||||
Llm,
|
||||
Llm.of(makeLlmServiceShape(makeClaudeProvider(config))),
|
||||
),
|
||||
layer: (config) => makeTextCompletionLayer(makeClaudeProviderEffect(config)),
|
||||
});
|
||||
|
||||
const claudeTextCompletionRuntime = ManagedRuntime.make(Layer.empty);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
import {
|
||||
Llm,
|
||||
TooManyRequestsError,
|
||||
errorMessage,
|
||||
makeLlmServiceShape,
|
||||
type LlmChunk,
|
||||
type LlmProvider,
|
||||
} from "@trustgraph/base";
|
||||
import { Config, Effect, Ref, Result, Stream } from "effect";
|
||||
import { Config, Effect, Layer, Ref, Result, Stream } from "effect";
|
||||
import * as O from "effect/Option";
|
||||
import * as Predicate from "effect/Predicate";
|
||||
import * as S from "effect/Schema";
|
||||
|
|
@ -29,6 +32,17 @@ export type TextCompletionRuntimeError =
|
|||
| TextCompletionProviderError
|
||||
| TooManyRequestsError;
|
||||
|
||||
export const makeTextCompletionLayer = <E, R>(
|
||||
provider: Effect.Effect<LlmProvider, E, R>,
|
||||
): Layer.Layer<Llm, E, R> =>
|
||||
Layer.effect(Llm)(
|
||||
provider.pipe(
|
||||
Effect.map((resolvedProvider) =>
|
||||
Llm.of(makeLlmServiceShape(resolvedProvider))
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
type StreamingTokenTotals = {
|
||||
readonly inToken: number;
|
||||
readonly outToken: number;
|
||||
|
|
|
|||
|
|
@ -9,11 +9,10 @@
|
|||
import { Mistral } from "@mistralai/mistralai";
|
||||
import { NodeRuntime } from "@effect/platform-node";
|
||||
import {
|
||||
Llm,
|
||||
makeLlmService,
|
||||
makeFlowProcessorProgram,
|
||||
makeLlmServiceShape,
|
||||
makeLlmSpecs,
|
||||
type Llm,
|
||||
type LlmProvider,
|
||||
type ProcessorConfig,
|
||||
type LlmResult,
|
||||
|
|
@ -22,12 +21,14 @@ import {
|
|||
import { Effect, Layer, ManagedRuntime, Stream } from "effect";
|
||||
import {
|
||||
llmStreamPart,
|
||||
makeTextCompletionLayer,
|
||||
optionalStringConfig,
|
||||
providerStatusError,
|
||||
requiredString,
|
||||
streamTextCompletionChunks,
|
||||
textFromContent,
|
||||
toAsyncGenerator,
|
||||
type TextCompletionConfigError,
|
||||
type TextCompletionRuntimeError,
|
||||
} from "./common.ts";
|
||||
|
||||
|
|
@ -67,17 +68,15 @@ const loadMistralConfig = Effect.fn("loadMistralConfig")(function*(config: Mistr
|
|||
const mapMistralError = (error: unknown): TextCompletionRuntimeError =>
|
||||
providerStatusError("Mistral", error);
|
||||
|
||||
export function makeMistralProvider(config: MistralProcessorConfig): LlmProvider {
|
||||
const makeMistralProviderFromClient = (
|
||||
resolved: ResolvedMistralConfig,
|
||||
client: Mistral,
|
||||
): LlmProvider => {
|
||||
const {
|
||||
defaultModel,
|
||||
defaultTemperature,
|
||||
maxOutput,
|
||||
apiKey,
|
||||
} = Effect.runSync(loadMistralConfig(config)) satisfies ResolvedMistralConfig;
|
||||
|
||||
const client = new Mistral({ apiKey });
|
||||
|
||||
Effect.runSync(Effect.log("[Mistral] LLM service initialized"));
|
||||
} = resolved;
|
||||
|
||||
return {
|
||||
generateContent: (
|
||||
|
|
@ -153,9 +152,26 @@ export function makeMistralProvider(config: MistralProcessorConfig): LlmProvider
|
|||
|
||||
return toAsyncGenerator(Stream.toAsyncIterable(stream), mapMistralError);
|
||||
},
|
||||
};
|
||||
} satisfies LlmProvider;
|
||||
};
|
||||
|
||||
export function makeMistralProvider(config: MistralProcessorConfig): LlmProvider {
|
||||
return Effect.runSync(makeMistralProviderEffect(config));
|
||||
}
|
||||
|
||||
export const makeMistralProviderEffect = Effect.fn("makeMistralProvider")(function*(
|
||||
config: MistralProcessorConfig,
|
||||
) {
|
||||
const resolved = yield* loadMistralConfig(config);
|
||||
const client = yield* Effect.try({
|
||||
try: () => new Mistral({ apiKey: resolved.apiKey }),
|
||||
catch: mapMistralError,
|
||||
});
|
||||
|
||||
yield* Effect.log("[Mistral] LLM service initialized");
|
||||
return makeMistralProviderFromClient(resolved, client);
|
||||
});
|
||||
|
||||
export type MistralProcessor = ReturnType<typeof makeMistralProcessor>;
|
||||
|
||||
export function makeMistralProcessor(
|
||||
|
|
@ -166,14 +182,14 @@ export function makeMistralProcessor(
|
|||
|
||||
export const MistralProcessor = makeMistralProcessor;
|
||||
|
||||
export const program = makeFlowProcessorProgram<ProcessorConfig, never, Llm>({
|
||||
export const program = makeFlowProcessorProgram<
|
||||
MistralProcessorConfig,
|
||||
TextCompletionConfigError | TextCompletionRuntimeError,
|
||||
Llm
|
||||
>({
|
||||
id: "text-completion",
|
||||
specs: () => makeLlmSpecs(),
|
||||
layer: (config) =>
|
||||
Layer.succeed(
|
||||
Llm,
|
||||
Llm.of(makeLlmServiceShape(makeMistralProvider(config))),
|
||||
),
|
||||
layer: (config) => makeTextCompletionLayer(makeMistralProviderEffect(config)),
|
||||
});
|
||||
|
||||
const mistralTextCompletionRuntime = ManagedRuntime.make(Layer.empty);
|
||||
|
|
|
|||
|
|
@ -9,11 +9,10 @@
|
|||
import { Ollama } from "ollama";
|
||||
import { NodeRuntime } from "@effect/platform-node";
|
||||
import {
|
||||
Llm,
|
||||
makeLlmService,
|
||||
makeFlowProcessorProgram,
|
||||
makeLlmServiceShape,
|
||||
makeLlmSpecs,
|
||||
type Llm,
|
||||
type LlmProvider,
|
||||
type ProcessorConfig,
|
||||
type LlmResult,
|
||||
|
|
@ -22,10 +21,12 @@ import {
|
|||
import { Effect, Layer, ManagedRuntime, Stream } from "effect";
|
||||
import {
|
||||
llmStreamPart,
|
||||
makeTextCompletionLayer,
|
||||
optionalStringConfig,
|
||||
providerRuntimeError,
|
||||
streamTextCompletionChunks,
|
||||
toAsyncGenerator,
|
||||
type TextCompletionConfigError,
|
||||
type TextCompletionRuntimeError,
|
||||
} from "./common.ts";
|
||||
|
||||
|
|
@ -55,14 +56,11 @@ const loadOllamaConfig = Effect.fn("loadOllamaConfig")(function*(config: OllamaP
|
|||
const mapOllamaError = (error: unknown): TextCompletionRuntimeError =>
|
||||
providerRuntimeError("Ollama", error);
|
||||
|
||||
export function makeOllamaProvider(config: OllamaProcessorConfig): LlmProvider {
|
||||
const { defaultModel, host } = Effect.runSync(loadOllamaConfig(config)) satisfies ResolvedOllamaConfig;
|
||||
|
||||
const client = new Ollama({ host });
|
||||
|
||||
Effect.runSync(Effect.log(
|
||||
`[Ollama] LLM service initialized (host=${host}, model=${defaultModel})`,
|
||||
));
|
||||
const makeOllamaProviderFromClient = (
|
||||
resolved: ResolvedOllamaConfig,
|
||||
client: Ollama,
|
||||
): LlmProvider => {
|
||||
const { defaultModel } = resolved;
|
||||
|
||||
return {
|
||||
generateContent: (
|
||||
|
|
@ -130,9 +128,28 @@ export function makeOllamaProvider(config: OllamaProcessorConfig): LlmProvider {
|
|||
|
||||
return toAsyncGenerator(Stream.toAsyncIterable(stream), mapOllamaError);
|
||||
},
|
||||
};
|
||||
} satisfies LlmProvider;
|
||||
};
|
||||
|
||||
export function makeOllamaProvider(config: OllamaProcessorConfig): LlmProvider {
|
||||
return Effect.runSync(makeOllamaProviderEffect(config));
|
||||
}
|
||||
|
||||
export const makeOllamaProviderEffect = Effect.fn("makeOllamaProvider")(function*(
|
||||
config: OllamaProcessorConfig,
|
||||
) {
|
||||
const resolved = yield* loadOllamaConfig(config);
|
||||
const client = yield* Effect.try({
|
||||
try: () => new Ollama({ host: resolved.host }),
|
||||
catch: mapOllamaError,
|
||||
});
|
||||
|
||||
yield* Effect.log(
|
||||
`[Ollama] LLM service initialized (host=${resolved.host}, model=${resolved.defaultModel})`,
|
||||
);
|
||||
return makeOllamaProviderFromClient(resolved, client);
|
||||
});
|
||||
|
||||
export type OllamaProcessor = ReturnType<typeof makeOllamaProcessor>;
|
||||
|
||||
export function makeOllamaProcessor(
|
||||
|
|
@ -143,14 +160,14 @@ export function makeOllamaProcessor(
|
|||
|
||||
export const OllamaProcessor = makeOllamaProcessor;
|
||||
|
||||
export const program = makeFlowProcessorProgram<ProcessorConfig, never, Llm>({
|
||||
export const program = makeFlowProcessorProgram<
|
||||
OllamaProcessorConfig,
|
||||
TextCompletionConfigError | TextCompletionRuntimeError,
|
||||
Llm
|
||||
>({
|
||||
id: "text-completion",
|
||||
specs: () => makeLlmSpecs(),
|
||||
layer: (config) =>
|
||||
Layer.succeed(
|
||||
Llm,
|
||||
Llm.of(makeLlmServiceShape(makeOllamaProvider(config))),
|
||||
),
|
||||
layer: (config) => makeTextCompletionLayer(makeOllamaProviderEffect(config)),
|
||||
});
|
||||
|
||||
const ollamaTextCompletionRuntime = ManagedRuntime.make(Layer.empty);
|
||||
|
|
|
|||
|
|
@ -12,11 +12,10 @@
|
|||
import OpenAI from "openai";
|
||||
import { NodeRuntime } from "@effect/platform-node";
|
||||
import {
|
||||
Llm,
|
||||
makeLlmService,
|
||||
makeFlowProcessorProgram,
|
||||
makeLlmServiceShape,
|
||||
makeLlmSpecs,
|
||||
type Llm,
|
||||
type LlmProvider,
|
||||
type ProcessorConfig,
|
||||
type LlmResult,
|
||||
|
|
@ -25,11 +24,13 @@ import {
|
|||
import { Effect, Layer, ManagedRuntime, Stream } from "effect";
|
||||
import {
|
||||
llmStreamPart,
|
||||
makeTextCompletionLayer,
|
||||
optionalStringConfig,
|
||||
providerStatusError,
|
||||
requiredString,
|
||||
streamTextCompletionChunks,
|
||||
toAsyncGenerator,
|
||||
type TextCompletionConfigError,
|
||||
type TextCompletionRuntimeError,
|
||||
} from "./common.ts";
|
||||
|
||||
|
|
@ -75,20 +76,15 @@ const loadOpenAICompatibleConfig = Effect.fn("loadOpenAICompatibleConfig")(funct
|
|||
const mapOpenAICompatibleError = (error: unknown): TextCompletionRuntimeError =>
|
||||
providerStatusError("OpenAI-Compatible", error);
|
||||
|
||||
export function makeOpenAICompatibleProvider(
|
||||
config: OpenAICompatibleProcessorConfig,
|
||||
): LlmProvider {
|
||||
const makeOpenAICompatibleProviderFromClient = (
|
||||
resolved: ResolvedOpenAICompatibleConfig,
|
||||
client: OpenAI,
|
||||
): LlmProvider => {
|
||||
const {
|
||||
defaultModel,
|
||||
defaultTemperature,
|
||||
maxOutput,
|
||||
apiKey,
|
||||
baseURL,
|
||||
} = Effect.runSync(loadOpenAICompatibleConfig(config)) satisfies ResolvedOpenAICompatibleConfig;
|
||||
|
||||
const client = new OpenAI({ baseURL, apiKey });
|
||||
|
||||
Effect.runSync(Effect.log("[OpenAI-Compatible] LLM service initialized"));
|
||||
} = resolved;
|
||||
|
||||
return {
|
||||
generateContent: (
|
||||
|
|
@ -165,9 +161,28 @@ export function makeOpenAICompatibleProvider(
|
|||
|
||||
return toAsyncGenerator(Stream.toAsyncIterable(stream), mapOpenAICompatibleError);
|
||||
},
|
||||
};
|
||||
} satisfies LlmProvider;
|
||||
};
|
||||
|
||||
export function makeOpenAICompatibleProvider(
|
||||
config: OpenAICompatibleProcessorConfig,
|
||||
): LlmProvider {
|
||||
return Effect.runSync(makeOpenAICompatibleProviderEffect(config));
|
||||
}
|
||||
|
||||
export const makeOpenAICompatibleProviderEffect = Effect.fn("makeOpenAICompatibleProvider")(function*(
|
||||
config: OpenAICompatibleProcessorConfig,
|
||||
) {
|
||||
const resolved = yield* loadOpenAICompatibleConfig(config);
|
||||
const client = yield* Effect.try({
|
||||
try: () => new OpenAI({ baseURL: resolved.baseURL, apiKey: resolved.apiKey }),
|
||||
catch: mapOpenAICompatibleError,
|
||||
});
|
||||
|
||||
yield* Effect.log("[OpenAI-Compatible] LLM service initialized");
|
||||
return makeOpenAICompatibleProviderFromClient(resolved, client);
|
||||
});
|
||||
|
||||
export type OpenAICompatibleProcessor = ReturnType<typeof makeOpenAICompatibleProcessor>;
|
||||
|
||||
export function makeOpenAICompatibleProcessor(
|
||||
|
|
@ -178,14 +193,14 @@ export function makeOpenAICompatibleProcessor(
|
|||
|
||||
export const OpenAICompatibleProcessor = makeOpenAICompatibleProcessor;
|
||||
|
||||
export const program = makeFlowProcessorProgram<ProcessorConfig, never, Llm>({
|
||||
export const program = makeFlowProcessorProgram<
|
||||
OpenAICompatibleProcessorConfig,
|
||||
TextCompletionConfigError | TextCompletionRuntimeError,
|
||||
Llm
|
||||
>({
|
||||
id: "text-completion",
|
||||
specs: () => makeLlmSpecs(),
|
||||
layer: (config) =>
|
||||
Layer.succeed(
|
||||
Llm,
|
||||
Llm.of(makeLlmServiceShape(makeOpenAICompatibleProvider(config))),
|
||||
),
|
||||
layer: (config) => makeTextCompletionLayer(makeOpenAICompatibleProviderEffect(config)),
|
||||
});
|
||||
|
||||
const openAICompatibleTextCompletionRuntime = ManagedRuntime.make(Layer.empty);
|
||||
|
|
|
|||
|
|
@ -7,11 +7,10 @@
|
|||
import OpenAI from "openai";
|
||||
import { NodeRuntime } from "@effect/platform-node";
|
||||
import {
|
||||
Llm,
|
||||
makeLlmService,
|
||||
makeFlowProcessorProgram,
|
||||
makeLlmServiceShape,
|
||||
makeLlmSpecs,
|
||||
type Llm,
|
||||
type LlmProvider,
|
||||
type ProcessorConfig,
|
||||
type LlmResult,
|
||||
|
|
@ -20,11 +19,13 @@ import {
|
|||
import { Effect, Layer, ManagedRuntime, Stream } from "effect";
|
||||
import {
|
||||
llmStreamPart,
|
||||
makeTextCompletionLayer,
|
||||
optionalStringConfig,
|
||||
providerStatusError,
|
||||
requiredString,
|
||||
streamTextCompletionChunks,
|
||||
toAsyncGenerator,
|
||||
type TextCompletionConfigError,
|
||||
type TextCompletionRuntimeError,
|
||||
} from "./common.ts";
|
||||
|
||||
|
|
@ -64,21 +65,15 @@ const loadOpenAIConfig = Effect.fn("loadOpenAIConfig")(function*(config: OpenAIP
|
|||
const mapOpenAIError = (error: unknown): TextCompletionRuntimeError =>
|
||||
providerStatusError("OpenAI", error);
|
||||
|
||||
export function makeOpenAIProvider(config: OpenAIProcessorConfig): LlmProvider {
|
||||
const makeOpenAIProviderFromClient = (
|
||||
resolved: ResolvedOpenAIConfig,
|
||||
client: OpenAI,
|
||||
): LlmProvider => {
|
||||
const {
|
||||
defaultModel,
|
||||
defaultTemperature,
|
||||
maxOutput,
|
||||
apiKey,
|
||||
baseURL,
|
||||
} = Effect.runSync(loadOpenAIConfig(config)) satisfies ResolvedOpenAIConfig;
|
||||
|
||||
const client = new OpenAI({
|
||||
apiKey,
|
||||
baseURL,
|
||||
});
|
||||
|
||||
Effect.runSync(Effect.log("[OpenAI] LLM service initialized"));
|
||||
} = resolved;
|
||||
|
||||
return {
|
||||
generateContent: (
|
||||
|
|
@ -156,9 +151,30 @@ export function makeOpenAIProvider(config: OpenAIProcessorConfig): LlmProvider {
|
|||
|
||||
return toAsyncGenerator(Stream.toAsyncIterable(stream), mapOpenAIError);
|
||||
},
|
||||
};
|
||||
} satisfies LlmProvider;
|
||||
};
|
||||
|
||||
export function makeOpenAIProvider(config: OpenAIProcessorConfig): LlmProvider {
|
||||
return Effect.runSync(makeOpenAIProviderEffect(config));
|
||||
}
|
||||
|
||||
export const makeOpenAIProviderEffect = Effect.fn("makeOpenAIProvider")(function*(
|
||||
config: OpenAIProcessorConfig,
|
||||
) {
|
||||
const resolved = yield* loadOpenAIConfig(config);
|
||||
const client = yield* Effect.try({
|
||||
try: () =>
|
||||
new OpenAI({
|
||||
apiKey: resolved.apiKey,
|
||||
baseURL: resolved.baseURL,
|
||||
}),
|
||||
catch: mapOpenAIError,
|
||||
});
|
||||
|
||||
yield* Effect.log("[OpenAI] LLM service initialized");
|
||||
return makeOpenAIProviderFromClient(resolved, client);
|
||||
});
|
||||
|
||||
export type OpenAIProcessor = ReturnType<typeof makeOpenAIProcessor>;
|
||||
|
||||
export function makeOpenAIProcessor(
|
||||
|
|
@ -169,14 +185,14 @@ export function makeOpenAIProcessor(
|
|||
|
||||
export const OpenAIProcessor = makeOpenAIProcessor;
|
||||
|
||||
export const program = makeFlowProcessorProgram<ProcessorConfig, never, Llm>({
|
||||
export const program = makeFlowProcessorProgram<
|
||||
OpenAIProcessorConfig,
|
||||
TextCompletionConfigError | TextCompletionRuntimeError,
|
||||
Llm
|
||||
>({
|
||||
id: "text-completion",
|
||||
specs: () => makeLlmSpecs(),
|
||||
layer: (config) =>
|
||||
Layer.succeed(
|
||||
Llm,
|
||||
Llm.of(makeLlmServiceShape(makeOpenAIProvider(config))),
|
||||
),
|
||||
layer: (config) => makeTextCompletionLayer(makeOpenAIProviderEffect(config)),
|
||||
});
|
||||
|
||||
const openAITextCompletionRuntime = ManagedRuntime.make(Layer.empty);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue