chatgpt-api/packages/core/src/message.ts

345 wiersze
9.4 KiB
TypeScript

import type { Jsonifiable } from 'type-fest'
import { cleanStringForModel, stringifyForModel } from './utils'
/**
* 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')
}
}