kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
pull/643/head^2
rodzic
ed85b708fd
commit
f5319b4e17
|
@ -1,4 +1,8 @@
|
||||||
{
|
{
|
||||||
"root": true,
|
"root": true,
|
||||||
"extends": ["@fisch0920/eslint-config/node"]
|
"extends": ["@fisch0920/eslint-config/node"],
|
||||||
|
"rules": {
|
||||||
|
"unicorn/no-static-only-class": "off",
|
||||||
|
"@typescript-eslint/naming-convention": "off"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,7 +71,7 @@ async function main() {
|
||||||
// })
|
// })
|
||||||
const res = await diffbot.enhanceEntity({
|
const res = await diffbot.enhanceEntity({
|
||||||
type: 'Person',
|
type: 'Person',
|
||||||
name: 'Travis Fischer'
|
name: 'Kevin Raheja'
|
||||||
})
|
})
|
||||||
console.log(JSON.stringify(res, null, 2))
|
console.log(JSON.stringify(res, null, 2))
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nangohq/node": "^0.39.32",
|
"@nangohq/node": "^0.39.32",
|
||||||
|
"dedent": "^1.5.3",
|
||||||
"delay": "^6.0.0",
|
"delay": "^6.0.0",
|
||||||
"jsonrepair": "^3.6.1",
|
"jsonrepair": "^3.6.1",
|
||||||
"ky": "^1.2.4",
|
"ky": "^1.2.4",
|
||||||
|
@ -85,6 +86,7 @@
|
||||||
"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",
|
||||||
|
"openai-fetch": "^2.0.3",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"restore-cursor": "^5.0.0",
|
"restore-cursor": "^5.0.0",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
|
|
|
@ -14,6 +14,9 @@ importers:
|
||||||
'@nangohq/node':
|
'@nangohq/node':
|
||||||
specifier: ^0.39.32
|
specifier: ^0.39.32
|
||||||
version: 0.39.32
|
version: 0.39.32
|
||||||
|
dedent:
|
||||||
|
specifier: ^1.5.3
|
||||||
|
version: 1.5.3
|
||||||
delay:
|
delay:
|
||||||
specifier: ^6.0.0
|
specifier: ^6.0.0
|
||||||
version: 6.0.0
|
version: 6.0.0
|
||||||
|
@ -87,6 +90,9 @@ importers:
|
||||||
only-allow:
|
only-allow:
|
||||||
specifier: ^1.2.1
|
specifier: ^1.2.1
|
||||||
version: 1.2.1
|
version: 1.2.1
|
||||||
|
openai-fetch:
|
||||||
|
specifier: ^2.0.3
|
||||||
|
version: 2.0.3
|
||||||
prettier:
|
prettier:
|
||||||
specifier: ^3.2.5
|
specifier: ^3.2.5
|
||||||
version: 3.3.0
|
version: 3.3.0
|
||||||
|
|
|
@ -3,9 +3,9 @@ export * from './create-ai-function.js'
|
||||||
export * from './create-ai-function.js'
|
export * from './create-ai-function.js'
|
||||||
export * from './errors.js'
|
export * from './errors.js'
|
||||||
export * from './fns.js'
|
export * from './fns.js'
|
||||||
|
export * from './message.js'
|
||||||
export * from './parse-structured-output.js'
|
export * from './parse-structured-output.js'
|
||||||
export * from './services/index.js'
|
export * from './services/index.js'
|
||||||
export * from './stringify-for-model.js'
|
|
||||||
export type * from './types.js'
|
export type * from './types.js'
|
||||||
export * from './utils.js'
|
export * from './utils.js'
|
||||||
export * from './zod-to-json-schema.js'
|
export * from './zod-to-json-schema.js'
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
import type * as OpenAI from 'openai-fetch'
|
||||||
|
import { describe, expect, expectTypeOf, it } from 'vitest'
|
||||||
|
|
||||||
|
import type * as types from './types.js'
|
||||||
|
import { Msg } from './message.js'
|
||||||
|
|
||||||
|
describe('Msg', () => {
|
||||||
|
it('creates a message and fixes indentation', () => {
|
||||||
|
const msgContent = `
|
||||||
|
Hello, World!
|
||||||
|
`
|
||||||
|
const msg = Msg.system(msgContent)
|
||||||
|
expect(msg.role).toEqual('system')
|
||||||
|
expect(msg.content).toEqual('Hello, World!')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('supports disabling indentation fixing', () => {
|
||||||
|
const msgContent = `
|
||||||
|
Hello, World!
|
||||||
|
`
|
||||||
|
const msg = Msg.system(msgContent, { cleanContent: false })
|
||||||
|
expect(msg.content).toEqual('\n Hello, World!\n ')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('handles tool calls request', () => {
|
||||||
|
const msg = Msg.toolCall([
|
||||||
|
{
|
||||||
|
id: 'fake-tool-call-id',
|
||||||
|
type: 'function',
|
||||||
|
function: {
|
||||||
|
arguments: '{"prompt": "Hello, World!"}',
|
||||||
|
name: 'hello'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
])
|
||||||
|
expectTypeOf(msg).toMatchTypeOf<types.Msg.ToolCall>()
|
||||||
|
expect(Msg.isToolCall(msg)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('handles tool call response', () => {
|
||||||
|
const msg = Msg.toolResult('Hello, World!', 'fake-tool-call-id')
|
||||||
|
expectTypeOf(msg).toMatchTypeOf<types.Msg.ToolResult>()
|
||||||
|
expect(Msg.isToolResult(msg)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('prompt message types should interop with openai-fetch message types', () => {
|
||||||
|
expectTypeOf({} as OpenAI.ChatMessage).toMatchTypeOf<types.Msg>()
|
||||||
|
expectTypeOf({} as types.Msg).toMatchTypeOf<OpenAI.ChatMessage>()
|
||||||
|
expectTypeOf({} as types.Msg.System).toMatchTypeOf<OpenAI.ChatMessage>()
|
||||||
|
expectTypeOf({} as types.Msg.User).toMatchTypeOf<OpenAI.ChatMessage>()
|
||||||
|
expectTypeOf({} as types.Msg.Assistant).toMatchTypeOf<OpenAI.ChatMessage>()
|
||||||
|
expectTypeOf({} as types.Msg.FuncCall).toMatchTypeOf<OpenAI.ChatMessage>()
|
||||||
|
expectTypeOf({} as types.Msg.FuncResult).toMatchTypeOf<OpenAI.ChatMessage>()
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,344 @@
|
||||||
|
import type { Jsonifiable } from 'type-fest'
|
||||||
|
|
||||||
|
import { cleanStringForModel, stringifyForModel } from './utils.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic/default OpenAI message without any narrowing applied.
|
||||||
|
*/
|
||||||
|
export interface Msg {
|
||||||
|
/**
|
||||||
|
* The contents of the message. `content` is required for all messages, and
|
||||||
|
* may be null for assistant messages with function calls.
|
||||||
|
*/
|
||||||
|
content: string | null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The role of the messages author. One of `system`, `user`, `assistant`,
|
||||||
|
* 'tool', or `function`.
|
||||||
|
*/
|
||||||
|
role: Msg.Role
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name and arguments of a function that should be called, as generated
|
||||||
|
* by the model.
|
||||||
|
*/
|
||||||
|
function_call?: Msg.Call.Function
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The tool calls generated by the model, such as function calls.
|
||||||
|
*/
|
||||||
|
tool_calls?: Msg.Call.Tool[]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tool call that this message is responding to.
|
||||||
|
*/
|
||||||
|
tool_call_id?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the author of this message. `name` is required if role is
|
||||||
|
* `function`, and it should be the name of the function whose response is in the
|
||||||
|
* `content`. May contain a-z, A-Z, 0-9, and underscores, with a maximum length of
|
||||||
|
* 64 characters.
|
||||||
|
*/
|
||||||
|
name?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Narrowed OpenAI Message types. */
|
||||||
|
export namespace Msg {
|
||||||
|
/** Possible roles for a message. */
|
||||||
|
export type Role = 'system' | 'user' | 'assistant' | 'function' | 'tool'
|
||||||
|
|
||||||
|
export namespace Call {
|
||||||
|
/**
|
||||||
|
* The name and arguments of a function that should be called, as generated
|
||||||
|
* by the model.
|
||||||
|
*/
|
||||||
|
export type Function = {
|
||||||
|
/**
|
||||||
|
* The arguments to call the function with, as generated by the model in
|
||||||
|
* JSON format.
|
||||||
|
*/
|
||||||
|
arguments: string
|
||||||
|
|
||||||
|
/** The name of the function to call. */
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The tool calls generated by the model, such as function calls. */
|
||||||
|
export type Tool = {
|
||||||
|
/** The ID of the tool call. */
|
||||||
|
id: string
|
||||||
|
|
||||||
|
/** The type of the tool. Currently, only `function` is supported. */
|
||||||
|
type: 'function'
|
||||||
|
|
||||||
|
/** The function that the model called. */
|
||||||
|
function: Call.Function
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Message with text content for the system. */
|
||||||
|
export type System = {
|
||||||
|
role: 'system'
|
||||||
|
content: string
|
||||||
|
name?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Message with text content from the user. */
|
||||||
|
export type User = {
|
||||||
|
role: 'user'
|
||||||
|
name?: string
|
||||||
|
content: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Message with text content from the assistant. */
|
||||||
|
export type Assistant = {
|
||||||
|
role: 'assistant'
|
||||||
|
name?: string
|
||||||
|
content: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Message with arguments to call a function. */
|
||||||
|
export type FuncCall = {
|
||||||
|
role: 'assistant'
|
||||||
|
name?: string
|
||||||
|
content: null
|
||||||
|
function_call: Call.Function
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Message with the result of a function call. */
|
||||||
|
export type FuncResult = {
|
||||||
|
role: 'function'
|
||||||
|
name: string
|
||||||
|
content: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Message with arguments to call one or more tools. */
|
||||||
|
export type ToolCall = {
|
||||||
|
role: 'assistant'
|
||||||
|
name?: string
|
||||||
|
content: null
|
||||||
|
tool_calls: Call.Tool[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Message with the result of a tool call. */
|
||||||
|
export type ToolResult = {
|
||||||
|
role: 'tool'
|
||||||
|
tool_call_id: string
|
||||||
|
content: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Utility functions for creating and checking message types. */
|
||||||
|
export namespace Msg {
|
||||||
|
/** Create a system message. Cleans indentation and newlines by default. */
|
||||||
|
export function system(
|
||||||
|
content: string,
|
||||||
|
opts?: {
|
||||||
|
/** Custom name for the message. */
|
||||||
|
name?: string
|
||||||
|
/** Whether to clean extra newlines and indentation. Defaults to true. */
|
||||||
|
cleanContent?: boolean
|
||||||
|
}
|
||||||
|
): Msg.System {
|
||||||
|
const { name, cleanContent = true } = opts ?? {}
|
||||||
|
return {
|
||||||
|
role: 'system',
|
||||||
|
content: cleanContent ? cleanStringForModel(content) : content,
|
||||||
|
...(name ? { name } : {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Create a user message. Cleans indentation and newlines by default. */
|
||||||
|
export function user(
|
||||||
|
content: string,
|
||||||
|
opts?: {
|
||||||
|
/** Custom name for the message. */
|
||||||
|
name?: string
|
||||||
|
/** Whether to clean extra newlines and indentation. Defaults to true. */
|
||||||
|
cleanContent?: boolean
|
||||||
|
}
|
||||||
|
): Msg.User {
|
||||||
|
const { name, cleanContent = true } = opts ?? {}
|
||||||
|
return {
|
||||||
|
role: 'user',
|
||||||
|
content: cleanContent ? cleanStringForModel(content) : content,
|
||||||
|
...(name ? { name } : {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Create an assistant message. Cleans indentation and newlines by default. */
|
||||||
|
export function assistant(
|
||||||
|
content: string,
|
||||||
|
opts?: {
|
||||||
|
/** Custom name for the message. */
|
||||||
|
name?: string
|
||||||
|
/** Whether to clean extra newlines and indentation. Defaults to true. */
|
||||||
|
cleanContent?: boolean
|
||||||
|
}
|
||||||
|
): Msg.Assistant {
|
||||||
|
const { name, cleanContent = true } = opts ?? {}
|
||||||
|
return {
|
||||||
|
role: 'assistant',
|
||||||
|
content: cleanContent ? cleanStringForModel(content) : content,
|
||||||
|
...(name ? { name } : {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Create a function call message with argumets. */
|
||||||
|
export function funcCall(
|
||||||
|
function_call: {
|
||||||
|
/** Name of the function to call. */
|
||||||
|
name: string
|
||||||
|
/** Arguments to pass to the function. */
|
||||||
|
arguments: string
|
||||||
|
},
|
||||||
|
opts?: {
|
||||||
|
/** The name descriptor for the message.(message.name) */
|
||||||
|
name?: string
|
||||||
|
}
|
||||||
|
): Msg.FuncCall {
|
||||||
|
return {
|
||||||
|
...opts,
|
||||||
|
role: 'assistant',
|
||||||
|
content: null,
|
||||||
|
function_call
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Create a function result message. */
|
||||||
|
export function funcResult(
|
||||||
|
content: Jsonifiable,
|
||||||
|
name: string
|
||||||
|
): Msg.FuncResult {
|
||||||
|
const contentString = stringifyForModel(content)
|
||||||
|
return { role: 'function', content: contentString, name }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Create a function call message with argumets. */
|
||||||
|
export function toolCall(
|
||||||
|
tool_calls: Msg.Call.Tool[],
|
||||||
|
opts?: {
|
||||||
|
/** The name descriptor for the message.(message.name) */
|
||||||
|
name?: string
|
||||||
|
}
|
||||||
|
): Msg.ToolCall {
|
||||||
|
return {
|
||||||
|
...opts,
|
||||||
|
role: 'assistant',
|
||||||
|
content: null,
|
||||||
|
tool_calls
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Create a tool call result message. */
|
||||||
|
export function toolResult(
|
||||||
|
content: Jsonifiable,
|
||||||
|
tool_call_id: string,
|
||||||
|
opts?: {
|
||||||
|
/** The name of the tool which was called */
|
||||||
|
name?: string
|
||||||
|
}
|
||||||
|
): Msg.ToolResult {
|
||||||
|
const contentString = stringifyForModel(content)
|
||||||
|
return { ...opts, role: 'tool', tool_call_id, content: contentString }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get the narrowed message from an EnrichedResponse. */
|
||||||
|
export function getMessage(
|
||||||
|
// @TODO
|
||||||
|
response: any
|
||||||
|
// response: ChatModel.EnrichedResponse
|
||||||
|
): Msg.Assistant | Msg.FuncCall | Msg.ToolCall {
|
||||||
|
const msg = response.choices[0].message as Msg
|
||||||
|
return narrowResponseMessage(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Narrow a message received from the API. It only responds with role=assistant */
|
||||||
|
export function narrowResponseMessage(
|
||||||
|
msg: Msg
|
||||||
|
): Msg.Assistant | Msg.FuncCall | Msg.ToolCall {
|
||||||
|
if (msg.content === null && msg.tool_calls != null) {
|
||||||
|
return Msg.toolCall(msg.tool_calls)
|
||||||
|
} else if (msg.content === null && msg.function_call != null) {
|
||||||
|
return Msg.funcCall(msg.function_call)
|
||||||
|
} else if (msg.content !== null) {
|
||||||
|
return Msg.assistant(msg.content)
|
||||||
|
} else {
|
||||||
|
// @TODO: probably don't want to error here
|
||||||
|
console.log('Invalid message', msg)
|
||||||
|
throw new Error('Invalid message')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Check if a message is a system message. */
|
||||||
|
export function isSystem(message: Msg): message is Msg.System {
|
||||||
|
return message.role === 'system'
|
||||||
|
}
|
||||||
|
/** Check if a message is a user message. */
|
||||||
|
export function isUser(message: Msg): message is Msg.User {
|
||||||
|
return message.role === 'user'
|
||||||
|
}
|
||||||
|
/** Check if a message is an assistant message. */
|
||||||
|
export function isAssistant(message: Msg): message is Msg.Assistant {
|
||||||
|
return message.role === 'assistant' && message.content !== null
|
||||||
|
}
|
||||||
|
/** Check if a message is a function call message with arguments. */
|
||||||
|
export function isFuncCall(message: Msg): message is Msg.FuncCall {
|
||||||
|
return message.role === 'assistant' && message.function_call != null
|
||||||
|
}
|
||||||
|
/** Check if a message is a function result message. */
|
||||||
|
export function isFuncResult(message: Msg): message is Msg.FuncResult {
|
||||||
|
return message.role === 'function' && message.name != null
|
||||||
|
}
|
||||||
|
/** Check if a message is a tool calls message. */
|
||||||
|
export function isToolCall(message: Msg): message is Msg.ToolCall {
|
||||||
|
return message.role === 'assistant' && message.tool_calls != null
|
||||||
|
}
|
||||||
|
/** Check if a message is a tool call result message. */
|
||||||
|
export function isToolResult(message: Msg): message is Msg.ToolResult {
|
||||||
|
return message.role === 'tool' && !!message.tool_call_id
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Narrow a ChatModel.Message to a specific type. */
|
||||||
|
export function narrow(message: Msg.System): Msg.System
|
||||||
|
export function narrow(message: Msg.User): Msg.User
|
||||||
|
export function narrow(message: Msg.Assistant): Msg.Assistant
|
||||||
|
export function narrow(message: Msg.FuncCall): Msg.FuncCall
|
||||||
|
export function narrow(message: Msg.FuncResult): Msg.FuncResult
|
||||||
|
export function narrow(message: Msg.ToolCall): Msg.ToolCall
|
||||||
|
export function narrow(message: Msg.ToolResult): Msg.ToolResult
|
||||||
|
export function narrow(
|
||||||
|
message: Msg
|
||||||
|
):
|
||||||
|
| Msg.System
|
||||||
|
| Msg.User
|
||||||
|
| Msg.Assistant
|
||||||
|
| Msg.FuncCall
|
||||||
|
| Msg.FuncResult
|
||||||
|
| Msg.ToolCall
|
||||||
|
| Msg.ToolResult {
|
||||||
|
if (isSystem(message)) {
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
if (isUser(message)) {
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
if (isAssistant(message)) {
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
if (isFuncCall(message)) {
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
if (isFuncResult(message)) {
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
if (isToolCall(message)) {
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
if (isToolResult(message)) {
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
throw new Error('Invalid message type')
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,7 @@ import { DynamicStructuredTool } from '@langchain/core/tools'
|
||||||
|
|
||||||
import type { AIFunctionLike } from '../types.js'
|
import type { AIFunctionLike } from '../types.js'
|
||||||
import { AIFunctionSet } from '../ai-function-set.js'
|
import { AIFunctionSet } from '../ai-function-set.js'
|
||||||
import { stringifyForModel } from '../stringify-for-model.js'
|
import { stringifyForModel } from '../utils.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a set of Agentic stdlib AI functions to an array of LangChain-
|
* Converts a set of Agentic stdlib AI functions to an array of LangChain-
|
||||||
|
|
|
@ -1,9 +1,18 @@
|
||||||
import defaultKy, { type KyInstance } from 'ky'
|
import defaultKy, { type KyInstance } from 'ky'
|
||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
import type * as types from '../types.js'
|
import { aiFunction, AIFunctionsProvider } from '../fns.js'
|
||||||
|
import { Msg } from '../message.js'
|
||||||
import { assert, getEnv } from '../utils.js'
|
import { assert, getEnv } from '../utils.js'
|
||||||
|
|
||||||
export class DexaClient {
|
export namespace dexa {
|
||||||
|
export const AskDexaOptionsSchema = z.object({
|
||||||
|
question: z.string().describe('The question to ask Dexa.')
|
||||||
|
})
|
||||||
|
export type AskDexaOptions = z.infer<typeof AskDexaOptionsSchema>
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DexaClient extends AIFunctionsProvider {
|
||||||
readonly apiKey: string
|
readonly apiKey: string
|
||||||
readonly apiBaseUrl: string
|
readonly apiBaseUrl: string
|
||||||
readonly ky: KyInstance
|
readonly ky: KyInstance
|
||||||
|
@ -23,6 +32,7 @@ export class DexaClient {
|
||||||
apiKey,
|
apiKey,
|
||||||
'DexaClient missing required "apiKey" (defaults to "DEXA_API_KEY")'
|
'DexaClient missing required "apiKey" (defaults to "DEXA_API_KEY")'
|
||||||
)
|
)
|
||||||
|
super()
|
||||||
|
|
||||||
this.apiKey = apiKey
|
this.apiKey = apiKey
|
||||||
this.apiBaseUrl = apiBaseUrl
|
this.apiBaseUrl = apiBaseUrl
|
||||||
|
@ -30,12 +40,18 @@ export class DexaClient {
|
||||||
this.ky = ky.extend({ prefixUrl: this.apiBaseUrl, timeout: timeoutMs })
|
this.ky = ky.extend({ prefixUrl: this.apiBaseUrl, timeout: timeoutMs })
|
||||||
}
|
}
|
||||||
|
|
||||||
async askDexa({ messages }: { messages: types.Msg[] }) {
|
@aiFunction({
|
||||||
|
name: 'ask_dexa',
|
||||||
|
description:
|
||||||
|
'Answers questions based on knowledge of trusted experts and podcasters. Example experts include: Andrew Huberman, Tim Ferriss, Lex Fridman, Peter Attia, Seth Godin, Rhonda Patrick, Rick Rubin, and more.',
|
||||||
|
inputSchema: dexa.AskDexaOptionsSchema
|
||||||
|
})
|
||||||
|
async askDexa(opts: dexa.AskDexaOptions) {
|
||||||
return this.ky
|
return this.ky
|
||||||
.post('api/ask-dexa', {
|
.post('api/ask-dexa', {
|
||||||
json: {
|
json: {
|
||||||
secret: this.apiKey,
|
secret: this.apiKey,
|
||||||
messages
|
messages: [Msg.user(opts.question)]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.json<string>()
|
.json<string>()
|
||||||
|
|
|
@ -414,6 +414,7 @@ export namespace diffbot {
|
||||||
locations?: Location[]
|
locations?: Location[]
|
||||||
location?: Location
|
location?: Location
|
||||||
interests?: Interest[]
|
interests?: Interest[]
|
||||||
|
emailAddresses?: any
|
||||||
age?: number
|
age?: number
|
||||||
crawlTimestamp?: number
|
crawlTimestamp?: number
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
import { describe, expect, it } from 'vitest'
|
|
||||||
|
|
||||||
import { stringifyForModel } from './stringify-for-model.js'
|
|
||||||
|
|
||||||
describe('stringifyForModel', () => {
|
|
||||||
it('handles basic objects', () => {
|
|
||||||
const input = {
|
|
||||||
foo: 'bar',
|
|
||||||
nala: ['is', 'cute'],
|
|
||||||
kittens: null,
|
|
||||||
cats: undefined,
|
|
||||||
paws: 4.3
|
|
||||||
}
|
|
||||||
const result = stringifyForModel(input)
|
|
||||||
expect(result).toEqual(JSON.stringify(input, null))
|
|
||||||
})
|
|
||||||
|
|
||||||
it('handles empty input', () => {
|
|
||||||
const result = stringifyForModel()
|
|
||||||
expect(result).toEqual('')
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,16 +0,0 @@
|
||||||
import type { Jsonifiable } from 'type-fest'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stringifies a JSON value in a way that's optimized for use with LLM prompts.
|
|
||||||
*/
|
|
||||||
export function stringifyForModel(jsonObject?: Jsonifiable): string {
|
|
||||||
if (jsonObject === undefined) {
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof jsonObject === 'string') {
|
|
||||||
return jsonObject
|
|
||||||
}
|
|
||||||
|
|
||||||
return JSON.stringify(jsonObject, null, 0)
|
|
||||||
}
|
|
127
src/types.ts
127
src/types.ts
|
@ -3,7 +3,9 @@ import type { z } from 'zod'
|
||||||
|
|
||||||
import type { AIFunctionSet } from './ai-function-set.js'
|
import type { AIFunctionSet } from './ai-function-set.js'
|
||||||
import type { AIFunctionsProvider } from './fns.js'
|
import type { AIFunctionsProvider } from './fns.js'
|
||||||
|
import type { Msg } from './message.js'
|
||||||
|
|
||||||
|
export type { Msg } from './message.js'
|
||||||
export type { KyInstance } from 'ky'
|
export type { KyInstance } from 'ky'
|
||||||
export type { ThrottledFunction } from 'p-throttle'
|
export type { ThrottledFunction } from 'p-throttle'
|
||||||
|
|
||||||
|
@ -70,128 +72,3 @@ export interface AITool<
|
||||||
/** The tool spec for the OpenAI API `tools` property. */
|
/** The tool spec for the OpenAI API `tools` property. */
|
||||||
spec: AIToolSpec
|
spec: AIToolSpec
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Generic/default OpenAI message without any narrowing applied.
|
|
||||||
*/
|
|
||||||
export interface Msg {
|
|
||||||
/**
|
|
||||||
* The contents of the message. `content` is required for all messages, and
|
|
||||||
* may be null for assistant messages with function calls.
|
|
||||||
*/
|
|
||||||
content: string | null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The role of the messages author. One of `system`, `user`, `assistant`,
|
|
||||||
* 'tool', or `function`.
|
|
||||||
*/
|
|
||||||
role: Msg.Role
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name and arguments of a function that should be called, as generated
|
|
||||||
* by the model.
|
|
||||||
*/
|
|
||||||
function_call?: Msg.Call.Function
|
|
||||||
|
|
||||||
/** The tool calls generated by the model, such as function calls. */
|
|
||||||
tool_calls?: Msg.Call.Tool[]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tool call that this message is responding to.
|
|
||||||
*/
|
|
||||||
tool_call_id?: string
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name of the author of this message. `name` is required if role is
|
|
||||||
* `function`, and it should be the name of the function whose response is in the
|
|
||||||
* `content`. May contain a-z, A-Z, 0-9, and underscores, with a maximum length of
|
|
||||||
* 64 characters.
|
|
||||||
*/
|
|
||||||
name?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Narrowed Message types. */
|
|
||||||
export namespace Msg {
|
|
||||||
/** Possible roles for a message. */
|
|
||||||
export type Role = 'system' | 'user' | 'assistant' | 'function' | 'tool'
|
|
||||||
|
|
||||||
export namespace Call {
|
|
||||||
/**
|
|
||||||
* The name and arguments of a function that should be called, as generated
|
|
||||||
* by the model.
|
|
||||||
*/
|
|
||||||
export type Function = {
|
|
||||||
/**
|
|
||||||
* The arguments to call the function with, as generated by the model in
|
|
||||||
* JSON format.
|
|
||||||
*/
|
|
||||||
arguments: string
|
|
||||||
|
|
||||||
/** The name of the function to call. */
|
|
||||||
name: string
|
|
||||||
}
|
|
||||||
|
|
||||||
/** The tool calls generated by the model, such as function calls. */
|
|
||||||
export type Tool = {
|
|
||||||
/** The ID of the tool call. */
|
|
||||||
id: string
|
|
||||||
|
|
||||||
/** The type of the tool. Currently, only `function` is supported. */
|
|
||||||
type: 'function'
|
|
||||||
|
|
||||||
/** The function that the model called. */
|
|
||||||
function: Call.Function
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Message with text content for the system. */
|
|
||||||
export type System = {
|
|
||||||
role: 'system'
|
|
||||||
content: string
|
|
||||||
name?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Message with text content from the user. */
|
|
||||||
export type User = {
|
|
||||||
role: 'user'
|
|
||||||
name?: string
|
|
||||||
content: string
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Message with text content from the assistant. */
|
|
||||||
export type Assistant = {
|
|
||||||
role: 'assistant'
|
|
||||||
name?: string
|
|
||||||
content: string
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Message with arguments to call a function. */
|
|
||||||
export type FuncCall = {
|
|
||||||
role: 'assistant'
|
|
||||||
name?: string
|
|
||||||
content: null
|
|
||||||
function_call: Call.Function
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Message with the result of a function call. */
|
|
||||||
export type FuncResult = {
|
|
||||||
role: 'function'
|
|
||||||
name: string
|
|
||||||
content: string
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Message with arguments to call one or more tools. */
|
|
||||||
export type ToolCall = {
|
|
||||||
role: 'assistant'
|
|
||||||
name?: string
|
|
||||||
content: null
|
|
||||||
tool_calls: Call.Tool[]
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Message with the result of a tool call. */
|
|
||||||
export type ToolResult = {
|
|
||||||
role: 'tool'
|
|
||||||
tool_call_id: string
|
|
||||||
content: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
import ky from 'ky'
|
import ky from 'ky'
|
||||||
import pThrottle from 'p-throttle'
|
import pThrottle from 'p-throttle'
|
||||||
import { expect, test } from 'vitest'
|
import { describe, expect, test } from 'vitest'
|
||||||
|
|
||||||
import { mockKyInstance } from './_utils.js'
|
import { mockKyInstance } from './_utils.js'
|
||||||
import { omit, pick, sanitizeSearchParams, throttleKy } from './utils.js'
|
import {
|
||||||
|
omit,
|
||||||
|
pick,
|
||||||
|
sanitizeSearchParams,
|
||||||
|
stringifyForModel,
|
||||||
|
throttleKy
|
||||||
|
} from './utils.js'
|
||||||
|
|
||||||
test('pick', () => {
|
test('pick', () => {
|
||||||
expect(pick({ a: 1, b: 2, c: 3 }, 'a', 'c')).toEqual({ a: 1, c: 3 })
|
expect(pick({ a: 1, b: 2, c: 3 }, 'a', 'c')).toEqual({ a: 1, c: 3 })
|
||||||
|
@ -79,3 +85,22 @@ test(
|
||||||
timeout: 60_000
|
timeout: 60_000
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
describe('stringifyForModel', () => {
|
||||||
|
test('handles basic objects', () => {
|
||||||
|
const input = {
|
||||||
|
foo: 'bar',
|
||||||
|
nala: ['is', 'cute'],
|
||||||
|
kittens: null,
|
||||||
|
cats: undefined,
|
||||||
|
paws: 4.3
|
||||||
|
}
|
||||||
|
const result = stringifyForModel(input)
|
||||||
|
expect(result).toEqual(JSON.stringify(input, null))
|
||||||
|
})
|
||||||
|
|
||||||
|
test('handles empty input', () => {
|
||||||
|
const result = stringifyForModel()
|
||||||
|
expect(result).toEqual('')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
29
src/utils.ts
29
src/utils.ts
|
@ -1,3 +1,6 @@
|
||||||
|
import type { Jsonifiable } from 'type-fest'
|
||||||
|
import dedent from 'dedent'
|
||||||
|
|
||||||
import type * as types from './types.js'
|
import type * as types from './types.js'
|
||||||
|
|
||||||
export { assert } from './assert.js'
|
export { assert } from './assert.js'
|
||||||
|
@ -111,3 +114,29 @@ export function sanitizeSearchParams(
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stringifies a JSON value in a way that's optimized for use with LLM prompts.
|
||||||
|
*/
|
||||||
|
export function stringifyForModel(jsonObject?: Jsonifiable): string {
|
||||||
|
if (jsonObject === undefined) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof jsonObject === 'string') {
|
||||||
|
return jsonObject
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify(jsonObject, null, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
const dedenter = dedent.withOptions({ escapeSpecialCharacters: true })
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean a string by removing extra newlines and indentation.
|
||||||
|
*
|
||||||
|
* @see: https://github.com/dmnd/dedent
|
||||||
|
*/
|
||||||
|
export function cleanStringForModel(text: string): string {
|
||||||
|
return dedenter(text).trim()
|
||||||
|
}
|
||||||
|
|
Ładowanie…
Reference in New Issue