feat: minor tool improvements

Travis Fischer 2023-06-14 19:37:28 -07:00
rodzic f95dd54436
commit e41fb75127
9 zmienionych plików z 122 dodań i 40 usunięć

Wyświetl plik

@ -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
}

Wyświetl plik

@ -1,5 +1,6 @@
export * from './agentic'
export * from './task'
export * from './constants'
export * from './errors'
export * from './tokenizer'
export * from './human-feedback'

Wyświetl plik

@ -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,

Wyświetl plik

@ -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() {

Wyświetl plik

@ -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() {

Wyświetl plik

@ -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() {

Wyświetl plik

@ -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>

2
test/_utils.ts vendored
Wyświetl plik

@ -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
}

Wyświetl plik

@ -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'
}>()
})