kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
pull/643/head^2
rodzic
f4e7861064
commit
839f46097c
|
@ -4,5 +4,3 @@
|
||||||
# All of these environment vars must be defined either in your environment or in
|
# All of these environment vars must be defined either in your environment or in
|
||||||
# a local .env file in order to run this project.
|
# a local .env file in order to run this project.
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
OPENAI_API_KEY=
|
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
import 'dotenv/config'
|
||||||
|
|
||||||
|
import {
|
||||||
|
ChatModel,
|
||||||
|
createAIFunction,
|
||||||
|
createAIRunner,
|
||||||
|
Msg
|
||||||
|
} from '@dexaai/dexter'
|
||||||
|
import { gracefulExit } from 'exit-hook'
|
||||||
|
import restoreCursor from 'restore-cursor'
|
||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
import { WeatherClient } from '../src/index.js'
|
||||||
|
|
||||||
|
/** Get the capital city for a given state. */
|
||||||
|
const getCapitalCity = createAIFunction(
|
||||||
|
{
|
||||||
|
name: 'get_capital_city',
|
||||||
|
description: 'Use this to get the the capital city for a given state',
|
||||||
|
argsSchema: z.object({
|
||||||
|
state: z
|
||||||
|
.string()
|
||||||
|
.length(2)
|
||||||
|
.describe(
|
||||||
|
'The state to get the capital city for, using the two letter abbreviation e.g. CA'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async ({ state }) => {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 500))
|
||||||
|
let capitalCity = ''
|
||||||
|
switch (state) {
|
||||||
|
case 'CA':
|
||||||
|
capitalCity = 'Sacramento'
|
||||||
|
break
|
||||||
|
case 'NY':
|
||||||
|
capitalCity = 'Albany'
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
capitalCity = 'Unknown'
|
||||||
|
}
|
||||||
|
return { capitalCity }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const weather = new WeatherClient()
|
||||||
|
|
||||||
|
const fns = [...weather.functions]
|
||||||
|
console.log('fns', fns)
|
||||||
|
|
||||||
|
const getCurrentWeather = weather.functions.get('get_current_weather')!
|
||||||
|
console.log('get_current_weather', getCurrentWeather)
|
||||||
|
|
||||||
|
/** A runner that uses the weather and capital city functions. */
|
||||||
|
const weatherCapitalRunner = createAIRunner({
|
||||||
|
chatModel: new ChatModel({ params: { model: 'gpt-4-1106-preview' } }),
|
||||||
|
functions: [
|
||||||
|
createAIFunction(
|
||||||
|
{
|
||||||
|
...getCurrentWeather.spec,
|
||||||
|
argsSchema: getCurrentWeather.inputSchema
|
||||||
|
},
|
||||||
|
getCurrentWeather.impl
|
||||||
|
),
|
||||||
|
getCapitalCity
|
||||||
|
],
|
||||||
|
systemMessage: `You use functions to answer questions about the weather and capital cities.`
|
||||||
|
})
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
restoreCursor()
|
||||||
|
|
||||||
|
// Run with a string input
|
||||||
|
const rString = await weatherCapitalRunner(
|
||||||
|
`Whats the capital of California and NY and the weather for both?`
|
||||||
|
)
|
||||||
|
console.log('rString', rString)
|
||||||
|
|
||||||
|
// Run with a message input
|
||||||
|
const rMessage = await weatherCapitalRunner({
|
||||||
|
messages: [
|
||||||
|
Msg.user(
|
||||||
|
`Whats the capital of California and NY and the weather for both?`
|
||||||
|
)
|
||||||
|
]
|
||||||
|
})
|
||||||
|
console.log('rMessage', rMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await main()
|
||||||
|
} catch (err) {
|
||||||
|
console.error('unexpected error', err)
|
||||||
|
gracefulExit(1)
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ import restoreCursor from 'restore-cursor'
|
||||||
// import { SearxngClient } from '../src/services/searxng-client.js'
|
// import { SearxngClient } from '../src/services/searxng-client.js'
|
||||||
// import { ClearbitClient } from '../src/index.js'
|
// import { ClearbitClient } from '../src/index.js'
|
||||||
// import { ProxycurlClient } from '../src/services/proxycurl-client.js'
|
// import { ProxycurlClient } from '../src/services/proxycurl-client.js'
|
||||||
// import { WikipediaClient } from '../src/services/wikipedia-client.js'
|
import { WikipediaClient } from '../src/index.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scratch pad for testing.
|
* Scratch pad for testing.
|
||||||
|
@ -27,11 +27,11 @@ async function main() {
|
||||||
// })
|
// })
|
||||||
// console.log(JSON.stringify(res, null, 2))
|
// console.log(JSON.stringify(res, null, 2))
|
||||||
|
|
||||||
// const wikipedia = new WikipediaClient()
|
const wikipedia = new WikipediaClient()
|
||||||
// const res = await wikipedia.getPageSummary({
|
const res = await wikipedia.getPageSummary({
|
||||||
// title: 'Naruto_(TV_series)'
|
title: 'Naruto_(TV_series)'
|
||||||
// })
|
})
|
||||||
// console.log(JSON.stringify(res, null, 2))
|
console.log(JSON.stringify(res, null, 2))
|
||||||
|
|
||||||
// const searxng = new SearxngClient()
|
// const searxng = new SearxngClient()
|
||||||
// const res = await searxng.search({
|
// const res = await searxng.search({
|
||||||
|
|
20
package.json
20
package.json
|
@ -30,7 +30,7 @@
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"preinstall": "npx only-allow pnpm",
|
"preinstall": "npx only-allow pnpm",
|
||||||
"build": "tsup",
|
"build": "tsc",
|
||||||
"dev": "tsup --watch",
|
"dev": "tsup --watch",
|
||||||
"clean": "del dist",
|
"clean": "del dist",
|
||||||
"prebuild": "run-s clean",
|
"prebuild": "run-s clean",
|
||||||
|
@ -45,7 +45,7 @@
|
||||||
"test:unit": "vitest run"
|
"test:unit": "vitest run"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nangohq/node": "^0.39.30",
|
"@nangohq/node": "^0.39.32",
|
||||||
"chalk": "^5.3.0",
|
"chalk": "^5.3.0",
|
||||||
"delay": "^6.0.0",
|
"delay": "^6.0.0",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
|
@ -58,32 +58,38 @@
|
||||||
"restore-cursor": "^5.0.0",
|
"restore-cursor": "^5.0.0",
|
||||||
"tiny-invariant": "^1.3.3",
|
"tiny-invariant": "^1.3.3",
|
||||||
"twitter-api-sdk": "^1.2.1",
|
"twitter-api-sdk": "^1.2.1",
|
||||||
"type-fest": "^4.16.0",
|
"type-fest": "^4.18.3",
|
||||||
"zod": "^3.23.3",
|
"zod": "^3.23.3",
|
||||||
"zod-to-json-schema": "^3.23.0"
|
"zod-to-json-schema": "^3.23.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@dexaai/dexter": "^2.0.3",
|
||||||
"@fisch0920/eslint-config": "^1.3.1",
|
"@fisch0920/eslint-config": "^1.3.1",
|
||||||
"@total-typescript/ts-reset": "^0.5.1",
|
"@total-typescript/ts-reset": "^0.5.1",
|
||||||
"@types/node": "^20.12.7",
|
"@types/node": "^20.12.7",
|
||||||
"del-cli": "^5.1.0",
|
"del-cli": "^5.1.0",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"husky": "^9.0.11",
|
"husky": "^9.0.11",
|
||||||
"lint-staged": "^15.2.4",
|
"lint-staged": "^15.2.5",
|
||||||
"np": "^10.0.5",
|
"np": "^10.0.5",
|
||||||
"npm-run-all2": "^6.2.0",
|
"npm-run-all2": "^6.2.0",
|
||||||
"only-allow": "^1.2.1",
|
"only-allow": "^1.2.1",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
|
"ts-node": "^10.9.2",
|
||||||
"tsup": "^8.0.2",
|
"tsup": "^8.0.2",
|
||||||
"tsx": "^4.10.5",
|
"tsx": "^4.11.0",
|
||||||
"typescript": "^5.4.5",
|
"typescript": "^5.4.5",
|
||||||
"vite": "^5.2.10",
|
"vitest": "2.0.0-beta.3"
|
||||||
"vitest": "^1.5.0"
|
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*.{ts,tsx}": [
|
"*.{ts,tsx}": [
|
||||||
"eslint --fix",
|
"eslint --fix",
|
||||||
"prettier --ignore-unknown --write"
|
"prettier --ignore-unknown --write"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"pnpm": {
|
||||||
|
"overrides": {
|
||||||
|
"esbuild": "~0.21.4"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
1098
pnpm-lock.yaml
1098
pnpm-lock.yaml
Plik diff jest za duży
Load Diff
|
@ -5,7 +5,9 @@ export class AIFunctionSet implements Iterable<types.AIFunction> {
|
||||||
protected readonly _map: Map<string, types.AIFunction>
|
protected readonly _map: Map<string, types.AIFunction>
|
||||||
|
|
||||||
constructor(functions?: readonly types.AIFunction[]) {
|
constructor(functions?: readonly types.AIFunction[]) {
|
||||||
this._map = new Map(functions ? functions.map((fn) => [fn.name, fn]) : null)
|
this._map = new Map(
|
||||||
|
functions ? functions.map((fn) => [fn.spec.name, fn]) : null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
get size(): number {
|
get size(): number {
|
||||||
|
|
|
@ -26,6 +26,14 @@ export function createAIFunction<InputSchema extends z.ZodObject<any>, Return>(
|
||||||
/** Implementation of the function to call with the parsed arguments. */
|
/** Implementation of the function to call with the parsed arguments. */
|
||||||
implementation: (params: z.infer<InputSchema>) => types.MaybePromise<Return>
|
implementation: (params: z.infer<InputSchema>) => types.MaybePromise<Return>
|
||||||
): types.AIFunction<InputSchema, Return> {
|
): types.AIFunction<InputSchema, Return> {
|
||||||
|
assert(spec.name, 'Missing required AIFunction "spec.name"')
|
||||||
|
assert(spec.inputSchema, 'Missing required AIFunction "spec.inputSchema"')
|
||||||
|
assert(implementation, 'Missing required AIFunction "implementation"')
|
||||||
|
assert(
|
||||||
|
typeof implementation === 'function',
|
||||||
|
'Required AIFunction "implementation" must be a function'
|
||||||
|
)
|
||||||
|
|
||||||
/** Parse the arguments string, optionally reading from a message. */
|
/** Parse the arguments string, optionally reading from a message. */
|
||||||
const parseInput = (input: string | types.Msg) => {
|
const parseInput = (input: string | types.Msg) => {
|
||||||
if (typeof input === 'string') {
|
if (typeof input === 'string') {
|
||||||
|
@ -55,6 +63,7 @@ export function createAIFunction<InputSchema extends z.ZodObject<any>, Return>(
|
||||||
description: spec.description?.trim() ?? '',
|
description: spec.description?.trim() ?? '',
|
||||||
parameters: zodToJsonSchema(spec.inputSchema)
|
parameters: zodToJsonSchema(spec.inputSchema)
|
||||||
}
|
}
|
||||||
|
aiFunction.impl = implementation
|
||||||
|
|
||||||
return aiFunction
|
return aiFunction
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ export class AIToolSet implements Iterable<types.AITool> {
|
||||||
|
|
||||||
constructor(tools?: readonly types.AITool[]) {
|
constructor(tools?: readonly types.AITool[]) {
|
||||||
this._map = new Map(
|
this._map = new Map(
|
||||||
tools ? tools.map((tool) => [tool.function.name, tool]) : []
|
tools ? tools.map((tool) => [tool.spec.function.name, tool]) : []
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ export class AIToolSet implements Iterable<types.AITool> {
|
||||||
}
|
}
|
||||||
|
|
||||||
add(tool: types.AITool): this {
|
add(tool: types.AITool): this {
|
||||||
this._map.set(tool.function.name, tool)
|
this._map.set(tool.spec.function.name, tool)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ export class AIToolSet implements Iterable<types.AITool> {
|
||||||
const keysToIncludeSet = new Set(keys)
|
const keysToIncludeSet = new Set(keys)
|
||||||
return new AIToolSet(
|
return new AIToolSet(
|
||||||
Array.from(this).filter((tool) =>
|
Array.from(this).filter((tool) =>
|
||||||
keysToIncludeSet.has(tool.function.name)
|
keysToIncludeSet.has(tool.spec.function.name)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ export class AIToolSet implements Iterable<types.AITool> {
|
||||||
const keysToExcludeSet = new Set(keys)
|
const keysToExcludeSet = new Set(keys)
|
||||||
return new AIToolSet(
|
return new AIToolSet(
|
||||||
Array.from(this).filter(
|
Array.from(this).filter(
|
||||||
(tool) => !keysToExcludeSet.has(tool.function.name)
|
(tool) => !keysToExcludeSet.has(tool.spec.function.name)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
10
src/fns.ts
10
src/fns.ts
|
@ -8,8 +8,6 @@ import { AIFunctionSet } from './ai-function-set.js'
|
||||||
import { AIToolSet } from './ai-tool-set.js'
|
import { AIToolSet } from './ai-tool-set.js'
|
||||||
import { assert } from './utils.js'
|
import { assert } from './utils.js'
|
||||||
|
|
||||||
export const invocableMetadataKey = Symbol('invocable')
|
|
||||||
|
|
||||||
export abstract class AIToolsProvider {
|
export abstract class AIToolsProvider {
|
||||||
private _tools?: AIToolSet
|
private _tools?: AIToolSet
|
||||||
private _functions?: AIFunctionSet
|
private _functions?: AIFunctionSet
|
||||||
|
@ -25,7 +23,9 @@ export abstract class AIToolsProvider {
|
||||||
get functions(): AIFunctionSet {
|
get functions(): AIFunctionSet {
|
||||||
if (!this._functions) {
|
if (!this._functions) {
|
||||||
const metadata = this.constructor[Symbol.metadata]
|
const metadata = this.constructor[Symbol.metadata]
|
||||||
|
assert(metadata)
|
||||||
const invocables = (metadata?.invocables as Invocable[]) ?? []
|
const invocables = (metadata?.invocables as Invocable[]) ?? []
|
||||||
|
console.log({ metadata, invocables })
|
||||||
|
|
||||||
const aiFunctions = invocables.map((invocable) => {
|
const aiFunctions = invocables.map((invocable) => {
|
||||||
const impl = (this as any)[invocable.methodName]?.bind(this)
|
const impl = (this as any)[invocable.methodName]?.bind(this)
|
||||||
|
@ -81,13 +81,17 @@ export function aiFunction<
|
||||||
if (!context.metadata.invocables) {
|
if (!context.metadata.invocables) {
|
||||||
context.metadata.invocables = []
|
context.metadata.invocables = []
|
||||||
}
|
}
|
||||||
|
|
||||||
;(context.metadata.invocables as Invocable[]).push({
|
;(context.metadata.invocables as Invocable[]).push({
|
||||||
name: name ?? methodName,
|
name: name ?? methodName,
|
||||||
description,
|
description,
|
||||||
inputSchema,
|
inputSchema,
|
||||||
methodName
|
methodName
|
||||||
})
|
})
|
||||||
|
console.log({
|
||||||
|
name,
|
||||||
|
methodName,
|
||||||
|
context
|
||||||
|
})
|
||||||
|
|
||||||
// context.addInitializer(function () {
|
// context.addInitializer(function () {
|
||||||
// ;(this as any)[methodName] = (this as any)[methodName].bind(this)
|
// ;(this as any)[methodName] = (this as any)[methodName].bind(this)
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { expect, test } from 'vitest'
|
||||||
|
|
||||||
|
import { WeatherClient } from './weather-client.js'
|
||||||
|
|
||||||
|
test('WeatherClient.functions', () => {
|
||||||
|
const weather = new WeatherClient({
|
||||||
|
apiKey: 'sk-test'
|
||||||
|
})
|
||||||
|
|
||||||
|
const fns = [...weather.functions]
|
||||||
|
console.log(fns)
|
||||||
|
|
||||||
|
expect(weather.functions.get('getCurrentWeather')).toBeTruthy()
|
||||||
|
})
|
|
@ -19,3 +19,11 @@ if (typeof Symbol === 'function' && Symbol.metadata) {
|
||||||
value: _metadata
|
value: _metadata
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// export {};
|
||||||
|
// declare global {
|
||||||
|
// interface SymbolConstructor {
|
||||||
|
// readonly metadata: unique symbol
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// (Symbol as any).metadata ??= Symbol.for("Symbol.metadata")
|
||||||
|
|
|
@ -40,6 +40,8 @@ export interface AIFunction<
|
||||||
|
|
||||||
/** The function spec for the OpenAI API `functions` property. */
|
/** The function spec for the OpenAI API `functions` property. */
|
||||||
spec: AIFunctionSpec
|
spec: AIFunctionSpec
|
||||||
|
|
||||||
|
impl: (params: z.infer<InputSchema>) => MaybePromise<Return>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
"useDefineForClassFields": true,
|
"useDefineForClassFields": true,
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
|
|
||||||
|
// NOTE: these are deprecated
|
||||||
// "experimentalDecorators": true,
|
// "experimentalDecorators": true,
|
||||||
// "emitDecoratorMetadata": true,
|
// "emitDecoratorMetadata": true,
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { defineConfig } from 'vitest/config'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
esbuild: {
|
||||||
|
target: 'es2022'
|
||||||
|
}
|
||||||
|
})
|
Ładowanie…
Reference in New Issue