Experts.js es la forma más fácil de crear e implementar los asistentes de OpenAI y vincularlos como herramientas para crear un panel de sistema de expertos con memoria ampliada y atención al detalle.
Hecho a través del soporte ❤️ por tinta personalizada | Técnico
La nueva API de asistentes de OpenAI establece un nuevo estándar de la industria, avanzando significativamente más allá de la API de finalización de chat ampliamente adoptada. Representa un salto importante en la usabilidad de los agentes de IA y la forma en que los ingenieros interactúan con LLM. Junto con el mini modelo GPT-4O de vanguardia, los asistentes ahora pueden hacer referencia a archivos e imágenes adjuntos como fuentes de conocimiento dentro de una ventana de contexto administrada llamada hilo. A diferencia de los GPT personalizados, los asistentes admiten instrucciones de hasta 256,000 caracteres, integran con 128 herramientas y utilizan la API innovadora de la tienda vectorial para una búsqueda eficiente de archivos en hasta 10,000 archivos por asistente.
Experts.js tiene como objetivo simplificar el uso de esta nueva API eliminando la complejidad de la gestión de objetos de ejecución y permitiendo que los asistentes se vinculen como herramientas.
import { Assistant , Thread } from "experts" ;
const thread = await Thread . create ( ) ;
const assistant = await Assistant . create ( ) ;
const output = await assistant . ask ( "Say hello." , thread . id ) ;
console . log ( output ) // Hello
Más importante aún, Experts.js presenta a los asistentes como herramientas, lo que permite la creación de sistemas de agentes de IA múltiples. Cada herramienta es un asistente respaldado por LLM que puede asumir roles especializados o cumplir tareas complejas en nombre de su asistente o herramienta para padres. Permitiendo flujos de trabajo de orquestación complejos o coreografiando una serie de tareas muy tejidas. Aquí se muestra un ejemplo de un asistente de empresa con una herramienta de catálogo de productos que tiene una herramienta respaldada por LLM para crear consultas de OpenSearch.
Instalar a través de NPM. El uso es muy simple, solo hay tres objetos para importar.
npm install experts
Experts.js admite tanto la sintaxis de importación de ES6 como las JS comunes requieren declaraciones.
import { Assistant , Tool , Thread } from "experts" ;
El constructor de nuestro objeto de fachada asistente requiere un nombre, descripción e instrucciones. El tercer argumento es un conjunto de opciones que se asignan directamente a todas las opciones de cuerpo de solicitud descritas en la documentación Crear asistente. Todos los ejemplos en expertos. Js están escritos en clases de ES6 por simplicidad. El modelo predeterminado es gpt-4o-mini
.
class MyAssistant extends Assistant {
constructor ( ) {
super ( {
name : "My Assistant" ,
instructions : "..." ,
model : "gpt-4o-mini" ,
tools : [ { type : "file_search" } ] ,
temperature : 0.1 ,
tool_resources : {
file_search : {
vector_store_ids : [ process . env . VECTOR_STORE_ID ] ,
} ,
} ,
} ) ;
}
}
const assistant = await MyAssistant . create ( ) ;
La función de fábrica base de Async Assys.js Assistant.create()
es una forma simple de crear un asistente que usa las mismas opciones de constructor.
const assistant = Assistant . create ( {
name : "My Assistant" ,
instructions : "..." ,
model : "gpt-4o-mini" ,
} ) ;
Importante
La creación de asistentes sin un parámetro id
siempre creará un nuevo asistente. Consulte nuestra sección de implementación para obtener más información.
La función ask()
es una interfaz simple para preguntar o instruir a su asistente (s). Requiere un mensaje y un identificador de hilo. Más en los hilos a continuación. El mensaje puede ser una cadena o objeto de mensaje Nativo de OpenAI. Aquí es donde los expertos.Js realmente brilla. Nunca tiene que administrar los objetos Ejecutar o sus pasos de ejecución directamente.
const output = await assistant . ask ( "..." , threadID )
const output = await assistant . ask ( { role : "user" , content : "..." } , threadID ) ;
Las herramientas y llamadas de funciones normales de OpenAI se admiten a través de nuestro objeto de opciones de constructores a través de tools
y tool_resources
. Experts.js también admite agregar asistentes como herramientas. Se puede encontrar más información sobre el uso de asistentes como herramientas en la siguiente sección. Use la función addAssistantTool
para agregar un asistente como herramienta. Esto debe suceder después de super()
en el constructor de su asistente.
class MainAssistant extends Assistant {
constructor ( ) {
super ( {
name : "Company Assistant" ,
instructions : "..." ,
} ) ;
this . addAssistantTool ( ProductsTools ) ;
}
}
Por defecto, Experts.js aprovecha los eventos de transmisión de asistentes. Estos permiten que sus aplicaciones reciban salidas de texto, imagen y herramientas a través de los eventos de envío de servidor de OpenAI. Aprovechamos los ayudantes de transmisión de Openai-Node y superficial de estos eventos junto con algunos personalizados, dando a sus asistentes para aprovechar el ciclo de vida completo de una carrera.
const assistant = await MainAssistant . create ( ) ;
assistant . on ( "textDelta" , ( delta , _snapshot ) => {
process . stdout . write ( delta . value )
} ) ;
Todos los eventos de transmisión de nodo Operai se admiten a través de la función on()
de nuestro Asistente. Los nombres de eventos disponibles son: event
, textDelta
, textDone
, imageFileDone
, toolCallDelta
, runStepDone
, toolCallDone
y end
Importante
Los eventos de servicio de servidor de OpenAI no son asíncronos/esperan amigos.
Si sus oyentes necesitan realizar el trabajo de manera asíncrata, como redirigir las salidas de herramientas, considere usar nuestras extensiones a estos eventos. Se les llama en este orden después de que se haya completado la ejecución. Los nombres de eventos de Async disponibles son: textDoneAsync
, imageFileDoneAsync
, runStepDoneAsync
, toolCallDoneAsync
y endAsync
.
Si desea hacer recursos adicionales de pie cuando se llama a la función create()
de un asistente, implementa la función beforeInit()
en su clase. Este es un método de async que se llamará antes de que se cree el asistente.
async beforeInit ( ) {
await this . # createFileSearch ( ) ;
}
Del mismo modo, se puede usar la función afterInit()
. Por ejemplo, para escribir ID de asistentes recién creados en un archivo de entorno.
async afterInit ( ) {
// ...
}
Todos los eventos asistentes reciben un argumento de metadatos de Experts'JS adicional. Un objeto que contiene la stream
de la ejecución. Esto le permite usar las funciones ayudantes del nodo Operai, como currentEvent
, finalMessages
, etc.
assistant . on ( "endAsync" , async ( metadata ) => {
await metadata . stream . finalMessages ( ) ;
} ) ;
El uso de un asistente como herramienta es el punto focal central del marco de expertos.js. Las herramientas son una subclase de asistente y encapsulan la interfaz para sus objetos principales. De esta manera, los expertos. Las herramientas JS son componentes reutilizables en su arquitectura de agente. Nuestros ejemplos ilustran un patrón básico de aprobación de mensajes, para la brevedad. Debe aprovechar todas las funciones de llamadas de herramientas y funciones de Openai al máximo.
class EchoTool extends Tool {
constructor ( ) {
super ( {
name : "Echo Tool" ,
instructions : "Echo the same text back to the user" ,
parentsTools : [
{
type : "function" ,
function : {
name : "echo" ,
description : description ,
parameters : {
type : "object" ,
properties : { message : { type : "string" } } ,
required : [ "message" ] ,
} ,
} ,
} ,
] ,
} ) ;
}
}
Precaución
Es fundamental que el nombre de la función de su herramienta sea único en el conjunto completo de nombres de herramientas de su padre.
Como tal, los nombres de la clase de herramientas son importantes y ayudan a los modelos de OpenAI a decidir qué herramienta llamar. Así que elija un buen nombre para su clase de herramientas. Por ejemplo, ProductsOpenSearchTool
será products_open_search
y claramente ayuda al modelo a inferir junto con la descripción de la herramienta qué papel desempeña.
Las herramientas se agregan a su asistente a través de la función addAssistantTool
. Esta función agregará la herramienta a la matriz de herramientas del asistente y actualizará la configuración del asistente. Esto debe suceder después de super()
en el constructor de su asistente.
class MainAssistant extends Assistant {
constructor ( ) {
super ( {
name : "Company Assistant" ,
instructions : "..."
} ) ;
this . addAssistantTool ( EchoTool ) ;
}
}
La respuesta de su asistente de herramientas se enviará automáticamente como salida para el asistente o herramienta matriz.
Por defecto, las herramientas están respaldadas por un model
LLM y realizan los mismos eventos de ciclos de vida, ejecutas, etc. como asistentes. Sin embargo, puede crear una herramienta que no use ninguna de las funciones del Asistente principal estableciendo la opción llm
en false
. Al hacerlo, debe implementar la función ask()
en su herramienta. El valor de retorno se enviará como salida de la herramienta.
class AnswerTwoTool extends Tool {
constructor ( ) {
super ( {
// ...
llm : false ,
parentsTools : [ ... ] ,
} ) ;
}
async ask ( message ) {
return ... ;
}
}
En flujos de trabajo complejos, una herramienta respaldada por LLM se puede usar para convertir las instrucciones de LLM en humanos en el código ejecutable y el resultado de ese código (no la salida LLM) necesitaría enviarse para las salidas de los padres de su herramienta. Por ejemplo, el ProductsOpenSearchTool
podría convertir los mensajes en consultas de OpenSearch, ejecutarlos y devolver los resultados. Las sub clases pueden implementar la función answered()
para controlar la salida. En este caso, la output
sería una consulta de OpenSearch y las salidas de herramientas ahora contienen los resultados de esa consulta generada por LLM.
async answered ( output ) {
const args = JSON . parse ( output ) ;
return await this . opensearchQuery ( args ) ;
}
Alternativamente, las herramientas respaldadas por LLM podrían optar por redirigir sus propias salidas de herramienta a su asistente o herramienta para padres. Ignorando así la salida LLM. Esto también permite que todas las salidas de herramientas de herramientas se envíen como salida de los padres. Más sobre por qué esto es importante en el ejemplo del catálogo de productos a continuación.
class ProductsTool extends Tool {
constructor ( ) {
super ( {
// ...
temperature : 0.1 ,
tools : [ { type : "code_interpreter" } ] ,
outputs : "tools" ,
parentsTools : [ ... ] ,
} ) ;
this . addAssistantTool ( ProductsOpenSearchTool ) ;
this . on ( "imageFileDoneAsync" , this . imageFileDoneAsync . bind ( this ) ) ;
}
}
La API de asistentes de OpenAI presenta un nuevo recurso llamado hilos en los que se almacenan los mensajes y archivos. Esencialmente, los hilos son una ventana de contexto administrada (memoria) para sus agentes. Crear un nuevo hilo con expertos.js es tan fácil como:
const thread = await Thread . create ( ) ;
console . log ( thread . id ) // thread_abc123
También puede crear un hilo con mensajes, archivos o recursos de herramientas para iniciar una conversación. Apoyamos el cuerpo de solicitud de hilo de OpenAI resumido en su referencia de la API de hilos.
const thread = await Thread . create ( {
messages : [
{ role : "user" , content : "My name is Ken" } ,
{ role : "user" , content : "Oh, my last name is Collins" } ,
] ,
} ) ;
const output = await assistant . ask ( "What is my full name?" , thread . id ) ;
console . log ( output ) // Ken Collins
Por defecto, cada herramienta en Experts.js tiene su propio hilo y contexto. Esto evita un problema potencial de bloqueo de hilos que ocurre si una herramienta compartía el hilo de un asistente que todavía espera que se presenten salidas de herramientas. El siguiente diagrama ilustra cómo expertos.js administra hilos en su nombre para evitar este problema:
Todas las preguntas a sus expertos requieren una identificación de hilo. Para aplicaciones de chat, la ID se almacenaría en el cliente. Como un parámetro de ruta de URL. Con Expert.js, no se necesitan otras ID del lado del cliente. Como cada asistente llama a una herramienta respaldada por LLM, encontrará o creará un hilo para esa herramienta según sea necesario. Experts.js almacena esta relación padre -> Hilo infantil para usted utilizando los metadatos de hilo de OpenAI.
Las ejecuciones se gestionan para usted detrás de la función ask
del asistente. Sin embargo, aún puede pasar opciones que se utilizarán al crear una ejecución de una de dos maneras.
Primero, puede especificar run_options
en el constructor del asistente. Estas opciones se utilizarán para todas las ejecuciones creadas por el Asistente. Esta es una excelente manera de obligar al modelo a usar una herramienta a través de la opción tool_choice
.
class CarpenterAssistant extends Assistant {
constructor ( ) {
super ( {
// ...
run_options : {
tool_choice : {
type : "function" ,
function : { name : "my_tool_name" } ,
} ,
} ,
} ) ;
this . addAssistantTool ( MyTool ) ;
}
}
Alternativamente, puede pasar un objeto de opciones al método ask
que se use para la ejecución actual. Esta es una excelente manera de crear opciones de ejecución única.
await assistant . ask ( "..." , "thread_abc123" , {
run : {
tool_choice : { type : "function" , function : { name : "my_tool_name" } } ,
additional_instructions : "..." ,
additional_messages : [ ... ] ,
} ,
} ) ;
Para ver ejemplos de código de estos y más en acción, eche un vistazo a nuestra suite de prueba.
En la sección Descripción general mostramos un sistema de agentes de tres niveles que puede responder a los siguientes tipos de preguntas. Los ejemplos utilizan la mayoría, si no todas, las características del marco Experts.js.
Ejemplo básico utilizando el evento textDelta
para transmitir las respuestas desde una ruta Express.
import express from "express" ;
import { MainAssistant } from "../experts/main.js" ;
const assistant = await MainAssistant . create ( ) ;
messagesRouter . post ( "" , async ( req , res , next ) => {
res . setHeader ( "Content-Type" , "text/plain" ) ;
res . setHeader ( "Transfer-Encoding" , "chunked" ) ;
assistant . on ( "textDelta" , ( delta , _snapshot ) => {
res . write ( delta . value ) ;
} ) ;
await assistant . ask ( req . body . message . content , req . body . threadID ) ;
res . end ( ) ;
} ) ;
La API del asistente admite mensajes con imágenes utilizando los tipos de contenido image_url
o image_file
. Dado que nuestra función ask()
admite cadenas o objetos de mensajes de OpenAI nativos.
const output = await assistant . ask (
{
role : "user" ,
content : [
{ type : "text" , text : "Tell me about this image." } ,
{ type : "image_file" , image_file : { file_id : file . id detail : "high" } } ,
] ,
} ,
threadID
) ;
El uso de una tienda vectorial para la búsqueda de archivos es fácil usar la interfaz de OpenAI a través de nuestra tercera opción de configuración. Alternativamente, puede crear su tienda vectorial a pedido utilizando nuestra función beforeInit()
la función descrita en características avanzadas.
class VectorSearchAssistant extends Assistant {
constructor ( ) {
super ( {
name : "Vector Search Assistant" ,
instructions : "..." ,
tools : [ { type : "file_search" } ] ,
temperature : 0.1 ,
tool_resources : {
file_search : {
vector_store_ids : [ process . env . VECTOR_STORE_ID ] ,
} ,
} ,
} ) ;
}
}
El uso de la función de transmisión y eventos para informar el uso de token le permite tener métricas perassistentes.
class MyAssistant extends Assistant {
constructor ( ) {
super ( {
// ...
} ) ;
this . on ( "runStepDone" , this . # reportUsage . bind ( this ) ) ;
}
# reportUsage ( runStep ) {
if ( ! runStep ?. usage ?. total_tokens ) return ;
const iT = runStep . usage . prompt_tokens ;
const oT = runStep . usage . completion_tokens ;
const tT = runStep . usage . total_tokens ;
console . log ( { InTokens : iT , OutTokens : oT , TotalTokens : tT } ) ;
}
}
Para que un asistente se despliegue en un entorno de producción, recomendamos las siguientes configuraciones. Primero, cree o encuentre la identificación de su asistente. La cadena estará en el formato de asst_abc123
. Luego pase esta identificación al constructor del asistente o herramientas. Esto asegurará que el mismo asistente se use en todas las implementaciones.
class MyAssistant extends Assistant {
constructor ( ) {
super ( {
// ...
id : process . env . MY_ASSISTANT_ID
} ) ;
}
}
Una vez que ID encuentra un asistente o herramienta, las configuraciones locales sobrescriben un asistente o herramienta, las configuraciones locales están sobrescribidas. Si es necesario, por ejemplo, en un entorno de puesta en escena, puede evitar este comportamiento estableciendo la opción skipUpdate
en true
.
Puede establecer globalmente el modelo para todos los asistentes utilizando la variable de entorno EXPERTS_DEFAULT_MODEL
. Esto solo funciona si no ha establecido explícitamente el modelo en el constructor de su asistente.
Para depurar su asistente, puede establecer la variable de entorno DEBUG=1
. Esto generará el registro detallado de todas las llamadas de API y eventos de envío de servidor. Los eventos delta pueden ser algo detallados y están deshabilitados de forma predeterminada. También use la variable de entorno DEBUG_DELTAS=1
para activarlos.
Este proyecto aprovecha los contenedores de desarrollo, lo que significa que puede abrirlo en cualquier IDE de apoyo para comenzar de inmediato. Esto incluye usar el código VS con contenedores de desarrollo, que es el enfoque recomendado.
Una vez abierto en su contenedor de desarrollo, cree un archivo .env.development.local
con su tecla API de OpenAI y la tecla API de Postimage.org:
OPENAI_API_KEY=sk-...
POST_IMAGES_API_KEY=...
Ahora puede ejecutar los siguientes comandos:
./bin/setup
./bin/test