2023-11-29 03:13:55 -08:00
import { AgentExecutor } from 'langchain/agents' ;
import { OpenAI as OpenAIClient } from 'openai' ;
import { OpenAIAssistantRunnable } from 'langchain/experimental/openai_assistant' ;
import { NodeConnectionType , NodeOperationError } from 'n8n-workflow' ;
import type {
IExecuteFunctions ,
INodeExecutionData ,
INodeType ,
INodeTypeDescription ,
} from 'n8n-workflow' ;
import type { OpenAIToolType } from 'langchain/dist/experimental/openai_assistant/schema' ;
2024-02-29 03:28:38 -08:00
import { getConnectedTools } from '../../../utils/helpers' ;
2024-04-08 13:51:49 -07:00
import { getTracingConfig } from '../../../utils/tracing' ;
2023-11-29 03:13:55 -08:00
import { formatToOpenAIAssistantTool } from './utils' ;
export class OpenAiAssistant implements INodeType {
description : INodeTypeDescription = {
displayName : 'OpenAI Assistant' ,
name : 'openAiAssistant' ,
2024-02-15 00:15:58 -08:00
hidden : true ,
2023-11-29 03:13:55 -08:00
icon : 'fa:robot' ,
group : [ 'transform' ] ,
2024-02-29 03:28:38 -08:00
version : [ 1 , 1.1 ] ,
2023-11-29 03:13:55 -08:00
description : 'Utilizes Assistant API from Open AI.' ,
subtitle : 'Open AI Assistant' ,
defaults : {
name : 'OpenAI Assistant' ,
color : '#404040' ,
} ,
codex : {
alias : [ 'LangChain' ] ,
categories : [ 'AI' ] ,
subcategories : {
2024-05-30 07:53:33 -07:00
AI : [ 'Agents' , 'Root Nodes' ] ,
2023-11-29 03:13:55 -08:00
} ,
resources : {
primaryDocumentation : [
{
url : 'https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.openaiassistant/' ,
} ,
] ,
} ,
} ,
inputs : [
{ type : NodeConnectionType . Main } ,
{ type : NodeConnectionType . AiTool , displayName : 'Tools' } ,
] ,
outputs : [ NodeConnectionType . Main ] ,
credentials : [
{
name : 'openAiApi' ,
required : true ,
} ,
] ,
requestDefaults : {
ignoreHttpStatusErrors : true ,
baseURL :
'={{ $parameter.options?.baseURL?.split("/").slice(0,-1).join("/") || "https://api.openai.com" }}' ,
} ,
properties : [
{
displayName : 'Operation' ,
name : 'mode' ,
type : 'options' ,
noDataExpression : true ,
default : 'existing' ,
options : [
{
2023-12-08 04:42:32 -08:00
name : 'Use New Assistant' ,
2023-11-29 03:13:55 -08:00
value : 'new' ,
} ,
{
name : 'Use Existing Assistant' ,
value : 'existing' ,
} ,
] ,
} ,
{
displayName : 'Name' ,
name : 'name' ,
type : 'string' ,
default : '' ,
required : true ,
displayOptions : {
show : {
'/mode' : [ 'new' ] ,
} ,
} ,
} ,
{
displayName : 'Instructions' ,
name : 'instructions' ,
type : 'string' ,
description : 'How the Assistant and model should behave or respond' ,
default : '' ,
typeOptions : {
rows : 5 ,
} ,
displayOptions : {
show : {
'/mode' : [ 'new' ] ,
} ,
} ,
} ,
{
displayName : 'Model' ,
name : 'model' ,
type : 'options' ,
description :
'The model which will be used to power the assistant. <a href="https://beta.openai.com/docs/models/overview">Learn more</a>. The Retrieval tool requires gpt-3.5-turbo-1106 and gpt-4-1106-preview models.' ,
required : true ,
displayOptions : {
show : {
'/mode' : [ 'new' ] ,
} ,
} ,
typeOptions : {
loadOptions : {
routing : {
request : {
method : 'GET' ,
url : '={{ $parameter.options?.baseURL?.split("/").slice(-1).pop() || "v1" }}/models' ,
} ,
output : {
postReceive : [
{
type : 'rootProperty' ,
properties : {
property : 'data' ,
} ,
} ,
{
type : 'filter' ,
properties : {
pass : "={{ $responseItem.id.startsWith('gpt-') && !$responseItem.id.includes('instruct') }}" ,
} ,
} ,
{
type : 'setKeyValue' ,
properties : {
name : '={{$responseItem.id}}' ,
value : '={{$responseItem.id}}' ,
} ,
} ,
{
type : 'sort' ,
properties : {
key : 'name' ,
} ,
} ,
] ,
} ,
} ,
} ,
} ,
routing : {
send : {
type : 'body' ,
property : 'model' ,
} ,
} ,
default : 'gpt-3.5-turbo-1106' ,
} ,
{
displayName : 'Assistant' ,
name : 'assistantId' ,
type : 'options' ,
noDataExpression : true ,
displayOptions : {
show : {
'/mode' : [ 'existing' ] ,
} ,
} ,
description :
'The assistant to use. <a href="https://beta.openai.com/docs/assistants/overview">Learn more</a>.' ,
typeOptions : {
loadOptions : {
routing : {
request : {
method : 'GET' ,
headers : {
'OpenAI-Beta' : 'assistants=v1' ,
} ,
url : '={{ $parameter.options?.baseURL?.split("/").slice(-1).pop() || "v1" }}/assistants' ,
} ,
output : {
postReceive : [
{
type : 'rootProperty' ,
properties : {
property : 'data' ,
} ,
} ,
{
type : 'setKeyValue' ,
properties : {
name : '={{$responseItem.name}}' ,
value : '={{$responseItem.id}}' ,
// eslint-disable-next-line n8n-local-rules/no-interpolation-in-regular-string
description : '={{$responseItem.model}}' ,
} ,
} ,
{
type : 'sort' ,
properties : {
key : 'name' ,
} ,
} ,
] ,
} ,
} ,
} ,
} ,
routing : {
send : {
type : 'body' ,
property : 'assistant' ,
} ,
} ,
required : true ,
default : '' ,
} ,
{
displayName : 'Text' ,
name : 'text' ,
type : 'string' ,
required : true ,
default : '={{ $json.chat_input }}' ,
2024-01-09 03:11:39 -08:00
displayOptions : {
show : {
'@version' : [ 1 ] ,
} ,
} ,
} ,
{
displayName : 'Text' ,
name : 'text' ,
type : 'string' ,
required : true ,
default : '={{ $json.chatInput }}' ,
displayOptions : {
show : {
'@version' : [ 1.1 ] ,
} ,
} ,
2023-11-29 03:13:55 -08:00
} ,
{
displayName : 'OpenAI Tools' ,
name : 'nativeTools' ,
type : 'multiOptions' ,
default : [ ] ,
options : [
{
name : 'Code Interpreter' ,
value : 'code_interpreter' ,
} ,
{
2023-12-08 04:42:32 -08:00
name : 'Knowledge Retrieval' ,
2023-11-29 03:13:55 -08:00
value : 'retrieval' ,
} ,
] ,
} ,
2023-12-08 04:42:32 -08:00
{
displayName : 'Connect your own custom tools to this node on the canvas' ,
name : 'noticeTools' ,
type : 'notice' ,
default : '' ,
} ,
{
displayName :
'Upload files for retrieval using the <a href="https://platform.openai.com/playground" target="_blank">OpenAI website<a/>' ,
name : 'noticeTools' ,
type : 'notice' ,
typeOptions : {
noticeTheme : 'info' ,
} ,
displayOptions : { show : { '/nativeTools' : [ 'retrieval' ] } } ,
default : '' ,
} ,
2023-11-29 03:13:55 -08:00
{
displayName : 'Options' ,
name : 'options' ,
placeholder : 'Add Option' ,
description : 'Additional options to add' ,
type : 'collection' ,
default : { } ,
options : [
{
displayName : 'Base URL' ,
name : 'baseURL' ,
default : 'https://api.openai.com/v1' ,
description : 'Override the default base URL for the API' ,
type : 'string' ,
} ,
{
displayName : 'Max Retries' ,
name : 'maxRetries' ,
default : 2 ,
description : 'Maximum number of retries to attempt' ,
type : 'number' ,
} ,
{
displayName : 'Timeout' ,
name : 'timeout' ,
default : 10000 ,
description : 'Maximum amount of time a request is allowed to take in milliseconds' ,
type : 'number' ,
} ,
] ,
} ,
] ,
} ;
async execute ( this : IExecuteFunctions ) : Promise < INodeExecutionData [ ] [ ] > {
2024-02-29 03:28:38 -08:00
const nodeVersion = this . getNode ( ) . typeVersion ;
const tools = await getConnectedTools ( this , nodeVersion > 1 ) ;
2023-11-29 03:13:55 -08:00
const credentials = await this . getCredentials ( 'openAiApi' ) ;
const items = this . getInputData ( ) ;
const returnData : INodeExecutionData [ ] = [ ] ;
for ( let itemIndex = 0 ; itemIndex < items . length ; itemIndex ++ ) {
2024-04-09 05:06:12 -07:00
try {
const input = this . getNodeParameter ( 'text' , itemIndex ) as string ;
const assistantId = this . getNodeParameter ( 'assistantId' , itemIndex , '' ) as string ;
const nativeTools = this . getNodeParameter ( 'nativeTools' , itemIndex , [ ] ) as Array <
'code_interpreter' | 'retrieval'
> ;
2023-11-29 03:13:55 -08:00
2024-04-09 05:06:12 -07:00
const options = this . getNodeParameter ( 'options' , itemIndex , { } ) as {
baseURL? : string ;
maxRetries : number ;
timeout : number ;
} ;
2023-11-29 03:13:55 -08:00
2024-04-09 05:06:12 -07:00
if ( input === undefined ) {
throw new NodeOperationError ( this . getNode ( ) , 'The ‘ text‘ parameter is empty.' ) ;
}
const client = new OpenAIClient ( {
apiKey : credentials.apiKey as string ,
maxRetries : options.maxRetries ? ? 2 ,
timeout : options.timeout ? ? 10000 ,
baseURL : options.baseURL ,
} ) ;
let agent ;
const nativeToolsParsed : OpenAIToolType = nativeTools . map ( ( tool ) = > ( { type : tool } ) ) ;
const transformedConnectedTools = tools ? . map ( formatToOpenAIAssistantTool ) ? ? [ ] ;
const newTools = [ . . . transformedConnectedTools , . . . nativeToolsParsed ] ;
2023-11-29 03:13:55 -08:00
2024-04-09 05:06:12 -07:00
// Existing agent, update tools with currently assigned
if ( assistantId ) {
agent = new OpenAIAssistantRunnable ( { assistantId , client , asAgent : true } ) ;
2023-11-29 03:13:55 -08:00
2024-04-09 05:06:12 -07:00
await client . beta . assistants . update ( assistantId , {
tools : newTools ,
} ) ;
} else {
const name = this . getNodeParameter ( 'name' , itemIndex , '' ) as string ;
const instructions = this . getNodeParameter ( 'instructions' , itemIndex , '' ) as string ;
const model = this . getNodeParameter ( 'model' , itemIndex , 'gpt-3.5-turbo-1106' ) as string ;
2023-11-29 03:13:55 -08:00
2024-04-09 05:06:12 -07:00
agent = await OpenAIAssistantRunnable . createAssistant ( {
model ,
client ,
instructions ,
name ,
tools : newTools ,
asAgent : true ,
} ) ;
}
2023-11-29 03:13:55 -08:00
2024-04-09 05:06:12 -07:00
const agentExecutor = AgentExecutor . fromAgentAndTools ( {
agent ,
tools ,
2023-11-29 03:13:55 -08:00
} ) ;
2024-04-09 05:06:12 -07:00
const response = await agentExecutor . withConfig ( getTracingConfig ( this ) ) . invoke ( {
content : input ,
signal : this.getExecutionCancelSignal ( ) ,
timeout : options.timeout ? ? 10000 ,
} ) ;
2023-11-29 03:13:55 -08:00
2024-04-09 05:06:12 -07:00
returnData . push ( { json : response } ) ;
} catch ( error ) {
if ( this . continueOnFail ( ) ) {
returnData . push ( { json : { error : error.message } , pairedItem : { item : itemIndex } } ) ;
continue ;
}
2023-11-29 03:13:55 -08:00
2024-04-09 05:06:12 -07:00
throw error ;
}
2023-11-29 03:13:55 -08:00
}
2024-05-22 08:40:52 -07:00
return [ returnData ] ;
2023-11-29 03:13:55 -08:00
}
}