diff --git a/legacy/.env.example b/legacy/.env.example index f3ee74f0..bff4b1c3 100644 --- a/legacy/.env.example +++ b/legacy/.env.example @@ -6,4 +6,3 @@ # ------------------------------------------------------------------------------ OPENAI_API_KEY= -METAPHOR_API_KEY= \ No newline at end of file diff --git a/legacy/examples/basic.ts b/legacy/examples/basic.ts index 090ac0d6..6873babd 100644 --- a/legacy/examples/basic.ts +++ b/legacy/examples/basic.ts @@ -1,11 +1,9 @@ -import dotenv from 'dotenv-safe' +import 'dotenv/config' import { OpenAIClient } from 'openai-fetch' import { z } from 'zod' import { Agentic } from '../src' -dotenv.config() - async function main() { const openai = new OpenAIClient({ apiKey: process.env.OPENAI_API_KEY! }) const $ = new Agentic({ openai }) diff --git a/legacy/examples/calc-eval.ts b/legacy/examples/calc-eval.ts index 5c3f6658..cab812e2 100644 --- a/legacy/examples/calc-eval.ts +++ b/legacy/examples/calc-eval.ts @@ -1,12 +1,10 @@ -import dotenv from 'dotenv-safe' +import 'dotenv/config' import { OpenAIClient } from 'openai-fetch' import { z } from 'zod' -import { Agentic } from '../src/llm' +import { Agentic } from '../src' import { getProblems } from './fixtures/calc' -dotenv.config() - export async function calcEval() { const openai = new OpenAIClient({ apiKey: process.env.OPENAI_API_KEY! }) const $ = new Agentic({ openai }) diff --git a/legacy/examples/equation-producer.ts b/legacy/examples/equation-producer.ts index 2cfa1acc..2f3362aa 100644 --- a/legacy/examples/equation-producer.ts +++ b/legacy/examples/equation-producer.ts @@ -1,11 +1,9 @@ -import dotenv from 'dotenv-safe' +import 'dotenv/config' import { OpenAIClient } from 'openai-fetch' import { z } from 'zod' import { Agentic } from '../src' -dotenv.config() - export async function equationProducer() { const openai = new OpenAIClient({ apiKey: process.env.OPENAI_API_KEY! }) const $ = new Agentic({ openai }) @@ -31,11 +29,13 @@ export async function equationProducer() { `You are an expert math teacher. Think step by step, and give me the equation for the following math problem: \n\n{{question}}` ) .input(z.object({ question: z.string() })) - .output({ - question: z.string(), - equation: z.string(), - predictedAnswer: z.number() - }) + .output( + z.object({ + question: z.string(), + equation: z.string(), + predictedAnswer: z.number() + }) + ) .examples(examples) // .assert( // (output) => diff --git a/legacy/examples/facts.ts b/legacy/examples/facts.ts index 979f73b7..4003fe5d 100644 --- a/legacy/examples/facts.ts +++ b/legacy/examples/facts.ts @@ -1,11 +1,9 @@ -import dotenv from 'dotenv-safe' +import 'dotenv/config' import { OpenAIClient } from 'openai-fetch' import { z } from 'zod' import { Agentic } from '../src' -dotenv.config() - async function main() { const openai = new OpenAIClient({ apiKey: process.env.OPENAI_API_KEY! }) const ai = new Agentic({ openai }) diff --git a/legacy/examples/food-expert.ts b/legacy/examples/food-expert.ts index 8f3eeefd..956eff67 100644 --- a/legacy/examples/food-expert.ts +++ b/legacy/examples/food-expert.ts @@ -1,12 +1,10 @@ -import dotenv from 'dotenv-safe' +import 'dotenv/config' import { OpenAIClient } from 'openai-fetch' import { z } from 'zod' import { Agentic } from '../src' import { summaryAgent } from './summary' -dotenv.config() - async function main() { const openai = new OpenAIClient({ apiKey: process.env.OPENAI_API_KEY! }) const $ = new Agentic({ openai }) diff --git a/legacy/examples/json-summary.ts b/legacy/examples/json-summary.ts index 321ddf01..cc95c0b6 100644 --- a/legacy/examples/json-summary.ts +++ b/legacy/examples/json-summary.ts @@ -1,11 +1,9 @@ -import dotenv from 'dotenv-safe' +import 'dotenv/config' import { OpenAIClient } from 'openai-fetch' import { z } from 'zod' import { Agentic } from '../src' -dotenv.config() - export async function main() { const openai = new OpenAIClient({ apiKey: process.env.OPENAI_API_KEY! }) const $ = new Agentic({ openai }) diff --git a/legacy/examples/llm-with-search.ts b/legacy/examples/llm-with-search.ts index a32309d2..ec7c7ca1 100644 --- a/legacy/examples/llm-with-search.ts +++ b/legacy/examples/llm-with-search.ts @@ -1,11 +1,9 @@ -import dotenv from 'dotenv-safe' +import 'dotenv/config' import { OpenAIClient } from 'openai-fetch' import { z } from 'zod' import { Agentic, MetaphorSearchTool } from '../src' -dotenv.config() - async function main() { const metaphorSearch = new MetaphorSearchTool() diff --git a/legacy/examples/misc.ts b/legacy/examples/misc.ts index 8a533d4e..37afdf38 100644 --- a/legacy/examples/misc.ts +++ b/legacy/examples/misc.ts @@ -1,11 +1,9 @@ -import dotenv from 'dotenv-safe' +import 'dotenv/config' import { OpenAIClient } from 'openai-fetch' import { z } from 'zod' import { Agentic } from '../src' -dotenv.config() - async function main() { const openai = new OpenAIClient({ apiKey: process.env.OPENAI_API_KEY! }) const $ = new Agentic({ openai }) diff --git a/legacy/examples/sentiment.ts b/legacy/examples/sentiment.ts index f88310dc..b36e3cdb 100644 --- a/legacy/examples/sentiment.ts +++ b/legacy/examples/sentiment.ts @@ -1,11 +1,9 @@ -import dotenv from 'dotenv-safe' +import 'dotenv/config' import { OpenAIClient } from 'openai-fetch' import { z } from 'zod' import { Agentic } from '../src' -dotenv.config() - export async function main() { const openai = new OpenAIClient({ apiKey: process.env.OPENAI_API_KEY! }) const $ = new Agentic({ openai }) diff --git a/legacy/examples/tools.ts b/legacy/examples/tools.ts index 10592ebc..5072f4f8 100644 --- a/legacy/examples/tools.ts +++ b/legacy/examples/tools.ts @@ -1,11 +1,9 @@ -import dotenv from 'dotenv-safe' +import 'dotenv/config' import { OpenAIClient } from 'openai-fetch' import { z } from 'zod' import { Agentic, MetaphorSearchTool } from '../src' -dotenv.config() - async function main() { const openai = new OpenAIClient({ apiKey: process.env.OPENAI_API_KEY! }) const $ = new Agentic({ openai }) diff --git a/legacy/package.json b/legacy/package.json index 423b15f3..7f972a1d 100644 --- a/legacy/package.json +++ b/legacy/package.json @@ -35,9 +35,9 @@ "test:prettier": "prettier '**/*.{js,jsx,ts,tsx}' --check" }, "dependencies": { - "@dqbd/tiktoken": "^1.0.7", - "dotenv-safe": "^8.2.0", + "dotenv": "^16.1.3", "handlebars": "^4.7.7", + "js-tiktoken": "^1.0.6", "jsonrepair": "^3.1.0", "ky": "^0.33.3", "openai-fetch": "^1.3.0", diff --git a/legacy/pnpm-lock.yaml b/legacy/pnpm-lock.yaml index 8b172185..03af3b0e 100644 --- a/legacy/pnpm-lock.yaml +++ b/legacy/pnpm-lock.yaml @@ -1,15 +1,15 @@ lockfileVersion: '6.0' dependencies: - '@dqbd/tiktoken': - specifier: ^1.0.7 - version: 1.0.7 - dotenv-safe: - specifier: ^8.2.0 - version: 8.2.0 + dotenv: + specifier: ^16.1.3 + version: 16.1.3 handlebars: specifier: ^4.7.7 version: 4.7.7 + js-tiktoken: + specifier: ^1.0.6 + version: 1.0.6 jsonrepair: specifier: ^3.1.0 version: 3.1.0 @@ -192,10 +192,6 @@ packages: to-fast-properties: 2.0.0 dev: true - /@dqbd/tiktoken@1.0.7: - resolution: {integrity: sha512-bhR5k5W+8GLzysjk8zTMVygQZsgvf7W1F0IlL4ZQ5ugjo5rCyiwGM5d8DYriXspytfu98tv59niang3/T+FoDw==} - dev: false - /@esbuild-kit/cjs-loader@2.4.2: resolution: {integrity: sha512-BDXFbYOJzT/NBEtp71cvsrGPwGAMGRB/349rwKuoxNSiKjPraNNnlK6MIIabViCjqZugu6j+xeMDlEkWdHHJSg==} dependencies: @@ -686,6 +682,10 @@ packages: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} dev: true + /base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + dev: false + /binary-extensions@2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} @@ -1024,15 +1024,9 @@ packages: path-type: 4.0.0 dev: true - /dotenv-safe@8.2.0: - resolution: {integrity: sha512-uWwWWdUQkSs5a3mySDB22UtNwyEYi0JtEQu+vDzIqr9OjbDdC2Ip13PnSpi/fctqlYmzkxCeabiyCAOROuAIaA==} - dependencies: - dotenv: 8.6.0 - dev: false - - /dotenv@8.6.0: - resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==} - engines: {node: '>=10'} + /dotenv@16.1.3: + resolution: {integrity: sha512-FYssxsmCTtKL72fGBSvb1K9dRz0/VZeWqFme/vSb7r7323x4CRaHu4LvQ5JG3+s6yt2YPbBrkpiEODktfyjI9A==} + engines: {node: '>=12'} dev: false /eastasianwidth@0.2.0: @@ -1730,6 +1724,12 @@ packages: engines: {node: '>= 0.8'} dev: true + /js-tiktoken@1.0.6: + resolution: {integrity: sha512-lxHntEupgjWvSh37WxpAW4XN6UBXBtFJOpZZq5HN5oNjDfN7L/iJhHOKjyL/DFtuYXUwn5jfTciLtOWpgQmHjQ==} + dependencies: + base64-js: 1.5.1 + dev: false + /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} dev: true diff --git a/legacy/src/llm.ts b/legacy/src/llm.ts index bc07719a..248ddf11 100644 --- a/legacy/src/llm.ts +++ b/legacy/src/llm.ts @@ -1,5 +1,6 @@ import { jsonrepair } from 'jsonrepair' import { dedent } from 'ts-dedent' +import { type SetRequired } from 'type-fest' import { ZodRawShape, ZodTypeAny, z } from 'zod' import { printNode, zodToTs } from 'zod-to-ts' @@ -12,18 +13,29 @@ import { } from './utils' export abstract class BaseLLMCallBuilder< - TInput extends ZodRawShape | ZodTypeAny = ZodTypeAny, + TInput extends ZodRawShape | ZodTypeAny = z.ZodVoid, TOutput extends ZodRawShape | ZodTypeAny = z.ZodType, TModelParams extends Record = Record > extends BaseTaskCallBuilder { + protected _inputSchema: TInput | undefined + protected _outputSchema: TOutput | undefined + protected _provider: string protected _model: string - protected _modelParams: TModelParams - protected _examples: types.LLMExample[] + protected _modelParams: TModelParams | undefined + protected _examples: types.LLMExample[] | undefined - constructor(options: types.BaseLLMOptions) { + constructor( + options: SetRequired< + types.BaseLLMOptions, + 'provider' | 'model' + > + ) { super(options) + this._inputSchema = options.inputSchema + this._outputSchema = options.outputSchema + this._provider = options.provider this._model = options.model this._modelParams = options.modelParams @@ -48,6 +60,23 @@ export abstract class BaseLLMCallBuilder< return this as unknown as BaseLLMCallBuilder } + public override get inputSchema(): TInput { + if (this._inputSchema) { + return this._inputSchema + } else { + return z.void() as TInput + } + } + + public override get outputSchema(): TOutput { + if (this._outputSchema) { + return this._outputSchema + } else { + // TODO: improve typing + return z.string() as unknown as TOutput + } + } + examples(examples: types.LLMExample[]) { this._examples = examples return this @@ -56,7 +85,7 @@ export abstract class BaseLLMCallBuilder< modelParams(params: Partial) { // We assume that modelParams does not include nested objects. // If it did, we would need to do a deep merge. - this._modelParams = Object.assign({}, this._modelParams, params) + this._modelParams = { ...this._modelParams, ...params } as TModelParams return this } @@ -75,7 +104,12 @@ export abstract class BaseChatModelBuilder< > extends BaseLLMCallBuilder { _messages: types.ChatMessage[] - constructor(options: types.ChatModelOptions) { + constructor( + options: SetRequired< + types.ChatModelOptions, + 'provider' | 'model' | 'messages' + > + ) { super(options) this._messages = options.messages diff --git a/legacy/src/openai.test.ts b/legacy/src/openai.test.ts new file mode 100644 index 00000000..bb2a6c77 --- /dev/null +++ b/legacy/src/openai.test.ts @@ -0,0 +1,38 @@ +import test from 'ava' +import { OpenAIClient } from 'openai-fetch' +import { z } from 'zod' + +import { OpenAIChatModelBuilder } from './openai' + +const client = new OpenAIClient({ apiKey: process.env.OPENAI_API_KEY! }) + +test('OpenAIChatModel ⇒ json output', async (t) => { + const builder = new OpenAIChatModelBuilder(client, { + messages: [ + { + role: 'user', + content: 'generate fake data' + } + ] + }).output(z.object({ foo: z.string(), bar: z.number() })) + + const result = await builder.call() + type verify = Expect< + Equal< + typeof result, + { + foo: string + bar: number + } + > + > +}) + +// Ensure parsed results are typed correctly +// https://github.com/total-typescript/zod-tutorial/blob/main/src/helpers/type-utils.ts +type Expect = T +type Equal = (() => T extends X ? 1 : 2) extends () => T extends Y + ? 1 + : 2 + ? true + : false diff --git a/legacy/src/openai.ts b/legacy/src/openai.ts index e9b5bb48..02fd9d90 100644 --- a/legacy/src/openai.ts +++ b/legacy/src/openai.ts @@ -1,13 +1,13 @@ -import type { SetRequired } from 'type-fest' -import { ZodRawShape, ZodTypeAny, z } from 'zod' +import { type SetRequired } from 'type-fest' +import { ZodTypeAny, z } from 'zod' import * as types from './types' import { defaultOpenAIModel } from './constants' import { BaseChatModelBuilder } from './llm' export class OpenAIChatModelBuilder< - TInput extends ZodRawShape | ZodTypeAny = ZodTypeAny, - TOutput extends ZodRawShape | ZodTypeAny = z.ZodType + TInput extends ZodTypeAny = ZodTypeAny, + TOutput extends ZodTypeAny = z.ZodType > extends BaseChatModelBuilder< TInput, TOutput, @@ -39,8 +39,8 @@ export class OpenAIChatModelBuilder< types.BaseChatCompletionResponse > { return this._client.createChatCompletion({ - model: this._model, ...this._modelParams, + model: this._model, messages }) } diff --git a/legacy/src/task.ts b/legacy/src/task.ts index 06133b18..7c52ef21 100644 --- a/legacy/src/task.ts +++ b/legacy/src/task.ts @@ -1,3 +1,4 @@ +import { type SetRequired } from 'type-fest' import { ZodRawShape, ZodTypeAny, z } from 'zod' import * as types from './types' @@ -15,32 +16,24 @@ export abstract class BaseTaskCallBuilder< TInput extends ZodRawShape | ZodTypeAny = ZodTypeAny, TOutput extends ZodRawShape | ZodTypeAny = z.ZodTypeAny > { - protected _inputSchema: TInput - protected _outputSchema: TOutput - protected _timeoutMs: number - protected _retryConfig: types.RetryConfig + protected _timeoutMs: number | undefined + protected _retryConfig: types.RetryConfig | undefined - constructor(options: types.BaseTaskOptions) { - this._inputSchema = options.inputSchema - this._outputSchema = options.outputSchema + constructor(options: types.BaseTaskOptions) { this._timeoutMs = options.timeoutMs this._retryConfig = options.retryConfig } - public get inputSchema(): TInput { - return this._inputSchema - } + public abstract get inputSchema(): TInput - public get outputSchema(): TOutput { - return this._outputSchema - } + public abstract get outputSchema(): TOutput - retry(retryConfig: types.RetryConfig) { + public retryConfig(retryConfig: types.RetryConfig) { this._retryConfig = retryConfig return this } - abstract call( + public abstract call( input?: types.ParsedData ): Promise> diff --git a/legacy/src/tokenizer.ts b/legacy/src/tokenizer.ts index ba989046..24b80811 100644 --- a/legacy/src/tokenizer.ts +++ b/legacy/src/tokenizer.ts @@ -1,5 +1,6 @@ -import { encoding_for_model } from '@dqbd/tiktoken' +import { getEncoding, getEncodingNameForModel } from 'js-tiktoken' export function getTokenizerForModel(model: string) { - return encoding_for_model(model as any) + const encodingName = getEncodingNameForModel(model as any) + return getEncoding(encodingName) } diff --git a/legacy/src/tools/metaphor.ts b/legacy/src/tools/metaphor.ts index 4f5666ba..bcfc72c9 100644 --- a/legacy/src/tools/metaphor.ts +++ b/legacy/src/tools/metaphor.ts @@ -40,18 +40,25 @@ export class MetaphorSearchTool extends BaseTaskCallBuilder< metaphorClient?: MetaphorClient } = {}) { super({ - inputSchema: MetaphorSearchToolInputSchema, - outputSchema: MetaphorSearchToolOutputSchema + // TODO }) this._metaphorClient = metaphorClient } + public override get inputSchema() { + return MetaphorSearchToolInputSchema + } + + public override get outputSchema() { + return MetaphorSearchToolOutputSchema + } + override async call( input: MetaphorSearchToolInput ): Promise { // TODO: handle errors gracefully - input = this._inputSchema.parse(input) + input = this.inputSchema.parse(input) return this._metaphorClient.search({ query: input.query, diff --git a/legacy/src/types.ts b/legacy/src/types.ts index 5b8c2171..8bdc4bf2 100644 --- a/legacy/src/types.ts +++ b/legacy/src/types.ts @@ -24,13 +24,7 @@ export type SafeParsedData = ? SafeParseReturnType, ParsedData> : never -export interface BaseTaskOptions< - TInput extends ZodRawShape | ZodTypeAny = ZodTypeAny, - TOutput extends ZodRawShape | ZodTypeAny = z.ZodType -> { - inputSchema?: TInput - outputSchema?: TOutput - +export interface BaseTaskOptions { timeoutMs?: number retryConfig?: RetryConfig @@ -44,7 +38,10 @@ export interface BaseLLMOptions< TInput extends ZodRawShape | ZodTypeAny = ZodTypeAny, TOutput extends ZodRawShape | ZodTypeAny = z.ZodType, TModelParams extends Record = Record -> extends BaseTaskOptions { +> extends BaseTaskOptions { + inputSchema?: TInput + outputSchema?: TOutput + provider?: string model?: string modelParams?: TModelParams diff --git a/legacy/tsconfig.json b/legacy/tsconfig.json index 9c6127d4..cba07bfa 100644 --- a/legacy/tsconfig.json +++ b/legacy/tsconfig.json @@ -4,7 +4,8 @@ "lib": ["esnext"], "allowJs": true, "skipLibCheck": true, - "strict": false, + "strict": true, + "noImplicitAny": false, "forceConsistentCasingInFileNames": true, "esModuleInterop": true, "module": "esnext",