Добро пожаловать в репозиторий примеров для интеграции пользовательских вторых пилотов в терминал OpenBB.
Этот репозиторий предоставляет все необходимое для создания и добавления собственных дополнительных пилотов в OpenBB Copilot.
Вот несколько распространенных причин, по которым вы можете захотеть создать свой собственный второй пилот:
Чтобы интегрировать собственный Copilot, с которым вы можете взаимодействовать из терминала OpenBB, вам необходимо создать внутренний API, к которому Терминал может отправлять запросы.
Ваш пользовательский API второго пилота будет отвечать событиями, отправленными сервером (SSE).
Примечание. Если вы хотите быстро приступить к работе, мы предлагаем запустить один из примеров второго пилота, включенного в этот репозиторий, и добавить его в качестве пользовательского второго пилота в терминал OpenBB (каждый пример второго пилота включает инструкции по их запуску). Клонирование и изменение примера второго пилота — отличный способ создать собственный второй пилот.
Самая важная концепция, которую необходимо понять, заключается в том, что протокол второго пилота не имеет состояния . Это означает, что каждый запрос от терминала OpenBB к вашему второму пилоту будет включать в полезную нагрузку запроса все предыдущие сообщения (такие как завершение AI, человеческие сообщения, вызовы функций и результаты вызова функций).
Это означает, что вашему пользовательскому второму пилоту не обязательно поддерживать какое-либо состояние между запросами. Он должен просто использовать полезную нагрузку запроса для генерации ответа.
Терминал OpenBB несет полную ответственность за поддержание состояния разговора и добавит ответы в массив messages
в полезных данных запроса.
Терминал OpenBB будет отправлять POST-запросы к конечной точке query
, определенной в вашем файле copilots.json
(подробнее об этом позже). Полезная нагрузка этого запроса будет содержать такие данные, как сообщения текущего разговора, любой явно добавленный контекст, информацию о виджетах на активной в данный момент панели мониторинга, URL-адреса для получения и т. д.
Ядро схемы запроса, которую вы должны реализовать, выглядит следующим образом:
{
"messages" : [ # <-- the chat messages between the user and the copilot (including function calls and results)
{
"role" : "human" , # <-- each message has a role: "human", "ai", or "tool"
"content" : "Hi there." # <-- the content of the message
}
],
"context" : [ # <-- explicitly added context by the user (optional)
{
"uuid" : "3fa85f64-5717-4562-b3fc-2c963f66afa6" , # <-- the UUID of the widget
"name" : "<widget name>" , # <-- the name of the widget
"description" : "<widget description>" , # <-- the description of the widget
"data" : {
"content" : "<data>" # <-- the data of the widget
},
"metadata" : {
"<metadata key>" : "<metadata value>" , # <-- the metadata of the widget
...
}
},
...
],
"widgets" : [ # <-- the widgets currently visible on the active dashboard on Terminal Pro (optional)
{
"uuid" : "3fa85f64-5717-4562-b3fc-2c963f66afa6" , # <-- the UUID of the widget
"name" : "<widget name>" , # <-- the name of the widget
"description" : "<widget description>" , # <-- the description of the widget
"metadata" : {
"<metadata key>" : "<metadata value>" , # <-- the metadata of the widget
...
}
},
...
],
}
Ниже мы рассмотрим каждое из этих полей более подробно.
messages
Это список сообщений между пользователем и вторым пилотом. Сюда входят сообщения пользователя, сообщения второго пилота, вызовы функций и результаты вызова функций. Каждое сообщение имеет role
и content
.
Самый простой пример — когда не используется вызов функции, который просто состоит из массива сообщений human
и ai
.
Терминал OpenBB автоматически добавляет все возвращаемые сообщения ai
(от вашего Copilot) в массив messages
любого последующего запроса.
# Only one human message
{
"messages" : [
{
"role" : "human" ,
"content" : "Hi there."
}
],
...
}
# Multiple messages
{
"messages" : [
{
"role" : "human" ,
"content" : "Hi there."
},
{
"role" : "ai" ,
"content" : "Hi there, I'm a copilot. How are you?"
},
{
"role" : "human" ,
"content" : "I'm fine, thank you. What is the weather in Tokyo?"
}
],
...
}
Вызовы функций Terminal Pro (например, при получении данных виджета), а также результаты этих вызовов функций (содержащих данные виджета) также включаются в массив messages
. Информацию о вызове функций см. в разделе «Вызов функций» ниже.
context
Это необязательный массив данных виджетов, который будет отправлен терминалом OpenBB, когда виджеты были явно добавлены пользователем в качестве контекста. Это происходит, когда пользователь нажимает кнопку «Добавить как контекст» на виджете в терминале OpenBB.
Поле context
работает следующим образом:
{
...
" context ": [
{
"uuid" : "3fa85f64-5717-4562-b3fc-2c963f66afa6" , # <-- each widget has a UUID
"name" : "Analyst Estimates" ,
"description" : "Contains analyst estimates for a ticker" ,
"data" : {
"content" : "<data>" # <-- the data of the widget could either be a JSON string or plaintext (you must choose how to handle this in your copilot)
},
"metadata" : { # <-- additional metadata about the widget
"symbol" : "AAPL" ,
"period" : "quarter" ,
"source" : "Financial Modelling Prep" ,
"lastUpdated" : 1728998071322
}
},
{
"uuid" : "8b2e5f79-3a1d-4c9e-b6f8-1e7d2a9c0b3d" , # <-- the context can have multiple widgets
"name" : "Earnings Transcripts" ,
"description" : "Contains earnings transcripts for a ticker" ,
"data" : {
"content" : "<data>" # <-- the data of the widget
},
"metadata" : {
"symbol" : "AAPL" ,
"period" : "quarter" ,
"source" : "Intrinio" ,
"lastUpdated" : 1728998071322
}
},
...
],
...
}
widgets
Это массив виджетов, которые в настоящее время видны на активной панели управления Terminal Pro. * Это полезно только в том случае, если вы планируете реализовать вызов функций в своем пользовательском втором пилоте (что рекомендуется, но не обязательно), что позволяет ему запрашивать данные виджета из активной в данный момент панели управления пользователя в терминале OpenBB.
{
...
" widgets ": [
{
"uuid" : "c276369e-e469-4689-b5fe-3f8c76f7c45a" ,
"name" : "Stock Price Quote Widget" ,
"description" : "Contains the current stock price of a ticker" ,
"metadata" : {
"ticker" : "AAPL"
}
},
{
"uuid" : "9f8e7d6c-5b4a-3c2e-1d0f-9e8d7c6b5a4b" ,
"name" : "Financial Ratios Widget" ,
"description" : "Displays key financial ratios for a company" ,
"metadata" : {
"ticker" : "AAPL" ,
"period" : "TTM"
}
},
...
],
...
}
Ваш пользовательский второй пилот должен ответить на запрос терминала OpenBB, используя события, отправленные сервером (SSE).
Терминал OpenBB может обрабатывать следующие SSE:
copilotMessageChunk
: используется для возврата потоковых токенов второго пилота (частичных ответов) обратно в терминал OpenBB. Эти ответы можно передавать в потоковом режиме по мере их создания.copilotFunctionCall
: используется для запроса данных (например, данных виджета) или выполнения определенной функции. Это дает указание Terminal Pro предпринять дальнейшие действия на стороне клиента. Это необходимо только в том случае, если вы планируете реализовать вызов функций в своем втором пилотном проекте. copilotMessageChunk
Блок сообщения SSE имеет следующий формат:
event: copilotMessageChunk
data: {"delta":"H"} # <-- the `data` field must be a JSON object.
delta
должна быть строкой, но может иметь любую длину. Мы предлагаем передавать обратно каждый фрагмент, полученный от вашего LLM, как только он будет сгенерирован как delta
.
Например, если вы хотите передать обратно сообщение «Привет!», вы должны отправить следующие SSE:
event: copilotMessageChunk
data: {"delta":"H"}
event: copilotMessageChunk
data: {"delta":"i"}
event: copilotMessageChunk
data: {"delta":"!"}
copilotFunctionCall
(требуется только для вызова функции)Вызов функции SSE имеет следующий формат:
event: copilotFunctionCall
data: {"function":"get_widget_data","input_arguments":{"widget_uuid":"c276369e-e469-4689-b5fe-3f8c76f7c45a"}}
Опять же, поле data
должно быть объектом JSON. Поле function
— это имя вызываемой функции (в настоящее время поддерживается только get_widget_data
), а поле input_arguments
— это словарь аргументов, которые будут переданы в функцию. Для функции get_widget_data
единственным обязательным аргументом является widget_uuid
, который представляет собой UUID виджета, для которого требуется получить данные (из одного из UUID в массиве widgets
запроса).
Добавив вызов функции к вашему второму пилоту, он сможет запрашивать данные, которые видны на активной в данный момент информационной панели пользователя в терминале OpenBB.
Список всех виджетов, видимых в данный момент на информационной панели пользователя, отправляется вашему второму пилоту в массиве widgets
полезных данных запроса.
Чтобы получить данные из виджета, ваш второй пилот должен ответить событием copilotFunctionCall
, указав UUID виджета:
event: copilotFunctionCall
data: {"function":"get_widget_data","input_arguments":{"widget_uuid":"c276369e-e469-4689-b5fe-3f8c76f7c45a"}}
После отправки события copilotFunctionCall
вы должны закрыть соединение и дождаться нового запроса от терминала OpenBB.
При получении события copilotFunctionCall
терминал OpenBB получит данные и инициирует новый запрос. Этот новый запрос будет включать исходный вызов функции, а также результат вызова функции в массиве messages
.
{
...
" messages ": [
...
{
"role" : "ai" ,
"content" : "{ " function " : " get_widget_data " , " input_arguments " :{ " widget_uuid " : " c276369e-e469-4689-b5fe-3f8c76f7c45a " }}"
},
{
"role" : "tool" ,
"function" : "get_widget_data" ,
"data" : {
"content" : "<data>"
}
}
]
}
Обратите внимание:
messages
.content
сообщения ai
вызова функции представляет собой дословно закодированный в виде строки объект JSON поля data
события copilotFunctionCall
(это очень полезный механизм для перенаправления дополнительных метаданных, связанных с вызовом функции, если это нужно вашему второму пилоту). В настоящее время единственным вызовом функции, поддерживаемым терминалом OpenBB, является get_widget_data
, который извлекает данные из определенного виджета.
Ваш пользовательский второй пилот получает следующий запрос от терминала OpenBB:
{
"messages" : [
{
"role" : " human " ,
"content" : " What is the current stock price of AAPL? "
}
],
"widgets" : [
{
"uuid" : " 38181a68-9650-4940-84fb-a3f29c8869f3 " ,
"name" : " Historical Stock Price " ,
"description" : " Historical Stock Price " ,
"metadata" : {
"symbol" : " AAPL " ,
"source" : " Financial Modelling Prep " ,
"lastUpdated" : 1728994470324
}
}
]
}
Затем вы анализируете ответ, форматируете сообщения в свой LLM (включая информацию о том, какие виджеты доступны). Предположим, ваш второй пилот определяет, что на запрос пользователя можно ответить с помощью доступного виджета, и генерирует вызов функции для получения данных.
Затем ваш второй пилот отвечает следующим SSE и закрывает соединение:
event: copilotFunctionCall
data: {"function":"get_widget_data","input_arguments":{"widget_uuid":"38181a68-9650-4940-84fb-a3f29c8869f3"}}
Затем OpenBB Terminal выполнит указанную функцию и отправит новый запрос вашему второму пилоту:
{
"messages" : [
{
"role" : "human" ,
"content" : "What is the current stock price of AAPL?"
},
{
"role" : "ai" ,
"content" : "{ " function " : " get_widget_data " , " input_arguments " :{ " widget_uuid " : " 38181a68-9650-4940-84fb-a3f29c8869f3 " }}"
},
{
"role" : "tool" ,
"function" : "get_widget_data" ,
"data" : {
"content" : "[{ " date " : " 2024-10-15T00:00:00-04:00 " , " open " :233.61, " high " :237.49, " low " :232.37, " close " :233.85, " volume " :61901688, " vwap " :234.33, " adj_close " :233.85, " change " :0.24, " change_percent " :0.0010274},{ " date " : " 2024-10-14T00:00:00-04:00 " , " open " :228.7, " high " :231.73, " low " :228.6, " close " :231.3, " volume " :39882100, " vwap " :230.0825, " adj_close " :231.3, " change " :2.6, " change_percent " :0.0114},{ " date " : " 2024-10-11T00:00:00-04:00 " , " open " :229.3, " high " :233.2, " low " :228.9, " close " :231.0, " volume " :32581944, " vwap " :231.0333, " adj_close " :231.0, " change " :1.7, " change_percent " :0.0074}, ... ]"
}
}
],
"widgets" : [
{
"uuid" : "38181a68-9650-4940-84fb-a3f29c8869f3" ,
"name" : "Historical Stock Price" ,
"description" : "Historical Stock Price" ,
"metadata" : {
"symbol" : "AAPL" ,
"source" : "Financial Modelling Prep" ,
"lastUpdated" : 1728994470324
}
}
}
Затем вы анализируете ответ, обрабатываете данные и форматируете сообщения в свой LLM. Предположим, что LLM затем генерирует строку токенов для ответа на запрос пользователя. Затем они передаются обратно пользователю с помощью copilotMessageChunk
SSE:
event: copilotMessageChunk
data: {"delta":"The"}
event: copilotMessageChunk
data: {"delta":" current"}
event: copilotMessageChunk
data: {"delta":" stock"}
event: copilotMessageChunk
data: {"delta":" price"}
event: copilotMessageChunk
data: {"delta":" of"}
event: copilotMessageChunk
data: {"delta":" Apple"}
event: copilotMessageChunk
data: {"delta":" Inc."}
event: copilotMessageChunk
data: {"delta":" (AAPL)"}
event: copilotMessageChunk
data: {"delta":" is"}
event: copilotMessageChunk
data: {"delta":" $150.75."}
copilots.json
) Чтобы интегрировать ваш собственный второй пилот с терминалом OpenBB, вам необходимо настроить и использовать файл copilots.json
. Этот файл определяет, как ваш настраиваемый второй пилот соединяется с внешним интерфейсом, в том числе какие функции поддерживаются вашим настраиваемым вторым пилотом и куда следует отправлять запросы.
Вот пример конфигурации copilots.json:
{
"example_copilot" : { # <-- the ID of your copilot
"name" : "Mistral Example Co. Copilot" , # <-- the display name of your copilot
"description" : "AI-powered financial copilot that uses Mistral Large as its LLM." , # <-- a short description of your copilot
"image" : "<url>" , # <-- a URL to an image icon for your copilot
"hasStreaming" : true , # <-- whether your copilot supports streaming responses via SSEs. This must always be true.
"hasFunctionCalling" : true , # <-- whether your copilot supports function calling
"endpoints" : {
"query" : "<url>" # <-- the URL that Terminal Pro will send requests to. For example, "http://localhost:7777/v1/query"
}
}
}
Ваш файл copilots.json
должен храниться по адресу <your-host>/copilots.json
, например http://localhost:7777/copilots.json
.