kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
feat: minor tool improvements
rodzic
f95dd54436
commit
e41fb75127
|
@ -1,3 +1,5 @@
|
||||||
|
import defaultKy from 'ky'
|
||||||
|
|
||||||
import * as types from './types'
|
import * as types from './types'
|
||||||
import { DEFAULT_OPENAI_MODEL } from './constants'
|
import { DEFAULT_OPENAI_MODEL } from './constants'
|
||||||
import {
|
import {
|
||||||
|
@ -9,6 +11,7 @@ import { defaultIDGeneratorFn } from './utils'
|
||||||
|
|
||||||
export class Agentic {
|
export class Agentic {
|
||||||
// _taskMap: WeakMap<string, BaseTask<any, any>>
|
// _taskMap: WeakMap<string, BaseTask<any, any>>
|
||||||
|
protected _ky: types.KyInstance
|
||||||
|
|
||||||
protected _openai?: types.openai.OpenAIClient
|
protected _openai?: types.openai.OpenAIClient
|
||||||
protected _anthropic?: types.anthropic.Client
|
protected _anthropic?: types.anthropic.Client
|
||||||
|
@ -33,13 +36,18 @@ export class Agentic {
|
||||||
>
|
>
|
||||||
defaultHumanFeedbackMechanism?: HumanFeedbackMechanism
|
defaultHumanFeedbackMechanism?: HumanFeedbackMechanism
|
||||||
idGeneratorFn?: types.IDGeneratorFunction
|
idGeneratorFn?: types.IDGeneratorFunction
|
||||||
|
ky?: types.KyInstance
|
||||||
}) {
|
}) {
|
||||||
|
// TODO: This is a bit hacky, but we're doing it to have a slightly nicer API
|
||||||
|
// for the end developer when creating subclasses of `BaseTask` to use as
|
||||||
|
// tools.
|
||||||
if (!globalThis.__agentic?.deref()) {
|
if (!globalThis.__agentic?.deref()) {
|
||||||
globalThis.__agentic = new WeakRef(this)
|
globalThis.__agentic = new WeakRef(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
this._openai = opts.openai
|
this._openai = opts.openai
|
||||||
this._anthropic = opts.anthropic
|
this._anthropic = opts.anthropic
|
||||||
|
this._ky = opts.ky ?? defaultKy
|
||||||
|
|
||||||
this._verbosity = opts.verbosity ?? 0
|
this._verbosity = opts.verbosity ?? 0
|
||||||
|
|
||||||
|
@ -75,6 +83,10 @@ export class Agentic {
|
||||||
return this._anthropic
|
return this._anthropic
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get ky(): types.KyInstance {
|
||||||
|
return this._ky
|
||||||
|
}
|
||||||
|
|
||||||
public get defaultHumanFeedbackMechamism() {
|
public get defaultHumanFeedbackMechamism() {
|
||||||
return this._defaultHumanFeedbackMechamism
|
return this._defaultHumanFeedbackMechamism
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
export * from './agentic'
|
export * from './agentic'
|
||||||
export * from './task'
|
export * from './task'
|
||||||
|
export * from './constants'
|
||||||
export * from './errors'
|
export * from './errors'
|
||||||
export * from './tokenizer'
|
export * from './tokenizer'
|
||||||
export * from './human-feedback'
|
export * from './human-feedback'
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import pMap from 'p-map'
|
import pMap from 'p-map'
|
||||||
import { zodToJsonSchema } from 'zod-to-json-schema'
|
import { ZodTypeAny } from 'zod'
|
||||||
|
import { zodToJsonSchema as zodToJsonSchemaImpl } from 'zod-to-json-schema'
|
||||||
|
|
||||||
import * as types from '@/types'
|
import * as types from '@/types'
|
||||||
import { BaseTask } from '@/task'
|
import { BaseTask } from '@/task'
|
||||||
|
@ -80,15 +81,14 @@ export async function getNumTokensForChatMessages({
|
||||||
return { numTokensTotal, numTokensPerMessage }
|
return { numTokensTotal, numTokensPerMessage }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getChatMessageFunctionDefinitionFromTask(
|
export function zodToJsonSchema({
|
||||||
task: BaseTask<any, any>
|
zodSchema,
|
||||||
): types.openai.ChatMessageFunction {
|
name
|
||||||
const name = task.nameForModel
|
}: {
|
||||||
if (!isValidTaskIdentifier(name)) {
|
zodSchema: ZodTypeAny
|
||||||
throw new Error(`Invalid task name "${name}"`)
|
name: string
|
||||||
}
|
}): any {
|
||||||
|
const jsonSchema = zodToJsonSchemaImpl(zodSchema, {
|
||||||
const jsonSchema = zodToJsonSchema(task.inputSchema, {
|
|
||||||
name,
|
name,
|
||||||
$refStrategy: 'none'
|
$refStrategy: 'none'
|
||||||
})
|
})
|
||||||
|
@ -100,6 +100,19 @@ export function getChatMessageFunctionDefinitionFromTask(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return parameters
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getChatMessageFunctionDefinitionFromTask(
|
||||||
|
task: BaseTask<any, any>
|
||||||
|
): types.openai.ChatMessageFunction {
|
||||||
|
const name = task.nameForModel
|
||||||
|
if (!isValidTaskIdentifier(name)) {
|
||||||
|
throw new Error(`Invalid task name "${name}"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const parameters = zodToJsonSchema({ zodSchema: task.inputSchema, name })
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
description: task.descForModel || task.nameForHuman,
|
description: task.descForModel || task.nameForHuman,
|
||||||
|
|
|
@ -8,15 +8,16 @@ export class MetaphorSearchTool extends BaseTask<
|
||||||
> {
|
> {
|
||||||
protected _metaphorClient: metaphor.MetaphorClient
|
protected _metaphorClient: metaphor.MetaphorClient
|
||||||
|
|
||||||
constructor({
|
constructor(
|
||||||
metaphorClient = new metaphor.MetaphorClient(),
|
opts: {
|
||||||
...opts
|
metaphorClient?: metaphor.MetaphorClient
|
||||||
}: {
|
} & types.BaseTaskOptions = {}
|
||||||
metaphorClient?: metaphor.MetaphorClient
|
) {
|
||||||
} & types.BaseTaskOptions = {}) {
|
|
||||||
super(opts)
|
super(opts)
|
||||||
|
|
||||||
this._metaphorClient = metaphorClient
|
this._metaphorClient =
|
||||||
|
opts.metaphorClient ??
|
||||||
|
new metaphor.MetaphorClient({ ky: opts.agentic?.ky })
|
||||||
}
|
}
|
||||||
|
|
||||||
public override get inputSchema() {
|
public override get inputSchema() {
|
||||||
|
|
|
@ -40,15 +40,15 @@ export class NovuNotificationTool extends BaseTask<
|
||||||
> {
|
> {
|
||||||
protected _novuClient: NovuClient
|
protected _novuClient: NovuClient
|
||||||
|
|
||||||
constructor({
|
constructor(
|
||||||
novuClient = new NovuClient(),
|
opts: {
|
||||||
...opts
|
novuClient?: NovuClient
|
||||||
}: {
|
} & types.BaseTaskOptions = {}
|
||||||
novuClient?: NovuClient
|
) {
|
||||||
} & types.BaseTaskOptions = {}) {
|
|
||||||
super(opts)
|
super(opts)
|
||||||
|
|
||||||
this._novuClient = novuClient
|
this._novuClient =
|
||||||
|
opts.novuClient ?? new NovuClient({ ky: opts.agentic?.ky })
|
||||||
}
|
}
|
||||||
|
|
||||||
public override get inputSchema() {
|
public override get inputSchema() {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import * as types from '@/types'
|
||||||
import { WeatherClient } from '@/services/weather'
|
import { WeatherClient } from '@/services/weather'
|
||||||
import { BaseTask } from '@/task'
|
import { BaseTask } from '@/task'
|
||||||
|
|
||||||
export const WeatherInputSchema = z.object({
|
const WeatherInputSchema = z.object({
|
||||||
query: z
|
query: z
|
||||||
.string()
|
.string()
|
||||||
.describe(
|
.describe(
|
||||||
|
@ -16,7 +16,7 @@ export const WeatherInputSchema = z.object({
|
||||||
.default('imperial')
|
.default('imperial')
|
||||||
.optional()
|
.optional()
|
||||||
})
|
})
|
||||||
export type WeatherInput = z.infer<typeof WeatherInputSchema>
|
type WeatherInput = z.infer<typeof WeatherInputSchema>
|
||||||
|
|
||||||
const LocationSchema = z.object({
|
const LocationSchema = z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
|
@ -61,28 +61,23 @@ const CurrentSchema = z.object({
|
||||||
gust_kph: z.number()
|
gust_kph: z.number()
|
||||||
})
|
})
|
||||||
|
|
||||||
export const WeatherOutputSchema = z.object({
|
const WeatherOutputSchema = z.object({
|
||||||
location: LocationSchema,
|
location: LocationSchema,
|
||||||
current: CurrentSchema
|
current: CurrentSchema
|
||||||
})
|
})
|
||||||
export type WeatherOutput = z.infer<typeof WeatherOutputSchema>
|
type WeatherOutput = z.infer<typeof WeatherOutputSchema>
|
||||||
|
|
||||||
export class WeatherTool extends BaseTask<WeatherInput, WeatherOutput> {
|
export class WeatherTool extends BaseTask<WeatherInput, WeatherOutput> {
|
||||||
client: WeatherClient
|
client: WeatherClient
|
||||||
|
|
||||||
constructor({
|
constructor(
|
||||||
weather = new WeatherClient({ apiKey: process.env.WEATHER_API_KEY }),
|
opts: {
|
||||||
...opts
|
weather?: WeatherClient
|
||||||
}: {
|
} & types.BaseTaskOptions = {}
|
||||||
weather?: WeatherClient
|
) {
|
||||||
} & types.BaseTaskOptions = {}) {
|
|
||||||
super(opts)
|
super(opts)
|
||||||
|
|
||||||
if (!weather) {
|
this.client = opts.weather ?? new WeatherClient({ ky: opts.agentic?.ky })
|
||||||
throw new Error(`Error WeatherTool missing required "weather" client`)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.client = weather
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override get inputSchema() {
|
public override get inputSchema() {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import * as openai from '@agentic/openai-fetch'
|
import * as openai from '@agentic/openai-fetch'
|
||||||
import * as anthropic from '@anthropic-ai/sdk'
|
import * as anthropic from '@anthropic-ai/sdk'
|
||||||
|
import ky from 'ky'
|
||||||
import type { Options as RetryOptions } from 'p-retry'
|
import type { Options as RetryOptions } from 'p-retry'
|
||||||
import type { JsonObject, JsonValue } from 'type-fest'
|
import type { JsonObject, JsonValue } from 'type-fest'
|
||||||
import { SafeParseReturnType, ZodType, ZodTypeAny, output, z } from 'zod'
|
import { SafeParseReturnType, ZodType, ZodTypeAny, output, z } from 'zod'
|
||||||
|
@ -9,7 +10,9 @@ import type { BaseTask } from './task'
|
||||||
|
|
||||||
export { openai }
|
export { openai }
|
||||||
export { anthropic }
|
export { anthropic }
|
||||||
|
|
||||||
export type { JsonObject, JsonValue }
|
export type { JsonObject, JsonValue }
|
||||||
|
export type KyInstance = typeof ky
|
||||||
|
|
||||||
export type ParsedData<T extends ZodTypeAny> = T extends ZodTypeAny
|
export type ParsedData<T extends ZodTypeAny> = T extends ZodTypeAny
|
||||||
? output<T>
|
? output<T>
|
||||||
|
|
|
@ -187,6 +187,6 @@ export function createTestAgenticRuntime() {
|
||||||
const openai = createOpenAITestClient()
|
const openai = createOpenAITestClient()
|
||||||
const anthropic = createAnthropicTestClient()
|
const anthropic = createAnthropicTestClient()
|
||||||
|
|
||||||
const agentic = new Agentic({ openai, anthropic })
|
const agentic = new Agentic({ openai, anthropic, ky })
|
||||||
return agentic
|
return agentic
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
import test from 'ava'
|
||||||
|
import { expectTypeOf } from 'expect-type'
|
||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
import { CalculatorTool, WeatherTool } from '@/index'
|
||||||
|
|
||||||
|
import { createTestAgenticRuntime } from '../_utils'
|
||||||
|
|
||||||
|
// TODO: callWithMetadata and verify sub-tool calls
|
||||||
|
|
||||||
|
test('OpenAIChatCompletion - tools - calculator', async (t) => {
|
||||||
|
t.timeout(2 * 60 * 1000)
|
||||||
|
const agentic = createTestAgenticRuntime()
|
||||||
|
|
||||||
|
const result = await agentic
|
||||||
|
.gpt3('What is 5 * 50?')
|
||||||
|
.tools([new CalculatorTool()])
|
||||||
|
.output(
|
||||||
|
z.object({
|
||||||
|
answer: z.number()
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.call()
|
||||||
|
|
||||||
|
t.truthy(typeof result === 'object')
|
||||||
|
t.truthy(typeof result.answer === 'number')
|
||||||
|
t.is(result.answer, 250)
|
||||||
|
|
||||||
|
expectTypeOf(result).toMatchTypeOf<{
|
||||||
|
answer: number
|
||||||
|
}>()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('OpenAIChatCompletion - tools - weather', async (t) => {
|
||||||
|
t.timeout(2 * 60 * 1000)
|
||||||
|
const agentic = createTestAgenticRuntime()
|
||||||
|
|
||||||
|
const result = await agentic
|
||||||
|
.gpt3('What is the temperature in san francisco today?')
|
||||||
|
.tools([new CalculatorTool({ agentic }), new WeatherTool({ agentic })])
|
||||||
|
.output(
|
||||||
|
z.object({
|
||||||
|
answer: z.number(),
|
||||||
|
units: z.union([z.literal('fahrenheit'), z.literal('celcius')])
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.call()
|
||||||
|
|
||||||
|
t.truthy(typeof result === 'object')
|
||||||
|
t.truthy(typeof result.answer === 'number')
|
||||||
|
t.truthy(typeof result.units === 'string')
|
||||||
|
|
||||||
|
expectTypeOf(result).toMatchTypeOf<{
|
||||||
|
answer: number
|
||||||
|
units: 'fahrenheit' | 'celcius'
|
||||||
|
}>()
|
||||||
|
})
|
Ładowanie…
Reference in New Issue