kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
feat: refactoring core
rodzic
8c7b34f0dc
commit
ed4ea41c51
|
@ -6,4 +6,3 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
|
||||
OPENAI_API_KEY=
|
||||
METAPHOR_API_KEY=
|
|
@ -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 })
|
||||
|
|
|
@ -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 })
|
||||
|
|
|
@ -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) =>
|
||||
|
|
|
@ -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 })
|
||||
|
|
|
@ -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 })
|
||||
|
|
|
@ -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 })
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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 })
|
||||
|
|
|
@ -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 })
|
||||
|
|
|
@ -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 })
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
46
src/llm.ts
46
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<string>,
|
||||
TModelParams extends Record<string, any> = Record<string, any>
|
||||
> extends BaseTaskCallBuilder<TInput, TOutput> {
|
||||
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<TInput, TOutput, TModelParams>) {
|
||||
constructor(
|
||||
options: SetRequired<
|
||||
types.BaseLLMOptions<TInput, TOutput, TModelParams>,
|
||||
'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<TInput, U, TModelParams>
|
||||
}
|
||||
|
||||
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<TModelParams>) {
|
||||
// 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<TInput, TOutput, TModelParams> {
|
||||
_messages: types.ChatMessage[]
|
||||
|
||||
constructor(options: types.ChatModelOptions<TInput, TOutput, TModelParams>) {
|
||||
constructor(
|
||||
options: SetRequired<
|
||||
types.ChatModelOptions<TInput, TOutput, TModelParams>,
|
||||
'provider' | 'model' | 'messages'
|
||||
>
|
||||
) {
|
||||
super(options)
|
||||
|
||||
this._messages = options.messages
|
||||
|
|
|
@ -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 extends true> = T
|
||||
type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y
|
||||
? 1
|
||||
: 2
|
||||
? true
|
||||
: false
|
|
@ -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<string>
|
||||
TInput extends ZodTypeAny = ZodTypeAny,
|
||||
TOutput extends ZodTypeAny = z.ZodType<string>
|
||||
> extends BaseChatModelBuilder<
|
||||
TInput,
|
||||
TOutput,
|
||||
|
@ -39,8 +39,8 @@ export class OpenAIChatModelBuilder<
|
|||
types.BaseChatCompletionResponse<types.openai.ChatCompletionResponse>
|
||||
> {
|
||||
return this._client.createChatCompletion({
|
||||
model: this._model,
|
||||
...this._modelParams,
|
||||
model: this._model,
|
||||
messages
|
||||
})
|
||||
}
|
||||
|
|
23
src/task.ts
23
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<TInput, TOutput>) {
|
||||
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<TInput>
|
||||
): Promise<types.ParsedData<TOutput>>
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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<MetaphorSearchToolOutput> {
|
||||
// TODO: handle errors gracefully
|
||||
input = this._inputSchema.parse(input)
|
||||
input = this.inputSchema.parse(input)
|
||||
|
||||
return this._metaphorClient.search({
|
||||
query: input.query,
|
||||
|
|
13
src/types.ts
13
src/types.ts
|
@ -24,13 +24,7 @@ export type SafeParsedData<T extends ZodRawShape | ZodTypeAny> =
|
|||
? SafeParseReturnType<ZodObject<T>, ParsedData<T>>
|
||||
: never
|
||||
|
||||
export interface BaseTaskOptions<
|
||||
TInput extends ZodRawShape | ZodTypeAny = ZodTypeAny,
|
||||
TOutput extends ZodRawShape | ZodTypeAny = z.ZodType<string>
|
||||
> {
|
||||
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<string>,
|
||||
TModelParams extends Record<string, any> = Record<string, any>
|
||||
> extends BaseTaskOptions<TInput, TOutput> {
|
||||
> extends BaseTaskOptions {
|
||||
inputSchema?: TInput
|
||||
outputSchema?: TOutput
|
||||
|
||||
provider?: string
|
||||
model?: string
|
||||
modelParams?: TModelParams
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
"lib": ["esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": false,
|
||||
"strict": true,
|
||||
"noImplicitAny": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
|
|
Ładowanie…
Reference in New Issue