mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-07-01 09:29: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
|
|
@ -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