mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-07-02 02:58:10 +02:00
refactor(ts): make port effect native
This commit is contained in:
parent
2868ced2d3
commit
b6759e75df
113 changed files with 4140 additions and 4554 deletions
|
|
@ -18,9 +18,8 @@ import {
|
|||
type LlmProvider,
|
||||
type ProcessorConfig,
|
||||
type LlmResult,
|
||||
type LlmChunk,
|
||||
} from "@trustgraph/base";
|
||||
import { Effect, Layer, ManagedRuntime, Stream } from "effect";
|
||||
import { Effect, Stream } from "effect";
|
||||
import {
|
||||
llmStreamPart,
|
||||
makeTextCompletionLayer,
|
||||
|
|
@ -28,7 +27,6 @@ import {
|
|||
providerStatusError,
|
||||
requiredString,
|
||||
streamTextCompletionChunks,
|
||||
toAsyncGenerator,
|
||||
type TextCompletionConfigError,
|
||||
type TextCompletionRuntimeError,
|
||||
} from "./common.ts";
|
||||
|
|
@ -89,7 +87,7 @@ const mapAzureOpenAIError = (error: unknown): TextCompletionRuntimeError =>
|
|||
const makeAzureOpenAIProviderFromClient = (
|
||||
resolved: ResolvedAzureOpenAIConfig,
|
||||
client: AzureOpenAI,
|
||||
): LlmProvider => {
|
||||
): LlmProvider<TextCompletionRuntimeError> => {
|
||||
const {
|
||||
defaultModel,
|
||||
defaultTemperature,
|
||||
|
|
@ -102,31 +100,29 @@ const makeAzureOpenAIProviderFromClient = (
|
|||
prompt: string,
|
||||
model?: string,
|
||||
temperature?: number,
|
||||
): Promise<LlmResult> => {
|
||||
) => {
|
||||
const modelName = model ?? defaultModel;
|
||||
const temp = temperature ?? defaultTemperature;
|
||||
|
||||
return Effect.runPromise(
|
||||
Effect.tryPromise({
|
||||
try: () =>
|
||||
client.chat.completions.create({
|
||||
model: modelName,
|
||||
messages: [
|
||||
{ role: "system", content: system },
|
||||
{ role: "user", content: prompt },
|
||||
],
|
||||
temperature: temp,
|
||||
max_completion_tokens: maxOutput,
|
||||
}),
|
||||
catch: mapAzureOpenAIError,
|
||||
}).pipe(
|
||||
Effect.map((resp): LlmResult => ({
|
||||
text: resp.choices[0].message.content ?? "",
|
||||
inToken: resp.usage?.prompt_tokens ?? 0,
|
||||
outToken: resp.usage?.completion_tokens ?? 0,
|
||||
return Effect.tryPromise({
|
||||
try: () =>
|
||||
client.chat.completions.create({
|
||||
model: modelName,
|
||||
})),
|
||||
),
|
||||
messages: [
|
||||
{ role: "system", content: system },
|
||||
{ role: "user", content: prompt },
|
||||
],
|
||||
temperature: temp,
|
||||
max_completion_tokens: maxOutput,
|
||||
}),
|
||||
catch: mapAzureOpenAIError,
|
||||
}).pipe(
|
||||
Effect.map((resp): LlmResult => ({
|
||||
text: resp.choices[0].message.content ?? "",
|
||||
inToken: resp.usage?.prompt_tokens ?? 0,
|
||||
outToken: resp.usage?.completion_tokens ?? 0,
|
||||
model: modelName,
|
||||
})),
|
||||
);
|
||||
},
|
||||
supportsStreaming: () => true,
|
||||
|
|
@ -135,11 +131,11 @@ const makeAzureOpenAIProviderFromClient = (
|
|||
prompt: string,
|
||||
model?: string,
|
||||
temperature?: number,
|
||||
): AsyncGenerator<LlmChunk> => {
|
||||
) => {
|
||||
const modelName = model ?? defaultModel;
|
||||
const temp = temperature ?? defaultTemperature;
|
||||
|
||||
const stream = Stream.fromEffect(
|
||||
return Stream.fromEffect(
|
||||
Effect.tryPromise({
|
||||
try: () =>
|
||||
client.chat.completions.create({
|
||||
|
|
@ -169,13 +165,13 @@ const makeAzureOpenAIProviderFromClient = (
|
|||
})
|
||||
),
|
||||
);
|
||||
|
||||
return toAsyncGenerator(Stream.toAsyncIterable(stream), mapAzureOpenAIError);
|
||||
},
|
||||
} satisfies LlmProvider;
|
||||
} satisfies LlmProvider<TextCompletionRuntimeError>;
|
||||
};
|
||||
|
||||
export function makeAzureOpenAIProvider(config: AzureOpenAIProcessorConfig): LlmProvider {
|
||||
export function makeAzureOpenAIProvider(
|
||||
config: AzureOpenAIProcessorConfig,
|
||||
): LlmProvider<TextCompletionRuntimeError> {
|
||||
return Effect.runSync(makeAzureOpenAIProviderEffect(config));
|
||||
}
|
||||
|
||||
|
|
@ -217,12 +213,6 @@ export const program = makeFlowProcessorProgram<
|
|||
layer: (config) => makeTextCompletionLayer(makeAzureOpenAIProviderEffect(config)),
|
||||
});
|
||||
|
||||
const azureOpenAITextCompletionRuntime = ManagedRuntime.make(Layer.empty);
|
||||
|
||||
export function run(): Promise<void> {
|
||||
return azureOpenAITextCompletionRuntime.runPromise(program);
|
||||
}
|
||||
|
||||
export function runMain(): void {
|
||||
NodeRuntime.runMain(program);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import {
|
|||
type LlmProvider,
|
||||
type ProcessorConfig,
|
||||
} from "@trustgraph/base";
|
||||
import { Effect, Layer, ManagedRuntime, Redacted } from "effect";
|
||||
import { Effect, Layer, Redacted } from "effect";
|
||||
import { FetchHttpClient } from "effect/unstable/http";
|
||||
import {
|
||||
makeLanguageModelProvider,
|
||||
|
|
@ -55,30 +55,31 @@ const loadClaudeConfig = Effect.fn("loadClaudeConfig")(function* (config: Claude
|
|||
} satisfies ResolvedClaudeConfig;
|
||||
});
|
||||
|
||||
const makeClaudeRuntime = (apiKey: string) =>
|
||||
ManagedRuntime.make(
|
||||
AnthropicClient.layer({
|
||||
apiKey: Redacted.make(apiKey),
|
||||
}).pipe(
|
||||
Layer.provide(FetchHttpClient.layer),
|
||||
),
|
||||
const makeClaudeLayer = (apiKey: string) =>
|
||||
AnthropicClient.layer({
|
||||
apiKey: Redacted.make(apiKey),
|
||||
}).pipe(
|
||||
Layer.provide(FetchHttpClient.layer),
|
||||
);
|
||||
|
||||
export function makeClaudeProvider(config: ClaudeProcessorConfig): LlmProvider {
|
||||
return Effect.runSync(makeClaudeProviderEffect(config));
|
||||
export function makeClaudeProvider(
|
||||
config: ClaudeProcessorConfig,
|
||||
): LlmProvider<TextCompletionRuntimeError> {
|
||||
return Effect.runSync(Effect.scoped(makeClaudeProviderEffect(config)));
|
||||
}
|
||||
|
||||
export const makeClaudeProviderEffect = Effect.fn("makeClaudeProvider")(function* (
|
||||
config: ClaudeProcessorConfig,
|
||||
) {
|
||||
const resolved = yield* loadClaudeConfig(config);
|
||||
const context = yield* Layer.build(makeClaudeLayer(resolved.apiKey));
|
||||
|
||||
yield* Effect.log("[Claude] LLM service initialized");
|
||||
return makeLanguageModelProvider({
|
||||
provider: "Claude",
|
||||
defaultModel: resolved.defaultModel,
|
||||
defaultTemperature: resolved.defaultTemperature,
|
||||
runtime: makeClaudeRuntime(resolved.apiKey),
|
||||
context,
|
||||
makeLanguageModel: ({ model, temperature }) =>
|
||||
AnthropicLanguageModel.make({
|
||||
model,
|
||||
|
|
@ -110,12 +111,6 @@ export const program = makeFlowProcessorProgram<
|
|||
layer: (config) => makeTextCompletionLayer(makeClaudeProviderEffect(config)),
|
||||
});
|
||||
|
||||
const claudeTextCompletionRuntime = ManagedRuntime.make(Layer.empty);
|
||||
|
||||
export function run(): Promise<void> {
|
||||
return claudeTextCompletionRuntime.runPromise(program);
|
||||
}
|
||||
|
||||
export function runMain(): void {
|
||||
NodeRuntime.runMain(program);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,10 +7,11 @@ import {
|
|||
type LlmResult,
|
||||
type LlmProvider,
|
||||
} from "@trustgraph/base";
|
||||
import { Config, Effect, Layer, ManagedRuntime, Match, Ref, Result, Stream } from "effect";
|
||||
import { Config, Context, Effect, Layer, Match, Ref, Result, Stream } from "effect";
|
||||
import * as O from "effect/Option";
|
||||
import * as Predicate from "effect/Predicate";
|
||||
import * as S from "effect/Schema";
|
||||
import type * as Scope from "effect/Scope";
|
||||
import { AiError, LanguageModel, Prompt, Response } from "effect/unstable/ai";
|
||||
|
||||
export class TextCompletionConfigError extends S.TaggedErrorClass<TextCompletionConfigError>()(
|
||||
|
|
@ -43,15 +44,15 @@ export interface LanguageModelProviderOptions<Requirements> {
|
|||
readonly provider: string;
|
||||
readonly defaultModel: string;
|
||||
readonly defaultTemperature: number;
|
||||
readonly runtime: ManagedRuntime.ManagedRuntime<Requirements, TextCompletionRuntimeError>;
|
||||
readonly context: Context.Context<Requirements>;
|
||||
readonly makeLanguageModel: (
|
||||
request: LanguageModelProviderRequest,
|
||||
) => Effect.Effect<LanguageModel.Service, TextCompletionRuntimeError, Requirements>;
|
||||
}
|
||||
|
||||
export const makeTextCompletionLayer = <E, R>(
|
||||
provider: Effect.Effect<LlmProvider, E, R>,
|
||||
): Layer.Layer<Llm, E, R> =>
|
||||
export const makeTextCompletionLayer = <ProviderError, E, R>(
|
||||
provider: Effect.Effect<LlmProvider<ProviderError>, E, R>,
|
||||
): Layer.Layer<Llm, E, Exclude<R, Scope.Scope>> =>
|
||||
Layer.effect(Llm)(
|
||||
provider.pipe(
|
||||
Effect.map((resolvedProvider) =>
|
||||
|
|
@ -279,39 +280,25 @@ const languageModelStreamChunk = (
|
|||
Match.orElse(() => Effect.succeed(Result.fail(undefined))),
|
||||
);
|
||||
|
||||
const runLanguageModelStream = <RuntimeRequirements, StreamRequirements extends RuntimeRequirements>(
|
||||
runtime: ManagedRuntime.ManagedRuntime<RuntimeRequirements, TextCompletionRuntimeError>,
|
||||
stream: Stream.Stream<LlmChunk, TextCompletionRuntimeError, StreamRequirements>,
|
||||
): AsyncIterable<LlmChunk> => ({
|
||||
[Symbol.asyncIterator]: () => {
|
||||
const iterator = runtime.context().then((context) =>
|
||||
Stream.toAsyncIterableWith(stream, context)[Symbol.asyncIterator]()
|
||||
);
|
||||
return {
|
||||
next: () => iterator.then((current) => current.next()),
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export const makeLanguageModelProvider = <Requirements>(
|
||||
options: LanguageModelProviderOptions<Requirements>,
|
||||
): LlmProvider => ({
|
||||
): LlmProvider<TextCompletionRuntimeError> => ({
|
||||
generateContent: (system, prompt, model, temperature) => {
|
||||
const modelName = model ?? options.defaultModel;
|
||||
const temp = temperature ?? options.defaultTemperature;
|
||||
return options.runtime.runPromise(
|
||||
Effect.gen(function* () {
|
||||
const languageModel = yield* options.makeLanguageModel({
|
||||
model: modelName,
|
||||
temperature: temp,
|
||||
});
|
||||
const response = yield* languageModel.generateText({
|
||||
prompt: languageModelPrompt(system, prompt),
|
||||
}).pipe(
|
||||
Effect.mapError((error) => effectAiProviderError(options.provider, error)),
|
||||
);
|
||||
return languageModelResult(response, modelName);
|
||||
}),
|
||||
return Effect.gen(function* () {
|
||||
const languageModel = yield* options.makeLanguageModel({
|
||||
model: modelName,
|
||||
temperature: temp,
|
||||
});
|
||||
const response = yield* languageModel.generateText({
|
||||
prompt: languageModelPrompt(system, prompt),
|
||||
}).pipe(
|
||||
Effect.mapError((error) => effectAiProviderError(options.provider, error)),
|
||||
);
|
||||
return languageModelResult(response, modelName);
|
||||
}).pipe(
|
||||
Effect.provideContext(options.context),
|
||||
);
|
||||
},
|
||||
supportsStreaming: () => true,
|
||||
|
|
@ -333,30 +320,9 @@ export const makeLanguageModelProvider = <Requirements>(
|
|||
),
|
||||
);
|
||||
}),
|
||||
).pipe(
|
||||
Stream.provideContext(options.context),
|
||||
);
|
||||
return toAsyncGenerator(runLanguageModelStream(options.runtime, stream), (error) =>
|
||||
effectAiProviderError(options.provider, error)
|
||||
);
|
||||
return stream;
|
||||
},
|
||||
});
|
||||
|
||||
export const toAsyncGenerator = (
|
||||
iterable: AsyncIterable<LlmChunk>,
|
||||
mapError: (error: unknown) => TextCompletionRuntimeError,
|
||||
): AsyncGenerator<LlmChunk> => {
|
||||
const iterator = iterable[Symbol.asyncIterator]();
|
||||
let generator: AsyncGenerator<LlmChunk>;
|
||||
generator = {
|
||||
next: (value?: unknown) => iterator.next(value),
|
||||
return: (value?: unknown) =>
|
||||
iterator.return === undefined
|
||||
? Promise.resolve({ done: true, value })
|
||||
: iterator.return(value),
|
||||
throw: (error?: unknown) =>
|
||||
iterator.throw === undefined
|
||||
? Promise.reject(mapError(error))
|
||||
: iterator.throw(error),
|
||||
[Symbol.asyncIterator]: () => generator,
|
||||
};
|
||||
return generator;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -16,9 +16,8 @@ import {
|
|||
type LlmProvider,
|
||||
type ProcessorConfig,
|
||||
type LlmResult,
|
||||
type LlmChunk,
|
||||
} from "@trustgraph/base";
|
||||
import { Effect, Layer, ManagedRuntime, Stream } from "effect";
|
||||
import { Effect, Stream } from "effect";
|
||||
import {
|
||||
llmStreamPart,
|
||||
makeTextCompletionLayer,
|
||||
|
|
@ -27,7 +26,6 @@ import {
|
|||
requiredString,
|
||||
streamTextCompletionChunks,
|
||||
textFromContent,
|
||||
toAsyncGenerator,
|
||||
type TextCompletionConfigError,
|
||||
type TextCompletionRuntimeError,
|
||||
} from "./common.ts";
|
||||
|
|
@ -71,7 +69,7 @@ const mapMistralError = (error: unknown): TextCompletionRuntimeError =>
|
|||
const makeMistralProviderFromClient = (
|
||||
resolved: ResolvedMistralConfig,
|
||||
client: Mistral,
|
||||
): LlmProvider => {
|
||||
): LlmProvider<TextCompletionRuntimeError> => {
|
||||
const {
|
||||
defaultModel,
|
||||
defaultTemperature,
|
||||
|
|
@ -84,31 +82,29 @@ const makeMistralProviderFromClient = (
|
|||
prompt: string,
|
||||
model?: string,
|
||||
temperature?: number,
|
||||
): Promise<LlmResult> => {
|
||||
) => {
|
||||
const modelName = model ?? defaultModel;
|
||||
const temp = temperature ?? defaultTemperature;
|
||||
|
||||
return Effect.runPromise(
|
||||
Effect.tryPromise({
|
||||
try: () =>
|
||||
client.chat.complete({
|
||||
model: modelName,
|
||||
messages: [
|
||||
{ role: "system", content: system },
|
||||
{ role: "user", content: prompt },
|
||||
],
|
||||
temperature: temp,
|
||||
maxTokens: maxOutput,
|
||||
}),
|
||||
catch: mapMistralError,
|
||||
}).pipe(
|
||||
Effect.map((resp): LlmResult => ({
|
||||
text: textFromContent(resp.choices?.[0]?.message?.content),
|
||||
inToken: resp.usage?.promptTokens ?? 0,
|
||||
outToken: resp.usage?.completionTokens ?? 0,
|
||||
return Effect.tryPromise({
|
||||
try: () =>
|
||||
client.chat.complete({
|
||||
model: modelName,
|
||||
})),
|
||||
),
|
||||
messages: [
|
||||
{ role: "system", content: system },
|
||||
{ role: "user", content: prompt },
|
||||
],
|
||||
temperature: temp,
|
||||
maxTokens: maxOutput,
|
||||
}),
|
||||
catch: mapMistralError,
|
||||
}).pipe(
|
||||
Effect.map((resp): LlmResult => ({
|
||||
text: textFromContent(resp.choices?.[0]?.message?.content),
|
||||
inToken: resp.usage?.promptTokens ?? 0,
|
||||
outToken: resp.usage?.completionTokens ?? 0,
|
||||
model: modelName,
|
||||
})),
|
||||
);
|
||||
},
|
||||
supportsStreaming: () => true,
|
||||
|
|
@ -117,11 +113,11 @@ const makeMistralProviderFromClient = (
|
|||
prompt: string,
|
||||
model?: string,
|
||||
temperature?: number,
|
||||
): AsyncGenerator<LlmChunk> => {
|
||||
) => {
|
||||
const modelName = model ?? defaultModel;
|
||||
const temp = temperature ?? defaultTemperature;
|
||||
|
||||
const stream = Stream.fromEffect(
|
||||
return Stream.fromEffect(
|
||||
Effect.tryPromise({
|
||||
try: () =>
|
||||
client.chat.stream({
|
||||
|
|
@ -149,13 +145,13 @@ const makeMistralProviderFromClient = (
|
|||
})
|
||||
),
|
||||
);
|
||||
|
||||
return toAsyncGenerator(Stream.toAsyncIterable(stream), mapMistralError);
|
||||
},
|
||||
} satisfies LlmProvider;
|
||||
} satisfies LlmProvider<TextCompletionRuntimeError>;
|
||||
};
|
||||
|
||||
export function makeMistralProvider(config: MistralProcessorConfig): LlmProvider {
|
||||
export function makeMistralProvider(
|
||||
config: MistralProcessorConfig,
|
||||
): LlmProvider<TextCompletionRuntimeError> {
|
||||
return Effect.runSync(makeMistralProviderEffect(config));
|
||||
}
|
||||
|
||||
|
|
@ -192,12 +188,6 @@ export const program = makeFlowProcessorProgram<
|
|||
layer: (config) => makeTextCompletionLayer(makeMistralProviderEffect(config)),
|
||||
});
|
||||
|
||||
const mistralTextCompletionRuntime = ManagedRuntime.make(Layer.empty);
|
||||
|
||||
export function run(): Promise<void> {
|
||||
return mistralTextCompletionRuntime.runPromise(program);
|
||||
}
|
||||
|
||||
export function runMain(): void {
|
||||
NodeRuntime.runMain(program);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,16 +16,14 @@ import {
|
|||
type LlmProvider,
|
||||
type ProcessorConfig,
|
||||
type LlmResult,
|
||||
type LlmChunk,
|
||||
} from "@trustgraph/base";
|
||||
import { Effect, Layer, ManagedRuntime, Stream } from "effect";
|
||||
import { Effect, Stream } from "effect";
|
||||
import {
|
||||
llmStreamPart,
|
||||
makeTextCompletionLayer,
|
||||
optionalStringConfig,
|
||||
providerRuntimeError,
|
||||
streamTextCompletionChunks,
|
||||
toAsyncGenerator,
|
||||
type TextCompletionConfigError,
|
||||
type TextCompletionRuntimeError,
|
||||
} from "./common.ts";
|
||||
|
|
@ -59,7 +57,7 @@ const mapOllamaError = (error: unknown): TextCompletionRuntimeError =>
|
|||
const makeOllamaProviderFromClient = (
|
||||
resolved: ResolvedOllamaConfig,
|
||||
client: Ollama,
|
||||
): LlmProvider => {
|
||||
): LlmProvider<TextCompletionRuntimeError> => {
|
||||
const { defaultModel } = resolved;
|
||||
|
||||
return {
|
||||
|
|
@ -68,27 +66,25 @@ const makeOllamaProviderFromClient = (
|
|||
prompt: string,
|
||||
model?: string,
|
||||
_temperature?: number,
|
||||
): Promise<LlmResult> => {
|
||||
) => {
|
||||
const modelName = model ?? defaultModel;
|
||||
const fullPrompt = system + "\n\n" + prompt;
|
||||
|
||||
return Effect.runPromise(
|
||||
Effect.tryPromise({
|
||||
try: () =>
|
||||
client.generate({
|
||||
model: modelName,
|
||||
prompt: fullPrompt,
|
||||
stream: false,
|
||||
}),
|
||||
catch: mapOllamaError,
|
||||
}).pipe(
|
||||
Effect.map((resp): LlmResult => ({
|
||||
text: resp.response,
|
||||
inToken: resp.prompt_eval_count ?? 0,
|
||||
outToken: resp.eval_count ?? 0,
|
||||
return Effect.tryPromise({
|
||||
try: () =>
|
||||
client.generate({
|
||||
model: modelName,
|
||||
})),
|
||||
),
|
||||
prompt: fullPrompt,
|
||||
stream: false,
|
||||
}),
|
||||
catch: mapOllamaError,
|
||||
}).pipe(
|
||||
Effect.map((resp): LlmResult => ({
|
||||
text: resp.response,
|
||||
inToken: resp.prompt_eval_count ?? 0,
|
||||
outToken: resp.eval_count ?? 0,
|
||||
model: modelName,
|
||||
})),
|
||||
);
|
||||
},
|
||||
supportsStreaming: () => true,
|
||||
|
|
@ -97,11 +93,11 @@ const makeOllamaProviderFromClient = (
|
|||
prompt: string,
|
||||
model?: string,
|
||||
_temperature?: number,
|
||||
): AsyncGenerator<LlmChunk> => {
|
||||
) => {
|
||||
const modelName = model ?? defaultModel;
|
||||
const fullPrompt = system + "\n\n" + prompt;
|
||||
|
||||
const stream = Stream.fromEffect(
|
||||
return Stream.fromEffect(
|
||||
Effect.tryPromise({
|
||||
try: () =>
|
||||
client.generate({
|
||||
|
|
@ -125,13 +121,13 @@ const makeOllamaProviderFromClient = (
|
|||
})
|
||||
),
|
||||
);
|
||||
|
||||
return toAsyncGenerator(Stream.toAsyncIterable(stream), mapOllamaError);
|
||||
},
|
||||
} satisfies LlmProvider;
|
||||
} satisfies LlmProvider<TextCompletionRuntimeError>;
|
||||
};
|
||||
|
||||
export function makeOllamaProvider(config: OllamaProcessorConfig): LlmProvider {
|
||||
export function makeOllamaProvider(
|
||||
config: OllamaProcessorConfig,
|
||||
): LlmProvider<TextCompletionRuntimeError> {
|
||||
return Effect.runSync(makeOllamaProviderEffect(config));
|
||||
}
|
||||
|
||||
|
|
@ -170,12 +166,6 @@ export const program = makeFlowProcessorProgram<
|
|||
layer: (config) => makeTextCompletionLayer(makeOllamaProviderEffect(config)),
|
||||
});
|
||||
|
||||
const ollamaTextCompletionRuntime = ManagedRuntime.make(Layer.empty);
|
||||
|
||||
export function run(): Promise<void> {
|
||||
return ollamaTextCompletionRuntime.runPromise(program);
|
||||
}
|
||||
|
||||
export function runMain(): void {
|
||||
NodeRuntime.runMain(program);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,9 +19,8 @@ import {
|
|||
type LlmProvider,
|
||||
type ProcessorConfig,
|
||||
type LlmResult,
|
||||
type LlmChunk,
|
||||
} from "@trustgraph/base";
|
||||
import { Effect, Layer, ManagedRuntime, Stream } from "effect";
|
||||
import { Effect, Stream } from "effect";
|
||||
import {
|
||||
llmStreamPart,
|
||||
makeTextCompletionLayer,
|
||||
|
|
@ -29,7 +28,6 @@ import {
|
|||
providerStatusError,
|
||||
requiredString,
|
||||
streamTextCompletionChunks,
|
||||
toAsyncGenerator,
|
||||
type TextCompletionConfigError,
|
||||
type TextCompletionRuntimeError,
|
||||
} from "./common.ts";
|
||||
|
|
@ -79,7 +77,7 @@ const mapOpenAICompatibleError = (error: unknown): TextCompletionRuntimeError =>
|
|||
const makeOpenAICompatibleProviderFromClient = (
|
||||
resolved: ResolvedOpenAICompatibleConfig,
|
||||
client: OpenAI,
|
||||
): LlmProvider => {
|
||||
): LlmProvider<TextCompletionRuntimeError> => {
|
||||
const {
|
||||
defaultModel,
|
||||
defaultTemperature,
|
||||
|
|
@ -92,31 +90,29 @@ const makeOpenAICompatibleProviderFromClient = (
|
|||
prompt: string,
|
||||
model?: string,
|
||||
temperature?: number,
|
||||
): Promise<LlmResult> => {
|
||||
) => {
|
||||
const modelName = model ?? defaultModel;
|
||||
const temp = temperature ?? defaultTemperature;
|
||||
|
||||
return Effect.runPromise(
|
||||
Effect.tryPromise({
|
||||
try: () =>
|
||||
client.chat.completions.create({
|
||||
model: modelName,
|
||||
messages: [
|
||||
{ role: "system", content: system },
|
||||
{ role: "user", content: prompt },
|
||||
],
|
||||
temperature: temp,
|
||||
max_tokens: maxOutput,
|
||||
}),
|
||||
catch: mapOpenAICompatibleError,
|
||||
}).pipe(
|
||||
Effect.map((resp): LlmResult => ({
|
||||
text: resp.choices[0].message.content ?? "",
|
||||
inToken: resp.usage?.prompt_tokens ?? 0,
|
||||
outToken: resp.usage?.completion_tokens ?? 0,
|
||||
return Effect.tryPromise({
|
||||
try: () =>
|
||||
client.chat.completions.create({
|
||||
model: modelName,
|
||||
})),
|
||||
),
|
||||
messages: [
|
||||
{ role: "system", content: system },
|
||||
{ role: "user", content: prompt },
|
||||
],
|
||||
temperature: temp,
|
||||
max_tokens: maxOutput,
|
||||
}),
|
||||
catch: mapOpenAICompatibleError,
|
||||
}).pipe(
|
||||
Effect.map((resp): LlmResult => ({
|
||||
text: resp.choices[0].message.content ?? "",
|
||||
inToken: resp.usage?.prompt_tokens ?? 0,
|
||||
outToken: resp.usage?.completion_tokens ?? 0,
|
||||
model: modelName,
|
||||
})),
|
||||
);
|
||||
},
|
||||
supportsStreaming: () => true,
|
||||
|
|
@ -125,11 +121,11 @@ const makeOpenAICompatibleProviderFromClient = (
|
|||
prompt: string,
|
||||
model?: string,
|
||||
temperature?: number,
|
||||
): AsyncGenerator<LlmChunk> => {
|
||||
) => {
|
||||
const modelName = model ?? defaultModel;
|
||||
const temp = temperature ?? defaultTemperature;
|
||||
|
||||
const stream = Stream.fromEffect(
|
||||
return Stream.fromEffect(
|
||||
Effect.tryPromise({
|
||||
try: () =>
|
||||
client.chat.completions.create({
|
||||
|
|
@ -158,15 +154,13 @@ const makeOpenAICompatibleProviderFromClient = (
|
|||
})
|
||||
),
|
||||
);
|
||||
|
||||
return toAsyncGenerator(Stream.toAsyncIterable(stream), mapOpenAICompatibleError);
|
||||
},
|
||||
} satisfies LlmProvider;
|
||||
} satisfies LlmProvider<TextCompletionRuntimeError>;
|
||||
};
|
||||
|
||||
export function makeOpenAICompatibleProvider(
|
||||
config: OpenAICompatibleProcessorConfig,
|
||||
): LlmProvider {
|
||||
): LlmProvider<TextCompletionRuntimeError> {
|
||||
return Effect.runSync(makeOpenAICompatibleProviderEffect(config));
|
||||
}
|
||||
|
||||
|
|
@ -203,12 +197,6 @@ export const program = makeFlowProcessorProgram<
|
|||
layer: (config) => makeTextCompletionLayer(makeOpenAICompatibleProviderEffect(config)),
|
||||
});
|
||||
|
||||
const openAICompatibleTextCompletionRuntime = ManagedRuntime.make(Layer.empty);
|
||||
|
||||
export function run(): Promise<void> {
|
||||
return openAICompatibleTextCompletionRuntime.runPromise(program);
|
||||
}
|
||||
|
||||
export function runMain(): void {
|
||||
NodeRuntime.runMain(program);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,9 +14,8 @@ import {
|
|||
type LlmProvider,
|
||||
type ProcessorConfig,
|
||||
type LlmResult,
|
||||
type LlmChunk,
|
||||
} from "@trustgraph/base";
|
||||
import { Effect, Layer, ManagedRuntime, Stream } from "effect";
|
||||
import { Effect, Stream } from "effect";
|
||||
import {
|
||||
llmStreamPart,
|
||||
makeTextCompletionLayer,
|
||||
|
|
@ -24,7 +23,6 @@ import {
|
|||
providerStatusError,
|
||||
requiredString,
|
||||
streamTextCompletionChunks,
|
||||
toAsyncGenerator,
|
||||
type TextCompletionConfigError,
|
||||
type TextCompletionRuntimeError,
|
||||
} from "./common.ts";
|
||||
|
|
@ -68,7 +66,7 @@ const mapOpenAIError = (error: unknown): TextCompletionRuntimeError =>
|
|||
const makeOpenAIProviderFromClient = (
|
||||
resolved: ResolvedOpenAIConfig,
|
||||
client: OpenAI,
|
||||
): LlmProvider => {
|
||||
): LlmProvider<TextCompletionRuntimeError> => {
|
||||
const {
|
||||
defaultModel,
|
||||
defaultTemperature,
|
||||
|
|
@ -81,31 +79,29 @@ const makeOpenAIProviderFromClient = (
|
|||
prompt: string,
|
||||
model?: string,
|
||||
temperature?: number,
|
||||
): Promise<LlmResult> => {
|
||||
) => {
|
||||
const modelName = model ?? defaultModel;
|
||||
const temp = temperature ?? defaultTemperature;
|
||||
|
||||
return Effect.runPromise(
|
||||
Effect.tryPromise({
|
||||
try: () =>
|
||||
client.chat.completions.create({
|
||||
model: modelName,
|
||||
messages: [
|
||||
{ role: "system", content: system },
|
||||
{ role: "user", content: prompt },
|
||||
],
|
||||
temperature: temp,
|
||||
max_completion_tokens: maxOutput,
|
||||
}),
|
||||
catch: mapOpenAIError,
|
||||
}).pipe(
|
||||
Effect.map((resp): LlmResult => ({
|
||||
text: resp.choices[0].message.content ?? "",
|
||||
inToken: resp.usage?.prompt_tokens ?? 0,
|
||||
outToken: resp.usage?.completion_tokens ?? 0,
|
||||
return Effect.tryPromise({
|
||||
try: () =>
|
||||
client.chat.completions.create({
|
||||
model: modelName,
|
||||
})),
|
||||
),
|
||||
messages: [
|
||||
{ role: "system", content: system },
|
||||
{ role: "user", content: prompt },
|
||||
],
|
||||
temperature: temp,
|
||||
max_completion_tokens: maxOutput,
|
||||
}),
|
||||
catch: mapOpenAIError,
|
||||
}).pipe(
|
||||
Effect.map((resp): LlmResult => ({
|
||||
text: resp.choices[0].message.content ?? "",
|
||||
inToken: resp.usage?.prompt_tokens ?? 0,
|
||||
outToken: resp.usage?.completion_tokens ?? 0,
|
||||
model: modelName,
|
||||
})),
|
||||
);
|
||||
},
|
||||
supportsStreaming: () => true,
|
||||
|
|
@ -114,11 +110,11 @@ const makeOpenAIProviderFromClient = (
|
|||
prompt: string,
|
||||
model?: string,
|
||||
temperature?: number,
|
||||
): AsyncGenerator<LlmChunk> => {
|
||||
) => {
|
||||
const modelName = model ?? defaultModel;
|
||||
const temp = temperature ?? defaultTemperature;
|
||||
|
||||
const stream = Stream.fromEffect(
|
||||
return Stream.fromEffect(
|
||||
Effect.tryPromise({
|
||||
try: () =>
|
||||
client.chat.completions.create({
|
||||
|
|
@ -148,13 +144,13 @@ const makeOpenAIProviderFromClient = (
|
|||
})
|
||||
),
|
||||
);
|
||||
|
||||
return toAsyncGenerator(Stream.toAsyncIterable(stream), mapOpenAIError);
|
||||
},
|
||||
} satisfies LlmProvider;
|
||||
} satisfies LlmProvider<TextCompletionRuntimeError>;
|
||||
};
|
||||
|
||||
export function makeOpenAIProvider(config: OpenAIProcessorConfig): LlmProvider {
|
||||
export function makeOpenAIProvider(
|
||||
config: OpenAIProcessorConfig,
|
||||
): LlmProvider<TextCompletionRuntimeError> {
|
||||
return Effect.runSync(makeOpenAIProviderEffect(config));
|
||||
}
|
||||
|
||||
|
|
@ -195,12 +191,6 @@ export const program = makeFlowProcessorProgram<
|
|||
layer: (config) => makeTextCompletionLayer(makeOpenAIProviderEffect(config)),
|
||||
});
|
||||
|
||||
const openAITextCompletionRuntime = ManagedRuntime.make(Layer.empty);
|
||||
|
||||
export function run(): Promise<void> {
|
||||
return openAITextCompletionRuntime.runPromise(program);
|
||||
}
|
||||
|
||||
export function runMain(): void {
|
||||
NodeRuntime.runMain(program);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue