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 { DEFAULT_OPENAI_MODEL } from './constants'
|
||||
import {
|
||||
|
@ -9,6 +11,7 @@ import { defaultIDGeneratorFn } from './utils'
|
|||
|
||||
export class Agentic {
|
||||
// _taskMap: WeakMap<string, BaseTask<any, any>>
|
||||
protected _ky: types.KyInstance
|
||||
|
||||
protected _openai?: types.openai.OpenAIClient
|
||||
protected _anthropic?: types.anthropic.Client
|
||||
|
@ -33,13 +36,18 @@ export class Agentic {
|
|||
>
|
||||
defaultHumanFeedbackMechanism?: HumanFeedbackMechanism
|
||||
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()) {
|
||||
globalThis.__agentic = new WeakRef(this)
|
||||
}
|
||||
|
||||
this._openai = opts.openai
|
||||
this._anthropic = opts.anthropic
|
||||
this._ky = opts.ky ?? defaultKy
|
||||
|
||||
this._verbosity = opts.verbosity ?? 0
|
||||
|
||||
|
@ -75,6 +83,10 @@ export class Agentic {
|
|||
return this._anthropic
|
||||
}
|
||||
|
||||
public get ky(): types.KyInstance {
|
||||
return this._ky
|
||||
}
|
||||
|
||||
public get defaultHumanFeedbackMechamism() {
|
||||
return this._defaultHumanFeedbackMechamism
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
export * from './agentic'
|
||||
export * from './task'
|
||||
export * from './constants'
|
||||
export * from './errors'
|
||||
export * from './tokenizer'
|
||||
export * from './human-feedback'
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
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 { BaseTask } from '@/task'
|
||||
|
@ -80,15 +81,14 @@ export async function getNumTokensForChatMessages({
|
|||
return { numTokensTotal, numTokensPerMessage }
|
||||
}
|
||||
|
||||
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 jsonSchema = zodToJsonSchema(task.inputSchema, {
|
||||
export function zodToJsonSchema({
|
||||
zodSchema,
|
||||
name
|
||||
}: {
|
||||
zodSchema: ZodTypeAny
|
||||
name: string
|
||||
}): any {
|
||||
const jsonSchema = zodToJsonSchemaImpl(zodSchema, {
|
||||
name,
|
||||
$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 {
|
||||
name,
|
||||
description: task.descForModel || task.nameForHuman,
|
||||
|
|
|
@ -8,15 +8,16 @@ export class MetaphorSearchTool extends BaseTask<
|
|||
> {
|
||||
protected _metaphorClient: metaphor.MetaphorClient
|
||||
|
||||
constructor({
|
||||
metaphorClient = new metaphor.MetaphorClient(),
|
||||
...opts
|
||||
}: {
|
||||
metaphorClient?: metaphor.MetaphorClient
|
||||
} & types.BaseTaskOptions = {}) {
|
||||
constructor(
|
||||
opts: {
|
||||
metaphorClient?: metaphor.MetaphorClient
|
||||
} & types.BaseTaskOptions = {}
|
||||
) {
|
||||
super(opts)
|
||||
|
||||
this._metaphorClient = metaphorClient
|
||||
this._metaphorClient =
|
||||
opts.metaphorClient ??
|
||||
new metaphor.MetaphorClient({ ky: opts.agentic?.ky })
|
||||
}
|
||||
|
||||
public override get inputSchema() {
|
||||
|
|
|
@ -40,15 +40,15 @@ export class NovuNotificationTool extends BaseTask<
|
|||
> {
|
||||
protected _novuClient: NovuClient
|
||||
|
||||
constructor({
|
||||
novuClient = new NovuClient(),
|
||||
...opts
|
||||
}: {
|
||||
novuClient?: NovuClient
|
||||
} & types.BaseTaskOptions = {}) {
|
||||
constructor(
|
||||
opts: {
|
||||
novuClient?: NovuClient
|
||||
} & types.BaseTaskOptions = {}
|
||||
) {
|
||||
super(opts)
|
||||
|
||||
this._novuClient = novuClient
|
||||
this._novuClient =
|
||||
opts.novuClient ?? new NovuClient({ ky: opts.agentic?.ky })
|
||||
}
|
||||
|
||||
public override get inputSchema() {
|
||||
|
|
|
@ -4,7 +4,7 @@ import * as types from '@/types'
|
|||
import { WeatherClient } from '@/services/weather'
|
||||
import { BaseTask } from '@/task'
|
||||
|
||||
export const WeatherInputSchema = z.object({
|
||||
const WeatherInputSchema = z.object({
|
||||
query: z
|
||||
.string()
|
||||
.describe(
|
||||
|
@ -16,7 +16,7 @@ export const WeatherInputSchema = z.object({
|
|||
.default('imperial')
|
||||
.optional()
|
||||
})
|
||||
export type WeatherInput = z.infer<typeof WeatherInputSchema>
|
||||
type WeatherInput = z.infer<typeof WeatherInputSchema>
|
||||
|
||||
const LocationSchema = z.object({
|
||||
name: z.string(),
|
||||
|
@ -61,28 +61,23 @@ const CurrentSchema = z.object({
|
|||
gust_kph: z.number()
|
||||
})
|
||||
|
||||
export const WeatherOutputSchema = z.object({
|
||||
const WeatherOutputSchema = z.object({
|
||||
location: LocationSchema,
|
||||
current: CurrentSchema
|
||||
})
|
||||
export type WeatherOutput = z.infer<typeof WeatherOutputSchema>
|
||||
type WeatherOutput = z.infer<typeof WeatherOutputSchema>
|
||||
|
||||
export class WeatherTool extends BaseTask<WeatherInput, WeatherOutput> {
|
||||
client: WeatherClient
|
||||
|
||||
constructor({
|
||||
weather = new WeatherClient({ apiKey: process.env.WEATHER_API_KEY }),
|
||||
...opts
|
||||
}: {
|
||||
weather?: WeatherClient
|
||||
} & types.BaseTaskOptions = {}) {
|
||||
constructor(
|
||||
opts: {
|
||||
weather?: WeatherClient
|
||||
} & types.BaseTaskOptions = {}
|
||||
) {
|
||||
super(opts)
|
||||
|
||||
if (!weather) {
|
||||
throw new Error(`Error WeatherTool missing required "weather" client`)
|
||||
}
|
||||
|
||||
this.client = weather
|
||||
this.client = opts.weather ?? new WeatherClient({ ky: opts.agentic?.ky })
|
||||
}
|
||||
|
||||
public override get inputSchema() {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import * as openai from '@agentic/openai-fetch'
|
||||
import * as anthropic from '@anthropic-ai/sdk'
|
||||
import ky from 'ky'
|
||||
import type { Options as RetryOptions } from 'p-retry'
|
||||
import type { JsonObject, JsonValue } from 'type-fest'
|
||||
import { SafeParseReturnType, ZodType, ZodTypeAny, output, z } from 'zod'
|
||||
|
@ -9,7 +10,9 @@ import type { BaseTask } from './task'
|
|||
|
||||
export { openai }
|
||||
export { anthropic }
|
||||
|
||||
export type { JsonObject, JsonValue }
|
||||
export type KyInstance = typeof ky
|
||||
|
||||
export type ParsedData<T extends ZodTypeAny> = T extends ZodTypeAny
|
||||
? output<T>
|
||||
|
|
|
@ -187,6 +187,6 @@ export function createTestAgenticRuntime() {
|
|||
const openai = createOpenAITestClient()
|
||||
const anthropic = createAnthropicTestClient()
|
||||
|
||||
const agentic = new Agentic({ openai, anthropic })
|
||||
const agentic = new Agentic({ openai, anthropic, ky })
|
||||
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