From 511ae5b583dee5176c156367b7aeffddaa7a74c2 Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Sat, 12 Aug 2023 08:44:06 +0200 Subject: [PATCH 01/18] Remove unused type. --- src/run/RunFunction.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/run/RunFunction.ts b/src/run/RunFunction.ts index 778f81964..3abe90072 100644 --- a/src/run/RunFunction.ts +++ b/src/run/RunFunction.ts @@ -1,4 +1,3 @@ -import { SafeResult } from "../util/SafeResult.js"; import { Run } from "./Run.js"; /** @@ -12,10 +11,3 @@ export type RunFunction = ( run?: Run; } ) => PromiseLike; - -export type SafeRunFunction = ( - input: INPUT, - options?: { - run?: Run; - } -) => PromiseLike>; From bdf2bd9b0e54c8f08553c871a89ae264fed291ea Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Sat, 12 Aug 2023 08:47:03 +0200 Subject: [PATCH 02/18] Don't check example lockfiles into repo. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 7e2875831..ab29d7636 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ build dist node_modules +examples/*/package-lock.json \ No newline at end of file From 7a2690b42bae9aff33ed80970a96e7f39742cb52 Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Sat, 12 Aug 2023 08:49:44 +0200 Subject: [PATCH 03/18] Add optional output schema. Turn execute into RunFunction. --- src/composed-function/use-tool/Tool.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/composed-function/use-tool/Tool.ts b/src/composed-function/use-tool/Tool.ts index 71cca7ee6..bc48d515a 100644 --- a/src/composed-function/use-tool/Tool.ts +++ b/src/composed-function/use-tool/Tool.ts @@ -1,21 +1,25 @@ import { z } from "zod"; import { SchemaDefinition } from "../../model-function/generate-json/SchemaDefinition.js"; +import { RunFunction } from "../../run/RunFunction.js"; export class Tool { readonly name: NAME; readonly description: string; readonly inputSchema: z.ZodSchema; - readonly execute: (input: INPUT) => PromiseLike; + readonly outputSchema?: z.ZodSchema; + readonly execute: RunFunction; constructor(options: { name: NAME; description: string; inputSchema: z.ZodSchema; + outputSchema?: z.ZodSchema; execute(input: INPUT): Promise; }) { this.name = options.name; this.description = options.description; this.inputSchema = options.inputSchema; + this.outputSchema = options.outputSchema; this.execute = options.execute; } From 55647a3e2b119b1bbb962090fb8e478b5fb2bb10 Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Sat, 12 Aug 2023 08:59:21 +0200 Subject: [PATCH 04/18] Move tool-related code to src/tool. --- src/composed-function/index.ts | 3 --- src/index.ts | 1 + src/model-provider/openai/chat/OpenAIChatPrompt.ts | 2 +- .../use-tool => tool}/NoSuchToolError.ts | 0 src/{composed-function/use-tool => tool}/Tool.ts | 4 ++-- src/tool/index.ts | 3 +++ src/{composed-function/use-tool => tool}/useTool.ts | 10 +++++----- 7 files changed, 12 insertions(+), 11 deletions(-) rename src/{composed-function/use-tool => tool}/NoSuchToolError.ts (100%) rename src/{composed-function/use-tool => tool}/Tool.ts (85%) create mode 100644 src/tool/index.ts rename src/{composed-function/use-tool => tool}/useTool.ts (90%) diff --git a/src/composed-function/index.ts b/src/composed-function/index.ts index 2c018a1ad..11366b3a7 100644 --- a/src/composed-function/index.ts +++ b/src/composed-function/index.ts @@ -1,6 +1,3 @@ -export * from "./use-tool/NoSuchToolError.js"; -export * from "./use-tool/Tool.js"; -export * from "./use-tool/useTool.js"; export * from "./summarize/SummarizationFunction.js"; export * from "./summarize/summarizeRecursively.js"; export * from "./summarize/summarizeRecursivelyWithTextGenerationAndTokenSplitting.js"; diff --git a/src/index.ts b/src/index.ts index af957c9e4..a1b91b105 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,5 +5,6 @@ export * from "./model-provider/index.js"; export * from "./prompt/index.js"; export * from "./run/index.js"; export * from "./text-chunk/index.js"; +export * from "./tool/index.js"; export * from "./util/index.js"; export * from "./vector-index/index.js"; diff --git a/src/model-provider/openai/chat/OpenAIChatPrompt.ts b/src/model-provider/openai/chat/OpenAIChatPrompt.ts index 5258b65a0..33e780239 100644 --- a/src/model-provider/openai/chat/OpenAIChatPrompt.ts +++ b/src/model-provider/openai/chat/OpenAIChatPrompt.ts @@ -1,10 +1,10 @@ import SecureJSON from "secure-json-parse"; import z from "zod"; import { zodToJsonSchema } from "zod-to-json-schema"; -import { Tool } from "../../../composed-function/use-tool/Tool.js"; import { GenerateJsonPrompt } from "../../../model-function/generate-json/GenerateJsonModel.js"; import { GenerateJsonOrTextPrompt } from "../../../model-function/generate-json/GenerateJsonOrTextModel.js"; import { SchemaDefinition } from "../../../model-function/generate-json/SchemaDefinition.js"; +import { Tool } from "../../../tool/Tool.js"; import { OpenAIChatMessage } from "./OpenAIChatMessage.js"; import { OpenAIChatResponse } from "./OpenAIChatModel.js"; diff --git a/src/composed-function/use-tool/NoSuchToolError.ts b/src/tool/NoSuchToolError.ts similarity index 100% rename from src/composed-function/use-tool/NoSuchToolError.ts rename to src/tool/NoSuchToolError.ts diff --git a/src/composed-function/use-tool/Tool.ts b/src/tool/Tool.ts similarity index 85% rename from src/composed-function/use-tool/Tool.ts rename to src/tool/Tool.ts index bc48d515a..4d8ac1657 100644 --- a/src/composed-function/use-tool/Tool.ts +++ b/src/tool/Tool.ts @@ -1,6 +1,6 @@ import { z } from "zod"; -import { SchemaDefinition } from "../../model-function/generate-json/SchemaDefinition.js"; -import { RunFunction } from "../../run/RunFunction.js"; +import { SchemaDefinition } from "../model-function/generate-json/SchemaDefinition.js"; +import { RunFunction } from "../run/RunFunction.js"; export class Tool { readonly name: NAME; diff --git a/src/tool/index.ts b/src/tool/index.ts new file mode 100644 index 000000000..3fdfa5441 --- /dev/null +++ b/src/tool/index.ts @@ -0,0 +1,3 @@ +export * from "./NoSuchToolError.js"; +export * from "./Tool.js"; +export * from "./useTool.js"; diff --git a/src/composed-function/use-tool/useTool.ts b/src/tool/useTool.ts similarity index 90% rename from src/composed-function/use-tool/useTool.ts rename to src/tool/useTool.ts index 0d4a1fb64..9b9c39285 100644 --- a/src/composed-function/use-tool/useTool.ts +++ b/src/tool/useTool.ts @@ -1,16 +1,16 @@ -import { FunctionOptions } from "../../model-function/FunctionOptions.js"; +import { FunctionOptions } from "../model-function/FunctionOptions.js"; import { GenerateJsonModel, GenerateJsonModelSettings, GenerateJsonPrompt, -} from "../../model-function/generate-json/GenerateJsonModel.js"; +} from "../model-function/generate-json/GenerateJsonModel.js"; import { GenerateJsonOrTextModel, GenerateJsonOrTextModelSettings, GenerateJsonOrTextPrompt, -} from "../../model-function/generate-json/GenerateJsonOrTextModel.js"; -import { generateJson } from "../../model-function/generate-json/generateJson.js"; -import { generateJsonOrText } from "../../model-function/generate-json/generateJsonOrText.js"; +} from "../model-function/generate-json/GenerateJsonOrTextModel.js"; +import { generateJson } from "../model-function/generate-json/generateJson.js"; +import { generateJsonOrText } from "../model-function/generate-json/generateJsonOrText.js"; import { NoSuchToolError } from "./NoSuchToolError.js"; import { Tool } from "./Tool.js"; From dff98aeeabb2d6fcf26dbeccd01ab86cb2574550 Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Sat, 12 Aug 2023 09:17:33 +0200 Subject: [PATCH 05/18] Distinguish event type for "json-or-text-generation". --- src/model-function/SuccessfulModelCall.ts | 2 ++ src/model-function/generate-json/JsonGenerationEvent.ts | 4 ++-- src/model-function/generate-json/generateJsonOrText.ts | 8 ++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/model-function/SuccessfulModelCall.ts b/src/model-function/SuccessfulModelCall.ts index 2aab387f3..224fbcc4a 100644 --- a/src/model-function/SuccessfulModelCall.ts +++ b/src/model-function/SuccessfulModelCall.ts @@ -8,6 +8,7 @@ export type SuccessfulModelCall = { type: | "image-generation" | "json-generation" + | "json-or-text-generation" | "text-embedding" | "text-generation" | "text-streaming" @@ -38,6 +39,7 @@ export function extractSuccessfulModelCalls( const eventTypeToCostType = { "image-generation-finished": "image-generation" as const, "json-generation-finished": "json-generation" as const, + "json-or-text-generation-finished": "json-or-text-generation" as const, "text-embedding-finished": "text-embedding" as const, "text-generation-finished": "text-generation" as const, "text-streaming-finished": "text-streaming" as const, diff --git a/src/model-function/generate-json/JsonGenerationEvent.ts b/src/model-function/generate-json/JsonGenerationEvent.ts index 4f4bafdaf..24fad3fe7 100644 --- a/src/model-function/generate-json/JsonGenerationEvent.ts +++ b/src/model-function/generate-json/JsonGenerationEvent.ts @@ -4,14 +4,14 @@ import { } from "../ModelCallEvent.js"; export type JsonGenerationStartedEvent = { - type: "json-generation-started"; + type: "json-generation-started" | "json-or-text-generation-started"; metadata: ModelCallStartedEventMetadata; settings: unknown; prompt: unknown; }; export type JsonGenerationFinishedEvent = { - type: "json-generation-finished"; + type: "json-generation-finished" | "json-or-text-generation-finished"; metadata: ModelCallFinishedEventMetadata; settings: unknown; prompt: unknown; diff --git a/src/model-function/generate-json/generateJsonOrText.ts b/src/model-function/generate-json/generateJsonOrText.ts index b42108c5e..296479beb 100644 --- a/src/model-function/generate-json/generateJsonOrText.ts +++ b/src/model-function/generate-json/generateJsonOrText.ts @@ -135,20 +135,20 @@ export async function generateJsonOrText< }; }, getStartEvent: (metadata, settings) => ({ - type: "json-generation-started", + type: "json-or-text-generation-started", metadata, settings, prompt, }), getAbortEvent: (metadata, settings) => ({ - type: "json-generation-finished", + type: "json-or-text-generation-finished", status: "abort", metadata, settings, prompt, }), getFailureEvent: (metadata, settings, error) => ({ - type: "json-generation-finished", + type: "json-or-text-generation-finished", status: "failure", metadata, settings, @@ -156,7 +156,7 @@ export async function generateJsonOrText< error, }), getSuccessEvent: (metadata, settings, response, output) => ({ - type: "json-generation-finished", + type: "json-or-text-generation-finished", status: "success", metadata, settings, From edac494231abc3a8804d1be49806cb15a58ff107 Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Sat, 12 Aug 2023 09:47:21 +0200 Subject: [PATCH 06/18] Introduce RunFunctionEvent. --- examples/pdf-to-tweet/src/main.ts | 4 +-- src/model-function/Model.ts | 4 +-- src/model-function/ModelCallEvent.ts | 17 ++++++----- src/model-function/ModelCallObserver.ts | 9 ------ src/model-function/executeCall.ts | 12 ++++---- .../generate-text/streamText.ts | 17 ++++++----- src/model-function/index.ts | 2 +- src/run/ConsoleLogger.ts | 14 ++++----- src/run/DefaultRun.ts | 29 +++++++++---------- src/run/Run.ts | 4 +-- src/run/RunFunctionEvent.ts | 22 ++++++++++++++ .../RunFunctionEventSource.ts} | 22 +++++++------- src/run/RunFunctionObserver.ts | 9 ++++++ 13 files changed, 93 insertions(+), 72 deletions(-) delete mode 100644 src/model-function/ModelCallObserver.ts create mode 100644 src/run/RunFunctionEvent.ts rename src/{model-function/ModelCallEventSource.ts => run/RunFunctionEventSource.ts} (54%) create mode 100644 src/run/RunFunctionObserver.ts diff --git a/examples/pdf-to-tweet/src/main.ts b/examples/pdf-to-tweet/src/main.ts index 71a2fbd61..bd5ef49d3 100644 --- a/examples/pdf-to-tweet/src/main.ts +++ b/examples/pdf-to-tweet/src/main.ts @@ -20,7 +20,7 @@ const run = new DefaultRun({ costCalculators: [new OpenAICostCalculator()], observers: [ { - onModelCallStarted(event) { + onRunFunctionStarted(event) { if (event.type === "text-generation-started") { console.log( `Generate text ${event.metadata.functionId ?? "unknown"} started.` @@ -32,7 +32,7 @@ const run = new DefaultRun({ } }, - onModelCallFinished(event) { + onRunFunctionFinished(event) { if (event.type === "text-generation-finished") { console.log( `Generate text ${event.metadata.functionId ?? "unknown"} finished.` diff --git a/src/model-function/Model.ts b/src/model-function/Model.ts index bae6205fc..4570ea3b9 100644 --- a/src/model-function/Model.ts +++ b/src/model-function/Model.ts @@ -1,8 +1,8 @@ import { ModelInformation } from "./ModelInformation.js"; -import { ModelCallObserver } from "./ModelCallObserver.js"; +import { RunFunctionObserver } from "../run/RunFunctionObserver.js"; export interface ModelSettings { - observers?: Array; + observers?: Array; } export interface Model { diff --git a/src/model-function/ModelCallEvent.ts b/src/model-function/ModelCallEvent.ts index 20e2f5b1a..d163d091f 100644 --- a/src/model-function/ModelCallEvent.ts +++ b/src/model-function/ModelCallEvent.ts @@ -1,4 +1,7 @@ -import { IdMetadata } from "../run/IdMetadata.js"; +import { + RunFunctionFinishedEventMetadata, + RunFunctionStartedEventMetadata, +} from "../run/RunFunctionEvent.js"; import { ModelInformation } from "./ModelInformation.js"; import { TextEmbeddingFinishedEvent, @@ -25,11 +28,8 @@ import { TranscriptionStartedEvent, } from "./transcribe-audio/TranscriptionEvent.js"; -export type ModelCallEvent = ModelCallStartedEvent | ModelCallFinishedEvent; - -export type ModelCallStartedEventMetadata = IdMetadata & { +export type ModelCallStartedEventMetadata = RunFunctionStartedEventMetadata & { model: ModelInformation; - startEpochSeconds: number; }; export type ModelCallStartedEvent = @@ -40,9 +40,10 @@ export type ModelCallStartedEvent = | TextStreamingStartedEvent | TranscriptionStartedEvent; -export type ModelCallFinishedEventMetadata = ModelCallStartedEventMetadata & { - durationInMs: number; -}; +export type ModelCallFinishedEventMetadata = + RunFunctionFinishedEventMetadata & { + model: ModelInformation; + }; export type ModelCallFinishedEvent = | ImageGenerationFinishedEvent diff --git a/src/model-function/ModelCallObserver.ts b/src/model-function/ModelCallObserver.ts deleted file mode 100644 index e1be9626f..000000000 --- a/src/model-function/ModelCallObserver.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { - ModelCallFinishedEvent, - ModelCallStartedEvent, -} from "./ModelCallEvent.js"; - -export type ModelCallObserver = { - onModelCallStarted?: (event: ModelCallStartedEvent) => void; - onModelCallFinished?: (event: ModelCallFinishedEvent) => void; -}; diff --git a/src/model-function/executeCall.ts b/src/model-function/executeCall.ts index 1665105d5..7c3593187 100644 --- a/src/model-function/executeCall.ts +++ b/src/model-function/executeCall.ts @@ -1,4 +1,5 @@ import { nanoid as createId } from "nanoid"; +import { RunFunctionEventSource } from "../run/RunFunctionEventSource.js"; import { startDurationMeasurement } from "../util/DurationMeasurement.js"; import { AbortError } from "../util/api/AbortError.js"; import { runSafe } from "../util/runSafe.js"; @@ -10,7 +11,6 @@ import { ModelCallStartedEvent, ModelCallStartedEventMetadata, } from "./ModelCallEvent.js"; -import { ModelCallEventSource } from "./ModelCallEventSource.js"; export type CallMetadata> = { callId: string; @@ -79,7 +79,7 @@ export async function executeCall< const run = options?.run; const settings = model.settings; - const eventSource = new ModelCallEventSource({ + const eventSource = new RunFunctionEventSource({ observers: [...(settings.observers ?? []), ...(run?.observers ?? [])], errorHandler: run?.errorHandler, }); @@ -96,7 +96,7 @@ export async function executeCall< startEpochSeconds: durationMeasurement.startEpochSeconds, }; - eventSource.notifyModelCallStarted(getStartEvent(startMetadata, settings)); + eventSource.notifyRunFunctionStarted(getStartEvent(startMetadata, settings)); const result = await runSafe(() => generateResponse({ @@ -113,13 +113,13 @@ export async function executeCall< if (!result.ok) { if (result.isAborted) { - eventSource.notifyModelCallFinished( + eventSource.notifyRunFunctionFinished( getAbortEvent(finishMetadata, settings) ); throw new AbortError(); } - eventSource.notifyModelCallFinished( + eventSource.notifyRunFunctionFinished( getFailureEvent(finishMetadata, settings, result.error) ); throw result.error; @@ -128,7 +128,7 @@ export async function executeCall< const response = result.output; const output = extractOutputValue(response); - eventSource.notifyModelCallFinished( + eventSource.notifyRunFunctionFinished( getSuccessEvent(finishMetadata, settings, response, output) ); diff --git a/src/model-function/generate-text/streamText.ts b/src/model-function/generate-text/streamText.ts index 941d5a596..608d99bfb 100644 --- a/src/model-function/generate-text/streamText.ts +++ b/src/model-function/generate-text/streamText.ts @@ -1,15 +1,16 @@ import { nanoid as createId } from "nanoid"; +import { RunFunctionEventSource } from "../../run/RunFunctionEventSource.js"; import { startDurationMeasurement } from "../../util/DurationMeasurement.js"; import { AbortError } from "../../util/api/AbortError.js"; import { runSafe } from "../../util/runSafe.js"; import { FunctionOptions } from "../FunctionOptions.js"; -import { ModelCallEventSource } from "../ModelCallEventSource.js"; import { CallMetadata } from "../executeCall.js"; import { DeltaEvent } from "./DeltaEvent.js"; import { TextGenerationModel, TextGenerationModelSettings, } from "./TextGenerationModel.js"; +import { TextStreamingFinishedEvent } from "./TextStreamingEvent.js"; import { extractTextDeltas } from "./extractTextDeltas.js"; export async function streamText< @@ -91,7 +92,7 @@ export async function streamText< const run = options?.run; const settings = model.settings; - const eventSource = new ModelCallEventSource({ + const eventSource = new RunFunctionEventSource({ observers: [...(settings.observers ?? []), ...(run?.observers ?? [])], errorHandler: run?.errorHandler, }); @@ -108,7 +109,7 @@ export async function streamText< startEpochSeconds: durationMeasurement.startEpochSeconds, }; - eventSource.notifyModelCallStarted({ + eventSource.notifyRunFunctionStarted({ type: "text-streaming-started", metadata: startMetadata, settings, @@ -129,7 +130,7 @@ export async function streamText< durationInMs: durationMeasurement.durationInMs, }; - eventSource.notifyModelCallFinished({ + eventSource.notifyRunFunctionFinished({ type: "text-streaming-finished", status: "success", metadata: finishMetadata, @@ -137,7 +138,7 @@ export async function streamText< prompt, response: lastFullDelta, generatedText: fullText, - }); + } as TextStreamingFinishedEvent); }, onError: (error) => { const finishMetadata = { @@ -145,7 +146,7 @@ export async function streamText< durationInMs: durationMeasurement.durationInMs, }; - eventSource.notifyModelCallFinished( + eventSource.notifyRunFunctionFinished( error instanceof AbortError ? { type: "text-streaming-finished", @@ -174,7 +175,7 @@ export async function streamText< }; if (result.isAborted) { - eventSource.notifyModelCallFinished({ + eventSource.notifyRunFunctionFinished({ type: "text-streaming-finished", status: "abort", metadata: finishMetadata, @@ -184,7 +185,7 @@ export async function streamText< throw new AbortError(); } - eventSource.notifyModelCallFinished({ + eventSource.notifyRunFunctionFinished({ type: "text-streaming-finished", status: "failure", metadata: finishMetadata, diff --git a/src/model-function/index.ts b/src/model-function/index.ts index 64b44864d..705836d6f 100644 --- a/src/model-function/index.ts +++ b/src/model-function/index.ts @@ -1,7 +1,7 @@ export * from "./FunctionOptions.js"; export * from "./Model.js"; export * from "./ModelCallEvent.js"; -export * from "./ModelCallObserver.js"; +export * from "../run/RunFunctionObserver.js"; export * from "./ModelInformation.js"; export * from "./SuccessfulModelCall.js"; export * from "./embed-text/TextEmbeddingEvent.js"; diff --git a/src/run/ConsoleLogger.ts b/src/run/ConsoleLogger.ts index 4c9d7b5fd..1ea0565b3 100644 --- a/src/run/ConsoleLogger.ts +++ b/src/run/ConsoleLogger.ts @@ -1,15 +1,15 @@ import { - ModelCallFinishedEvent, - ModelCallStartedEvent, -} from "../model-function/ModelCallEvent.js"; -import { ModelCallObserver } from "../model-function/ModelCallObserver.js"; + RunFunctionFinishedEvent, + RunFunctionStartedEvent, +} from "./RunFunctionEvent.js"; +import { RunFunctionObserver } from "./RunFunctionObserver.js"; -export class ConsoleLogger implements ModelCallObserver { - onModelCallStarted(event: ModelCallStartedEvent) { +export class ConsoleLogger implements RunFunctionObserver { + onRunFunctionStarted(event: RunFunctionStartedEvent) { console.log(JSON.stringify(event, null, 2)); } - onModelCallFinished(event: ModelCallFinishedEvent) { + onRunFunctionFinished(event: RunFunctionFinishedEvent) { console.log(JSON.stringify(event, null, 2)); } } diff --git a/src/run/DefaultRun.ts b/src/run/DefaultRun.ts index ffd298551..4ea875840 100644 --- a/src/run/DefaultRun.ts +++ b/src/run/DefaultRun.ts @@ -1,16 +1,14 @@ import { nanoid as createId } from "nanoid"; +import { CostCalculator } from "../cost/CostCalculator.js"; +import { calculateCost } from "../cost/calculateCost.js"; import { SuccessfulModelCall, extractSuccessfulModelCalls, } from "../model-function/SuccessfulModelCall.js"; -import { - ModelCallFinishedEvent, - ModelCallStartedEvent, -} from "../model-function/ModelCallEvent.js"; -import { ModelCallObserver } from "../model-function/ModelCallObserver.js"; import { Run } from "./Run.js"; -import { CostCalculator } from "../cost/CostCalculator.js"; -import { calculateCost } from "../cost/calculateCost.js"; +import { RunFunctionEvent } from "./RunFunctionEvent.js"; +import { RunFunctionObserver } from "./RunFunctionObserver.js"; + export class DefaultRun implements Run { readonly runId: string; readonly sessionId?: string; @@ -19,10 +17,9 @@ export class DefaultRun implements Run { readonly abortSignal?: AbortSignal; readonly costCalculators: CostCalculator[]; - readonly modelCallEvents: (ModelCallFinishedEvent | ModelCallStartedEvent)[] = - []; + readonly events: RunFunctionEvent[] = []; - readonly observers?: ModelCallObserver[]; + readonly observers?: RunFunctionObserver[]; constructor({ runId = createId(), @@ -36,7 +33,7 @@ export class DefaultRun implements Run { sessionId?: string; userId?: string; abortSignal?: AbortSignal; - observers?: ModelCallObserver[]; + observers?: RunFunctionObserver[]; costCalculators?: CostCalculator[]; } = {}) { this.runId = runId; @@ -47,11 +44,11 @@ export class DefaultRun implements Run { this.observers = [ { - onModelCallStarted: (event) => { - this.modelCallEvents.push(event); + onRunFunctionStarted: (event) => { + this.events.push(event); }, - onModelCallFinished: (event) => { - this.modelCallEvents.push(event); + onRunFunctionFinished: (event) => { + this.events.push(event); }, }, ...(observers ?? []), @@ -59,7 +56,7 @@ export class DefaultRun implements Run { } get successfulModelCalls(): Array { - return extractSuccessfulModelCalls(this.modelCallEvents); + return extractSuccessfulModelCalls(this.events); } calculateCost() { diff --git a/src/run/Run.ts b/src/run/Run.ts index b57013b73..40b2e60da 100644 --- a/src/run/Run.ts +++ b/src/run/Run.ts @@ -1,5 +1,5 @@ import { ErrorHandler } from "../util/ErrorHandler.js"; -import { ModelCallObserver } from "../model-function/ModelCallObserver.js"; +import { RunFunctionObserver } from "./RunFunctionObserver.js"; export interface Run { /** @@ -27,7 +27,7 @@ export interface Run { */ abortSignal?: AbortSignal; - observers?: ModelCallObserver[]; + observers?: RunFunctionObserver[]; errorHandler?: ErrorHandler; } diff --git a/src/run/RunFunctionEvent.ts b/src/run/RunFunctionEvent.ts new file mode 100644 index 000000000..80b1695b6 --- /dev/null +++ b/src/run/RunFunctionEvent.ts @@ -0,0 +1,22 @@ +import { + ModelCallFinishedEvent, + ModelCallStartedEvent, +} from "../model-function/ModelCallEvent.js"; +import { IdMetadata } from "./IdMetadata.js"; + +export type RunFunctionEvent = + | RunFunctionStartedEvent + | RunFunctionFinishedEvent; + +export type RunFunctionStartedEventMetadata = IdMetadata & { + startEpochSeconds: number; +}; + +export type RunFunctionStartedEvent = ModelCallStartedEvent; + +export type RunFunctionFinishedEventMetadata = + RunFunctionStartedEventMetadata & { + durationInMs: number; + }; + +export type RunFunctionFinishedEvent = ModelCallFinishedEvent; diff --git a/src/model-function/ModelCallEventSource.ts b/src/run/RunFunctionEventSource.ts similarity index 54% rename from src/model-function/ModelCallEventSource.ts rename to src/run/RunFunctionEventSource.ts index 4e8284a63..3e2eeb7ae 100644 --- a/src/model-function/ModelCallEventSource.ts +++ b/src/run/RunFunctionEventSource.ts @@ -1,39 +1,39 @@ import { ErrorHandler } from "../util/ErrorHandler.js"; import { - ModelCallFinishedEvent, - ModelCallStartedEvent, -} from "./ModelCallEvent.js"; -import { ModelCallObserver } from "./ModelCallObserver.js"; + RunFunctionFinishedEvent, + RunFunctionStartedEvent, +} from "./RunFunctionEvent.js"; +import { RunFunctionObserver } from "./RunFunctionObserver.js"; -export class ModelCallEventSource { - readonly observers: ModelCallObserver[]; +export class RunFunctionEventSource { + readonly observers: RunFunctionObserver[]; readonly errorHandler: ErrorHandler; constructor({ observers, errorHandler, }: { - observers: ModelCallObserver[]; + observers: RunFunctionObserver[]; errorHandler?: ErrorHandler; }) { this.observers = observers; this.errorHandler = errorHandler ?? ((error) => console.error(error)); } - notifyModelCallStarted(event: ModelCallStartedEvent) { + notifyRunFunctionStarted(event: RunFunctionStartedEvent) { for (const observer of this.observers) { try { - observer.onModelCallStarted?.(event); + observer.onRunFunctionStarted?.(event); } catch (error) { this.errorHandler(error); } } } - notifyModelCallFinished(event: ModelCallFinishedEvent) { + notifyRunFunctionFinished(event: RunFunctionFinishedEvent) { for (const observer of this.observers) { try { - observer.onModelCallFinished?.(event); + observer.onRunFunctionFinished?.(event); } catch (error) { this.errorHandler(error); } diff --git a/src/run/RunFunctionObserver.ts b/src/run/RunFunctionObserver.ts new file mode 100644 index 000000000..d7ea366dc --- /dev/null +++ b/src/run/RunFunctionObserver.ts @@ -0,0 +1,9 @@ +import { + RunFunctionFinishedEvent, + RunFunctionStartedEvent, +} from "./RunFunctionEvent.js"; + +export type RunFunctionObserver = { + onRunFunctionStarted?: (event: RunFunctionStartedEvent) => void; + onRunFunctionFinished?: (event: RunFunctionFinishedEvent) => void; +}; From 912997cc781c0a2f9dd6bbae1e7993412263a9b9 Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Sat, 12 Aug 2023 09:52:02 +0200 Subject: [PATCH 07/18] Move useToolOrGenerateText into separate file. --- src/tool/index.ts | 1 + src/tool/useTool.ts | 81 ---------------------------- src/tool/useToolOrGenerateText.ts | 87 +++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 81 deletions(-) create mode 100644 src/tool/useToolOrGenerateText.ts diff --git a/src/tool/index.ts b/src/tool/index.ts index 3fdfa5441..7c0a22268 100644 --- a/src/tool/index.ts +++ b/src/tool/index.ts @@ -1,3 +1,4 @@ export * from "./NoSuchToolError.js"; export * from "./Tool.js"; export * from "./useTool.js"; +export * from "./useToolOrGenerateText.js"; diff --git a/src/tool/useTool.ts b/src/tool/useTool.ts index 9b9c39285..d1441e2fc 100644 --- a/src/tool/useTool.ts +++ b/src/tool/useTool.ts @@ -4,14 +4,7 @@ import { GenerateJsonModelSettings, GenerateJsonPrompt, } from "../model-function/generate-json/GenerateJsonModel.js"; -import { - GenerateJsonOrTextModel, - GenerateJsonOrTextModelSettings, - GenerateJsonOrTextPrompt, -} from "../model-function/generate-json/GenerateJsonOrTextModel.js"; import { generateJson } from "../model-function/generate-json/generateJson.js"; -import { generateJsonOrText } from "../model-function/generate-json/generateJsonOrText.js"; -import { NoSuchToolError } from "./NoSuchToolError.js"; import { Tool } from "./Tool.js"; // In this file, using 'any' is required to allow for flexibility in the inputs. The actual types are @@ -66,77 +59,3 @@ export async function useTool< result: await tool.execute(value), }; } - -// [ { name: "n", ... } | { ... } ] -type ToolArray[]> = T; - -// { n: { name: "n", ... }, ... } -type ToToolMap[]>> = { - [K in T[number]["name"]]: Extract>; -}; - -// { tool: "n", result: ... } | { ... } -type ToToolUnion = { - [KEY in keyof T]: T[KEY] extends Tool - ? { tool: KEY; parameters: INPUT; result: OUTPUT; text: string | null } - : never; -}[keyof T]; - -type ToOutputValue[]>> = - ToToolUnion>; - -export async function useToolOrGenerateText< - PROMPT, - RESPONSE, - SETTINGS extends GenerateJsonOrTextModelSettings, - TOOLS extends Array>, ->( - model: GenerateJsonOrTextModel, - tools: TOOLS, - prompt: (tools: TOOLS) => PROMPT & GenerateJsonOrTextPrompt, - options?: FunctionOptions -): Promise< - | { tool: null; parameters: null; result: null; text: string } - | ToOutputValue -> { - const expandedPrompt = prompt(tools); - - const modelResponse = await generateJsonOrText( - model, - tools.map((tool) => ({ - name: tool.name, - description: tool.description, - schema: tool.inputSchema, - })), - () => expandedPrompt, - options - ); - - const { schema, text } = modelResponse; - - if (schema == null) { - return { - tool: null, - parameters: null, - result: null, - text, - }; - } - - const tool = tools.find((tool) => tool.name === schema); - - if (tool == null) { - throw new NoSuchToolError(schema.toString()); - } - - const toolParameters = modelResponse.value; - - const result = await tool.execute(toolParameters); - - return { - tool: schema as keyof ToToolMap, - result, - parameters: toolParameters, - text: text as any, // string | null is the expected value here - }; -} diff --git a/src/tool/useToolOrGenerateText.ts b/src/tool/useToolOrGenerateText.ts new file mode 100644 index 000000000..4ea893913 --- /dev/null +++ b/src/tool/useToolOrGenerateText.ts @@ -0,0 +1,87 @@ +import { FunctionOptions } from "../model-function/FunctionOptions.js"; +import { + GenerateJsonOrTextModel, + GenerateJsonOrTextModelSettings, + GenerateJsonOrTextPrompt, +} from "../model-function/generate-json/GenerateJsonOrTextModel.js"; +import { generateJsonOrText } from "../model-function/generate-json/generateJsonOrText.js"; +import { NoSuchToolError } from "./NoSuchToolError.js"; +import { Tool } from "./Tool.js"; + +// In this file, using 'any' is required to allow for flexibility in the inputs. The actual types are +// retrieved through lookups such as TOOL["name"], such that any does not affect any client. +/* eslint-disable @typescript-eslint/no-explicit-any */ + +// [ { name: "n", ... } | { ... } ] +type ToolArray[]> = T; + +// { n: { name: "n", ... }, ... } +type ToToolMap[]>> = { + [K in T[number]["name"]]: Extract>; +}; + +// { tool: "n", result: ... } | { ... } +type ToToolUnion = { + [KEY in keyof T]: T[KEY] extends Tool + ? { tool: KEY; parameters: INPUT; result: OUTPUT; text: string | null } + : never; +}[keyof T]; + +type ToOutputValue[]>> = + ToToolUnion>; + +export async function useToolOrGenerateText< + PROMPT, + RESPONSE, + SETTINGS extends GenerateJsonOrTextModelSettings, + TOOLS extends Array>, +>( + model: GenerateJsonOrTextModel, + tools: TOOLS, + prompt: (tools: TOOLS) => PROMPT & GenerateJsonOrTextPrompt, + options?: FunctionOptions +): Promise< + | { tool: null; parameters: null; result: null; text: string } + | ToOutputValue +> { + const expandedPrompt = prompt(tools); + + const modelResponse = await generateJsonOrText( + model, + tools.map((tool) => ({ + name: tool.name, + description: tool.description, + schema: tool.inputSchema, + })), + () => expandedPrompt, + options + ); + + const { schema, text } = modelResponse; + + if (schema == null) { + return { + tool: null, + parameters: null, + result: null, + text, + }; + } + + const tool = tools.find((tool) => tool.name === schema); + + if (tool == null) { + throw new NoSuchToolError(schema.toString()); + } + + const toolParameters = modelResponse.value; + + const result = await tool.execute(toolParameters); + + return { + tool: schema as keyof ToToolMap, + result, + parameters: toolParameters, + text: text as any, // string | null is the expected value here + }; +} From 7c467e36a7ea5231812de373909d92c55fb0df9b Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Sat, 12 Aug 2023 10:05:01 +0200 Subject: [PATCH 08/18] Add basic executeTool function. --- src/tool/executeTool.ts | 14 ++++++++++++++ src/tool/index.ts | 1 + src/tool/useTool.ts | 5 ++++- src/tool/useToolOrGenerateText.ts | 5 ++++- 4 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 src/tool/executeTool.ts diff --git a/src/tool/executeTool.ts b/src/tool/executeTool.ts new file mode 100644 index 000000000..1a665dc68 --- /dev/null +++ b/src/tool/executeTool.ts @@ -0,0 +1,14 @@ +import { FunctionOptions } from "../model-function/FunctionOptions.js"; +import { Tool } from "./Tool.js"; + +export async function executeTool< + INPUT, + OUTPUT, + TOOL extends Tool, +>( + tool: TOOL, + input: INPUT, + options?: FunctionOptions +): Promise { + return await tool.execute(input, options); +} diff --git a/src/tool/index.ts b/src/tool/index.ts index 7c0a22268..d152401e0 100644 --- a/src/tool/index.ts +++ b/src/tool/index.ts @@ -1,4 +1,5 @@ export * from "./NoSuchToolError.js"; export * from "./Tool.js"; +export * from "./executeTool.js"; export * from "./useTool.js"; export * from "./useToolOrGenerateText.js"; diff --git a/src/tool/useTool.ts b/src/tool/useTool.ts index d1441e2fc..e84461fc2 100644 --- a/src/tool/useTool.ts +++ b/src/tool/useTool.ts @@ -6,6 +6,7 @@ import { } from "../model-function/generate-json/GenerateJsonModel.js"; import { generateJson } from "../model-function/generate-json/generateJson.js"; import { Tool } from "./Tool.js"; +import { executeTool } from "./executeTool.js"; // In this file, using 'any' is required to allow for flexibility in the inputs. The actual types are // retrieved through lookups such as TOOL["name"], such that any does not affect any client. @@ -56,6 +57,8 @@ export async function useTool< return { tool: tool.name, parameters: value, - result: await tool.execute(value), + result: await executeTool(tool, value, { + run: options?.run, + }), }; } diff --git a/src/tool/useToolOrGenerateText.ts b/src/tool/useToolOrGenerateText.ts index 4ea893913..4ee58330e 100644 --- a/src/tool/useToolOrGenerateText.ts +++ b/src/tool/useToolOrGenerateText.ts @@ -7,6 +7,7 @@ import { import { generateJsonOrText } from "../model-function/generate-json/generateJsonOrText.js"; import { NoSuchToolError } from "./NoSuchToolError.js"; import { Tool } from "./Tool.js"; +import { executeTool } from "./executeTool.js"; // In this file, using 'any' is required to allow for flexibility in the inputs. The actual types are // retrieved through lookups such as TOOL["name"], such that any does not affect any client. @@ -76,7 +77,9 @@ export async function useToolOrGenerateText< const toolParameters = modelResponse.value; - const result = await tool.execute(toolParameters); + const result = (await executeTool(tool, toolParameters, { + run: options?.run, + })) as any; return { tool: schema as keyof ToToolMap, From 4359c17a1e0c244e518227dfa9d2a0cc18abb08b Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Sat, 12 Aug 2023 10:38:01 +0200 Subject: [PATCH 09/18] Add events to executeTool. --- src/run/RunFunctionEvent.ts | 12 +++++- src/tool/ExecuteToolEvent.ts | 26 ++++++++++++ src/tool/executeTool.ts | 76 +++++++++++++++++++++++++++++++++++- 3 files changed, 111 insertions(+), 3 deletions(-) create mode 100644 src/tool/ExecuteToolEvent.ts diff --git a/src/run/RunFunctionEvent.ts b/src/run/RunFunctionEvent.ts index 80b1695b6..962b5672c 100644 --- a/src/run/RunFunctionEvent.ts +++ b/src/run/RunFunctionEvent.ts @@ -1,3 +1,7 @@ +import { + ExecuteToolFinishedEvent, + ExecuteToolStartedEvent, +} from "tool/ExecuteToolEvent.js"; import { ModelCallFinishedEvent, ModelCallStartedEvent, @@ -12,11 +16,15 @@ export type RunFunctionStartedEventMetadata = IdMetadata & { startEpochSeconds: number; }; -export type RunFunctionStartedEvent = ModelCallStartedEvent; +export type RunFunctionStartedEvent = + | ModelCallStartedEvent + | ExecuteToolStartedEvent; export type RunFunctionFinishedEventMetadata = RunFunctionStartedEventMetadata & { durationInMs: number; }; -export type RunFunctionFinishedEvent = ModelCallFinishedEvent; +export type RunFunctionFinishedEvent = + | ModelCallFinishedEvent + | ExecuteToolFinishedEvent; diff --git a/src/tool/ExecuteToolEvent.ts b/src/tool/ExecuteToolEvent.ts new file mode 100644 index 000000000..10ba06f29 --- /dev/null +++ b/src/tool/ExecuteToolEvent.ts @@ -0,0 +1,26 @@ +import { + RunFunctionFinishedEventMetadata, + RunFunctionStartedEventMetadata, +} from "../run/RunFunctionEvent.js"; +import { Tool } from "./Tool.js"; + +export type ExecuteToolStartedEvent = { + type: "execute-tool-started"; + metadata: RunFunctionStartedEventMetadata; + tool: Tool; + input: unknown; +}; + +export type ExecuteToolFinishedEvent = { + type: "execute-tool-finished"; + metadata: RunFunctionFinishedEventMetadata; + tool: Tool; + input: unknown; +} & ( + | { + status: "success"; + output: unknown; + } + | { status: "failure"; error: unknown } + | { status: "abort" } +); diff --git a/src/tool/executeTool.ts b/src/tool/executeTool.ts index 1a665dc68..155ad54fc 100644 --- a/src/tool/executeTool.ts +++ b/src/tool/executeTool.ts @@ -1,4 +1,9 @@ +import { nanoid as createId } from "nanoid"; import { FunctionOptions } from "../model-function/FunctionOptions.js"; +import { RunFunctionEventSource } from "../run/RunFunctionEventSource.js"; +import { startDurationMeasurement } from "../util/DurationMeasurement.js"; +import { AbortError } from "../util/api/AbortError.js"; +import { runSafe } from "../util/runSafe.js"; import { Tool } from "./Tool.js"; export async function executeTool< @@ -10,5 +15,74 @@ export async function executeTool< input: INPUT, options?: FunctionOptions ): Promise { - return await tool.execute(input, options); + const run = options?.run; + + const eventSource = new RunFunctionEventSource({ + observers: run?.observers ?? [], + errorHandler: run?.errorHandler, + }); + + const durationMeasurement = startDurationMeasurement(); + + const startMetadata = { + callId: `call-${createId()}`, + runId: run?.runId, + sessionId: run?.sessionId, + userId: run?.userId, + functionId: options?.functionId, + startEpochSeconds: durationMeasurement.startEpochSeconds, + }; + + eventSource.notifyRunFunctionStarted({ + type: "execute-tool-started", + metadata: startMetadata, + tool: tool as Tool, + input, + }); + + const result = await runSafe(() => tool.execute(input, options)); + + const finishMetadata = { + ...startMetadata, + durationInMs: durationMeasurement.durationInMs, + }; + + if (!result.ok) { + if (result.isAborted) { + eventSource.notifyRunFunctionFinished({ + type: "execute-tool-finished", + status: "abort", + metadata: finishMetadata, + tool: tool as Tool, + input, + }); + + throw new AbortError(); + } + + eventSource.notifyRunFunctionFinished({ + type: "execute-tool-finished", + status: "failure", + metadata: finishMetadata, + tool: tool as Tool, + input, + error: result.error, + }); + + // TODO special ToolExecutionError + throw result.error; + } + + const output = result.output; + + eventSource.notifyRunFunctionFinished({ + type: "execute-tool-finished", + status: "success", + metadata: finishMetadata, + tool: tool as Tool, + input, + output, + }); + + return output; } From 478d5d1e9c61df3c118eafd26e1278145dc2bfe0 Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Sat, 12 Aug 2023 10:49:26 +0200 Subject: [PATCH 10/18] Introduce ToolExecutionError. --- src/tool/ToolExecutionError.ts | 24 ++++++++++++++++++++++++ src/tool/executeTool.ts | 10 ++++++++-- src/tool/index.ts | 1 + 3 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 src/tool/ToolExecutionError.ts diff --git a/src/tool/ToolExecutionError.ts b/src/tool/ToolExecutionError.ts new file mode 100644 index 000000000..b979a00e6 --- /dev/null +++ b/src/tool/ToolExecutionError.ts @@ -0,0 +1,24 @@ +export class ToolExecutionError extends Error { + readonly toolName: string; + readonly input: unknown; + readonly cause: unknown; + + constructor({ + message = "unknown error", + toolName, + input, + cause, + }: { + toolName: string; + input: unknown; + message: string | undefined; + cause: unknown | undefined; + }) { + super(`Error executing tool ${toolName}: ${message}`); + + this.name = "ToolExecutionError"; + this.toolName = toolName; + this.input = input; + this.cause = cause; + } +} diff --git a/src/tool/executeTool.ts b/src/tool/executeTool.ts index 155ad54fc..49e59b96a 100644 --- a/src/tool/executeTool.ts +++ b/src/tool/executeTool.ts @@ -5,6 +5,7 @@ import { startDurationMeasurement } from "../util/DurationMeasurement.js"; import { AbortError } from "../util/api/AbortError.js"; import { runSafe } from "../util/runSafe.js"; import { Tool } from "./Tool.js"; +import { ToolExecutionError } from "./ToolExecutionError.js"; export async function executeTool< INPUT, @@ -69,8 +70,13 @@ export async function executeTool< error: result.error, }); - // TODO special ToolExecutionError - throw result.error; + throw new ToolExecutionError({ + toolName: tool.name, + input, + cause: result.error, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + message: (result.error as any)?.message, + }); } const output = result.output; diff --git a/src/tool/index.ts b/src/tool/index.ts index d152401e0..cf5a654db 100644 --- a/src/tool/index.ts +++ b/src/tool/index.ts @@ -1,5 +1,6 @@ export * from "./NoSuchToolError.js"; export * from "./Tool.js"; +export * from "./ToolExecutionError.js"; export * from "./executeTool.js"; export * from "./useTool.js"; export * from "./useToolOrGenerateText.js"; From 826f9a8e2547c8570c05bd4ebd635b1e3bb925f5 Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Sat, 12 Aug 2023 10:58:54 +0200 Subject: [PATCH 11/18] Fix filter. --- src/model-function/SuccessfulModelCall.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/model-function/SuccessfulModelCall.ts b/src/model-function/SuccessfulModelCall.ts index 224fbcc4a..82e50b6d3 100644 --- a/src/model-function/SuccessfulModelCall.ts +++ b/src/model-function/SuccessfulModelCall.ts @@ -1,7 +1,5 @@ -import { - ModelCallFinishedEvent, - ModelCallStartedEvent, -} from "./ModelCallEvent.js"; +import { RunFunctionEvent } from "../run/RunFunctionEvent.js"; +import { ModelCallFinishedEvent } from "./ModelCallEvent.js"; import { ModelInformation } from "./ModelInformation.js"; export type SuccessfulModelCall = { @@ -19,12 +17,14 @@ export type SuccessfulModelCall = { }; export function extractSuccessfulModelCalls( - modelCallEvents: (ModelCallFinishedEvent | ModelCallStartedEvent)[] + runFunctionEvents: RunFunctionEvent[] ) { - return modelCallEvents + return runFunctionEvents .filter( (event): event is ModelCallFinishedEvent & { status: "success" } => - "status" in event && event.status === "success" + Object.keys(eventTypeToCostType).includes(event.type) && + "status" in event && + event.status === "success" ) .map( (event): SuccessfulModelCall => ({ From 6d0be6e8a0466d6fa8341d56d3e85b67fa31b804 Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Sat, 12 Aug 2023 11:04:20 +0200 Subject: [PATCH 12/18] Simplify generics. --- src/tool/executeTool.ts | 8 ++------ src/tool/useToolOrGenerateText.ts | 4 ++-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/tool/executeTool.ts b/src/tool/executeTool.ts index 49e59b96a..52ceca53d 100644 --- a/src/tool/executeTool.ts +++ b/src/tool/executeTool.ts @@ -7,12 +7,8 @@ import { runSafe } from "../util/runSafe.js"; import { Tool } from "./Tool.js"; import { ToolExecutionError } from "./ToolExecutionError.js"; -export async function executeTool< - INPUT, - OUTPUT, - TOOL extends Tool, ->( - tool: TOOL, +export async function executeTool( + tool: Tool, input: INPUT, options?: FunctionOptions ): Promise { diff --git a/src/tool/useToolOrGenerateText.ts b/src/tool/useToolOrGenerateText.ts index 4ee58330e..423221703 100644 --- a/src/tool/useToolOrGenerateText.ts +++ b/src/tool/useToolOrGenerateText.ts @@ -77,9 +77,9 @@ export async function useToolOrGenerateText< const toolParameters = modelResponse.value; - const result = (await executeTool(tool, toolParameters, { + const result = await executeTool(tool, toolParameters, { run: options?.run, - })) as any; + }); return { tool: schema as keyof ToToolMap, From 8bbbfec68ccbf6ab8bb5807abd6f48796f138e51 Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Sat, 12 Aug 2023 11:04:30 +0200 Subject: [PATCH 13/18] Move to tool. --- .../basic/src/{composed-function => tool}/use-tool-example.ts | 0 .../use-tool-or-generate-text-example.ts | 0 .../use-tool-or-generate-text-with-custom-prompt-example.ts | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename examples/basic/src/{composed-function => tool}/use-tool-example.ts (100%) rename examples/basic/src/{composed-function => tool}/use-tool-or-generate-text-example.ts (100%) rename examples/basic/src/{composed-function => tool}/use-tool-or-generate-text-with-custom-prompt-example.ts (100%) diff --git a/examples/basic/src/composed-function/use-tool-example.ts b/examples/basic/src/tool/use-tool-example.ts similarity index 100% rename from examples/basic/src/composed-function/use-tool-example.ts rename to examples/basic/src/tool/use-tool-example.ts diff --git a/examples/basic/src/composed-function/use-tool-or-generate-text-example.ts b/examples/basic/src/tool/use-tool-or-generate-text-example.ts similarity index 100% rename from examples/basic/src/composed-function/use-tool-or-generate-text-example.ts rename to examples/basic/src/tool/use-tool-or-generate-text-example.ts diff --git a/examples/basic/src/composed-function/use-tool-or-generate-text-with-custom-prompt-example.ts b/examples/basic/src/tool/use-tool-or-generate-text-with-custom-prompt-example.ts similarity index 100% rename from examples/basic/src/composed-function/use-tool-or-generate-text-with-custom-prompt-example.ts rename to examples/basic/src/tool/use-tool-or-generate-text-with-custom-prompt-example.ts From 17f8415bd612ec1895a1ca5f04d51762ec65ffca Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Sat, 12 Aug 2023 11:06:30 +0200 Subject: [PATCH 14/18] Add execute tool example. --- .../basic/src/tool/execute-tool-example.ts | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 examples/basic/src/tool/execute-tool-example.ts diff --git a/examples/basic/src/tool/execute-tool-example.ts b/examples/basic/src/tool/execute-tool-example.ts new file mode 100644 index 000000000..6d4859369 --- /dev/null +++ b/examples/basic/src/tool/execute-tool-example.ts @@ -0,0 +1,41 @@ +import dotenv from "dotenv"; +import { Tool, executeTool } from "modelfusion"; +import { z } from "zod"; + +dotenv.config(); + +(async () => { + const calculator = new Tool({ + name: "calculator" as const, // mark 'as const' for type inference + description: "Execute a calculation", + + inputSchema: z.object({ + a: z.number().describe("The first number."), + b: z.number().describe("The second number."), + operator: z.enum(["+", "-", "*", "/"]).describe("The operator."), + }), + + execute: async ({ a, b, operator }) => { + switch (operator) { + case "+": + return a + b; + case "-": + return a - b; + case "*": + return a * b; + case "/": + return a / b; + default: + throw new Error(`Unknown operator: ${operator}`); + } + }, + }); + + const result = await executeTool(calculator, { + a: 14, + b: 12, + operator: "*" as const, + }); + + console.log(`Result: ${result}`); +})(); From 2f2562c5320c573a9285d486b7aedddaca3b95a4 Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Sat, 12 Aug 2023 12:00:48 +0200 Subject: [PATCH 15/18] Add fullResponse option. --- .../src/tool/execute-tool-full-example.ts | 46 +++++++++++++++++++ src/tool/executeTool.ts | 46 +++++++++++++++++-- 2 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 examples/basic/src/tool/execute-tool-full-example.ts diff --git a/examples/basic/src/tool/execute-tool-full-example.ts b/examples/basic/src/tool/execute-tool-full-example.ts new file mode 100644 index 000000000..563def2ee --- /dev/null +++ b/examples/basic/src/tool/execute-tool-full-example.ts @@ -0,0 +1,46 @@ +import dotenv from "dotenv"; +import { Tool, executeTool } from "modelfusion"; +import { z } from "zod"; + +dotenv.config(); + +(async () => { + const calculator = new Tool({ + name: "calculator" as const, // mark 'as const' for type inference + description: "Execute a calculation", + + inputSchema: z.object({ + a: z.number().describe("The first number."), + b: z.number().describe("The second number."), + operator: z.enum(["+", "-", "*", "/"]).describe("The operator."), + }), + + execute: async ({ a, b, operator }) => { + switch (operator) { + case "+": + return a + b; + case "-": + return a - b; + case "*": + return a * b; + case "/": + return a / b; + default: + throw new Error(`Unknown operator: ${operator}`); + } + }, + }); + + const result = await executeTool( + calculator, + { + a: 14, + b: 12, + operator: "*" as const, + }, + { fullResponse: true } + ); + + console.log(`Result: ${result.output}`); + console.log(`Duration: ${result.metadata.durationInMs}ms`); +})(); diff --git a/src/tool/executeTool.ts b/src/tool/executeTool.ts index 52ceca53d..694a6da7b 100644 --- a/src/tool/executeTool.ts +++ b/src/tool/executeTool.ts @@ -7,11 +7,46 @@ import { runSafe } from "../util/runSafe.js"; import { Tool } from "./Tool.js"; import { ToolExecutionError } from "./ToolExecutionError.js"; +export type ExecuteToolMetadata = { + callId: string; + runId?: string; + sessionId?: string; + userId?: string; + functionId?: string; + startEpochSeconds: number; + durationInMs: number; +}; + +export async function executeTool( + tool: Tool, + input: INPUT, + options: FunctionOptions & { + fullResponse: true; + } +): Promise<{ + output: OUTPUT; + metadata: ExecuteToolMetadata; +}>; +export async function executeTool( + tool: Tool, + input: INPUT, + options?: FunctionOptions & { + fullResponse?: false; + } +): Promise; export async function executeTool( tool: Tool, input: INPUT, - options?: FunctionOptions -): Promise { + options?: FunctionOptions & { + fullResponse?: boolean; + } +): Promise< + | OUTPUT + | { + output: OUTPUT; + metadata: ExecuteToolMetadata; + } +> { const run = options?.run; const eventSource = new RunFunctionEventSource({ @@ -86,5 +121,10 @@ export async function executeTool( output, }); - return output; + return options?.fullResponse === true + ? { + output, + metadata: finishMetadata, + } + : output; } From c46fb44b42c048afc28dfcefe5adc28a90a218e3 Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Sat, 12 Aug 2023 12:01:16 +0200 Subject: [PATCH 16/18] Tweak example. --- examples/basic/src/tool/execute-tool-full-example.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/basic/src/tool/execute-tool-full-example.ts b/examples/basic/src/tool/execute-tool-full-example.ts index 563def2ee..44832e4f2 100644 --- a/examples/basic/src/tool/execute-tool-full-example.ts +++ b/examples/basic/src/tool/execute-tool-full-example.ts @@ -31,7 +31,7 @@ dotenv.config(); }, }); - const result = await executeTool( + const { metadata, output } = await executeTool( calculator, { a: 14, @@ -41,6 +41,6 @@ dotenv.config(); { fullResponse: true } ); - console.log(`Result: ${result.output}`); - console.log(`Duration: ${result.metadata.durationInMs}ms`); + console.log(`Result: ${output}`); + console.log(`Duration: ${metadata.durationInMs}ms`); })(); From 92c8d36e2be6abbd65e5a13913ff18d97f697b29 Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Sat, 12 Aug 2023 12:04:34 +0200 Subject: [PATCH 17/18] Expose RunFunctionEvent. --- src/run/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/run/index.ts b/src/run/index.ts index b9f0045fa..500569290 100644 --- a/src/run/index.ts +++ b/src/run/index.ts @@ -3,4 +3,7 @@ export * from "./DefaultRun.js"; export * from "./IdMetadata.js"; export * from "./Run.js"; export * from "./RunFunction.js"; +export * from "./RunFunctionEvent.js"; +export * from "./RunFunctionObserver.js"; +export * from "./RunFunctionEventSource.js"; export * from "./Vector.js"; From dbea4f8f5df37fc7f256e58dbb7e76a0408f99e2 Mon Sep 17 00:00:00 2001 From: Lars Grammel Date: Sat, 12 Aug 2023 12:05:26 +0200 Subject: [PATCH 18/18] Fix example. --- .../recipes/information-extraction-openai-chat-functions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/basic/src/recipes/information-extraction-openai-chat-functions.ts b/examples/basic/src/recipes/information-extraction-openai-chat-functions.ts index a3f07b4d1..2c0a3f7a1 100644 --- a/examples/basic/src/recipes/information-extraction-openai-chat-functions.ts +++ b/examples/basic/src/recipes/information-extraction-openai-chat-functions.ts @@ -49,13 +49,13 @@ dotenv.config(); fs.readFileSync("data/san-francisco-wikipedia.json", "utf8") ).content; - const { value: extractedInformation1 } = await extractNameAndPopulation( + const extractedInformation1 = await extractNameAndPopulation( sanFranciscoWikipedia.slice(0, 2000) ); console.log(extractedInformation1); - const { value: extractedInformation2 } = await extractNameAndPopulation( + const extractedInformation2 = await extractNameAndPopulation( "Carl was a friendly robot." );