kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
rodzic
2afaf4d9ad
commit
c4a5006f26
|
@ -25,6 +25,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dqbd/tiktoken": "^1.0.7",
|
"@dqbd/tiktoken": "^1.0.7",
|
||||||
"dotenv-safe": "^8.2.0",
|
"dotenv-safe": "^8.2.0",
|
||||||
|
"jsonrepair": "^3.1.0",
|
||||||
"mustache": "^4.2.0",
|
"mustache": "^4.2.0",
|
||||||
"openai-fetch": "^1.2.1",
|
"openai-fetch": "^1.2.1",
|
||||||
"p-map": "^6.0.0",
|
"p-map": "^6.0.0",
|
||||||
|
|
|
@ -7,6 +7,9 @@ dependencies:
|
||||||
dotenv-safe:
|
dotenv-safe:
|
||||||
specifier: ^8.2.0
|
specifier: ^8.2.0
|
||||||
version: 8.2.0
|
version: 8.2.0
|
||||||
|
jsonrepair:
|
||||||
|
specifier: ^3.1.0
|
||||||
|
version: 3.1.0
|
||||||
mustache:
|
mustache:
|
||||||
specifier: ^4.2.0
|
specifier: ^4.2.0
|
||||||
version: 4.2.0
|
version: 4.2.0
|
||||||
|
@ -1461,6 +1464,11 @@ packages:
|
||||||
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/jsonrepair@3.1.0:
|
||||||
|
resolution: {integrity: sha512-idqReg23J0PVRAADmZMc5xQM3xeOX5bTB6OTyMnzq33IXJXmn9iJuWIEvGmrN80rQf4d7uLTMEDwpzujNcI0Rg==}
|
||||||
|
hasBin: true
|
||||||
|
dev: false
|
||||||
|
|
||||||
/kind-of@6.0.3:
|
/kind-of@6.0.3:
|
||||||
resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
|
resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
47
src/llm.ts
47
src/llm.ts
|
@ -1,3 +1,4 @@
|
||||||
|
import { jsonrepair } from 'jsonrepair'
|
||||||
import Mustache from 'mustache'
|
import Mustache from 'mustache'
|
||||||
import { dedent } from 'ts-dedent'
|
import { dedent } from 'ts-dedent'
|
||||||
import type { SetRequired } from 'type-fest'
|
import type { SetRequired } from 'type-fest'
|
||||||
|
@ -5,6 +6,10 @@ import { ZodRawShape, ZodTypeAny, z } from 'zod'
|
||||||
import { printNode, zodToTs } from 'zod-to-ts'
|
import { printNode, zodToTs } from 'zod-to-ts'
|
||||||
|
|
||||||
import * as types from './types'
|
import * as types from './types'
|
||||||
|
import {
|
||||||
|
extractJSONArrayFromString,
|
||||||
|
extractJSONObjectFromString
|
||||||
|
} from './utils'
|
||||||
|
|
||||||
const defaultOpenAIModel = 'gpt-3.5-turbo'
|
const defaultOpenAIModel = 'gpt-3.5-turbo'
|
||||||
|
|
||||||
|
@ -174,6 +179,8 @@ export class OpenAIChatModelBuilder<
|
||||||
input = inputSchema.parse(input)
|
input = inputSchema.parse(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: validate input message variables against input schema
|
||||||
|
|
||||||
const messages = this._messages
|
const messages = this._messages
|
||||||
.map((message) => {
|
.map((message) => {
|
||||||
return {
|
return {
|
||||||
|
@ -185,6 +192,24 @@ export class OpenAIChatModelBuilder<
|
||||||
})
|
})
|
||||||
.filter((message) => message.content)
|
.filter((message) => message.content)
|
||||||
|
|
||||||
|
if (this._options.output) {
|
||||||
|
const outputSchema =
|
||||||
|
this._options.output instanceof z.ZodType
|
||||||
|
? this._options.output
|
||||||
|
: z.object(this._options.output)
|
||||||
|
|
||||||
|
const { node } = zodToTs(outputSchema)
|
||||||
|
const tsTypeString = printNode(node)
|
||||||
|
|
||||||
|
messages.push({
|
||||||
|
role: 'system',
|
||||||
|
content: dedent`Output JSON only in the following format:
|
||||||
|
\`\`\`ts
|
||||||
|
${tsTypeString}
|
||||||
|
\`\`\``
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: filter/compress messages based on token counts
|
// TODO: filter/compress messages based on token counts
|
||||||
|
|
||||||
const completion = await this._client.createChatCompletion({
|
const completion = await this._client.createChatCompletion({
|
||||||
|
@ -199,10 +224,28 @@ export class OpenAIChatModelBuilder<
|
||||||
? this._options.output
|
? this._options.output
|
||||||
: z.object(this._options.output)
|
: z.object(this._options.output)
|
||||||
|
|
||||||
// TODO: convert string => object if necessary
|
let output: any = completion.message.content
|
||||||
|
if (outputSchema instanceof z.ZodArray) {
|
||||||
|
try {
|
||||||
|
const trimmedOutput = extractJSONArrayFromString(output)
|
||||||
|
output = jsonrepair(trimmedOutput ?? output)
|
||||||
|
} catch (err) {
|
||||||
|
// TODO
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
} else if (outputSchema instanceof z.ZodObject) {
|
||||||
|
try {
|
||||||
|
const trimmedOutput = extractJSONObjectFromString(output)
|
||||||
|
output = jsonrepair(trimmedOutput ?? output)
|
||||||
|
} catch (err) {
|
||||||
|
// TODO
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: handle errors, retry logic, and self-healing
|
// TODO: handle errors, retry logic, and self-healing
|
||||||
|
|
||||||
return outputSchema.parse(completion.message.content)
|
return outputSchema.parse(output)
|
||||||
} else {
|
} else {
|
||||||
return completion.message.content as any
|
return completion.message.content as any
|
||||||
}
|
}
|
||||||
|
|
30
src/temp.ts
30
src/temp.ts
|
@ -12,28 +12,26 @@ async function main() {
|
||||||
const openai = new OpenAIClient({ apiKey: process.env.OPENAI_API_KEY! })
|
const openai = new OpenAIClient({ apiKey: process.env.OPENAI_API_KEY! })
|
||||||
const $ = new Agentic({ openai })
|
const $ = new Agentic({ openai })
|
||||||
|
|
||||||
const ex0 = await $.gpt4(`give me a single boolean value`)
|
// const ex0 = await $.gpt4(`give me a single boolean value`)
|
||||||
.output(z.boolean())
|
// .output(z.boolean())
|
||||||
// .retry({ attempts: 3 })
|
// // .retry({ attempts: 3 })
|
||||||
.call()
|
// .call()
|
||||||
|
// console.log(ex0)
|
||||||
console.log(ex0)
|
|
||||||
|
|
||||||
const ex1 = await $.gpt4(`give me fake data conforming to this schema`)
|
const ex1 = await $.gpt4(`give me fake data conforming to this schema`)
|
||||||
.output(z.object({ foo: z.string(), bar: z.number() }))
|
.output(z.object({ foo: z.string(), bar: z.number() }))
|
||||||
// .retry({ attempts: 3 })
|
// .retry({ attempts: 3 })
|
||||||
.call()
|
.call()
|
||||||
|
|
||||||
const getBoolean = $.gpt4(`give me a single boolean value {{foo}}`)
|
|
||||||
.input(z.object({ foo: z.string() }))
|
|
||||||
.output(z.boolean())
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
getBoolean.call({ foo: 'foo' }),
|
|
||||||
getBoolean.call({ foo: 'bar' })
|
|
||||||
])
|
|
||||||
|
|
||||||
console.log(ex1)
|
console.log(ex1)
|
||||||
|
|
||||||
|
// const getBoolean = $.gpt4(`give me a single boolean value {{foo}}`)
|
||||||
|
// .input(z.object({ foo: z.string() }))
|
||||||
|
// .output(z.boolean())
|
||||||
|
|
||||||
|
// await Promise.all([
|
||||||
|
// getBoolean.call({ foo: 'foo' }),
|
||||||
|
// getBoolean.call({ foo: 'bar' })
|
||||||
|
// ])
|
||||||
}
|
}
|
||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
export const extractJSONObjectFromString = (text: string): string | undefined =>
|
||||||
|
text.match(/\{(.|\n)*\}/gm)?.[0]
|
||||||
|
|
||||||
|
export const extractJSONArrayFromString = (text: string): string | undefined =>
|
||||||
|
text.match(/\[(.|\n)*\]/gm)?.[0]
|
Ładowanie…
Reference in New Issue