Skip to content

Commit c11c43c

Browse files
authored
Feature/OpenAI Tool Agent (#1991)
add openai tool agent
1 parent ec1bbc8 commit c11c43c

File tree

9 files changed

+435
-1029
lines changed

9 files changed

+435
-1029
lines changed
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { AgentExecutor, formatAgentSteps } from '../../../src/agents'
1414
import { Moderation, checkInputs, streamResponse } from '../../moderation/Moderation'
1515
import { formatResponse } from '../../outputparsers/OutputParserHelpers'
1616

17-
class MistralAIFunctionAgent_Agents implements INode {
17+
class MistralAIToolAgent_Agents implements INode {
1818
label: string
1919
name: string
2020
version: number
@@ -28,14 +28,14 @@ class MistralAIFunctionAgent_Agents implements INode {
2828
badge?: string
2929

3030
constructor(fields?: { sessionId?: string }) {
31-
this.label = 'MistralAI Function Agent'
32-
this.name = 'mistralAIFunctionAgent'
31+
this.label = 'MistralAI Tool Agent'
32+
this.name = 'mistralAIToolAgent'
3333
this.version = 1.0
3434
this.type = 'AgentExecutor'
3535
this.category = 'Agents'
3636
this.icon = 'MistralAI.svg'
3737
this.badge = 'NEW'
38-
this.description = `An agent that uses MistralAI Function Calling to pick the tool and args to call`
38+
this.description = `Agent that uses MistralAI Function Calling to pick the tools and args to call`
3939
this.baseClasses = [this.type, ...getBaseClasses(AgentExecutor)]
4040
this.inputs = [
4141
{
@@ -204,4 +204,4 @@ const prepareAgent = (
204204
return executor
205205
}
206206

207-
module.exports = { nodeClass: MistralAIFunctionAgent_Agents }
207+
module.exports = { nodeClass: MistralAIToolAgent_Agents }

packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class OpenAIFunctionAgent_Agents implements INode {
2323
category: string
2424
baseClasses: string[]
2525
inputs: INodeParams[]
26+
badge?: string
2627
sessionId?: string
2728

2829
constructor(fields?: { sessionId?: string }) {
@@ -32,8 +33,9 @@ class OpenAIFunctionAgent_Agents implements INode {
3233
this.type = 'AgentExecutor'
3334
this.category = 'Agents'
3435
this.icon = 'function.svg'
35-
this.description = `An agent that uses Function Calling to pick the tool and args to call`
36+
this.description = `An agent that uses OpenAI Function Calling to pick the tool and args to call`
3637
this.baseClasses = [this.type, ...getBaseClasses(AgentExecutor)]
38+
this.badge = 'DEPRECATING'
3739
this.inputs = [
3840
{
3941
label: 'Allowed Tools',
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
import { flatten } from 'lodash'
2+
import { BaseMessage } from '@langchain/core/messages'
3+
import { ChainValues } from '@langchain/core/utils/types'
4+
import { RunnableSequence } from '@langchain/core/runnables'
5+
import { ChatOpenAI } from '@langchain/openai'
6+
import { ChatPromptTemplate, MessagesPlaceholder } from '@langchain/core/prompts'
7+
import { convertToOpenAITool } from '@langchain/core/utils/function_calling'
8+
import { formatToOpenAIToolMessages } from 'langchain/agents/format_scratchpad/openai_tools'
9+
import { OpenAIToolsAgentOutputParser, type ToolsAgentStep } from 'langchain/agents/openai/output_parser'
10+
import { getBaseClasses } from '../../../src/utils'
11+
import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams, IUsedTool } from '../../../src/Interface'
12+
import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler'
13+
import { AgentExecutor } from '../../../src/agents'
14+
import { Moderation, checkInputs } from '../../moderation/Moderation'
15+
import { formatResponse } from '../../outputparsers/OutputParserHelpers'
16+
17+
class OpenAIToolAgent_Agents implements INode {
18+
label: string
19+
name: string
20+
version: number
21+
description: string
22+
type: string
23+
icon: string
24+
category: string
25+
baseClasses: string[]
26+
inputs: INodeParams[]
27+
sessionId?: string
28+
badge?: string
29+
30+
constructor(fields?: { sessionId?: string }) {
31+
this.label = 'OpenAI Tool Agent'
32+
this.name = 'openAIToolAgent'
33+
this.version = 1.0
34+
this.type = 'AgentExecutor'
35+
this.category = 'Agents'
36+
this.icon = 'function.svg'
37+
this.description = `Agent that uses OpenAI Function Calling to pick the tools and args to call`
38+
this.baseClasses = [this.type, ...getBaseClasses(AgentExecutor)]
39+
this.badge = 'NEW'
40+
this.inputs = [
41+
{
42+
label: 'Tools',
43+
name: 'tools',
44+
type: 'Tool',
45+
list: true
46+
},
47+
{
48+
label: 'Memory',
49+
name: 'memory',
50+
type: 'BaseChatMemory'
51+
},
52+
{
53+
label: 'OpenAI/Azure Chat Model',
54+
name: 'model',
55+
type: 'BaseChatModel'
56+
},
57+
{
58+
label: 'System Message',
59+
name: 'systemMessage',
60+
type: 'string',
61+
rows: 4,
62+
optional: true,
63+
additionalParams: true
64+
},
65+
{
66+
label: 'Input Moderation',
67+
description: 'Detect text that could generate harmful output and prevent it from being sent to the language model',
68+
name: 'inputModeration',
69+
type: 'Moderation',
70+
optional: true,
71+
list: true
72+
}
73+
]
74+
this.sessionId = fields?.sessionId
75+
}
76+
77+
async init(nodeData: INodeData, input: string, options: ICommonObject): Promise<any> {
78+
return prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }, options.chatHistory)
79+
}
80+
81+
async run(nodeData: INodeData, input: string, options: ICommonObject): Promise<string | ICommonObject> {
82+
const memory = nodeData.inputs?.memory as FlowiseMemory
83+
const moderations = nodeData.inputs?.inputModeration as Moderation[]
84+
85+
if (moderations && moderations.length > 0) {
86+
try {
87+
// Use the output of the moderation chain as input for the OpenAI Function Agent
88+
input = await checkInputs(moderations, input)
89+
} catch (e) {
90+
await new Promise((resolve) => setTimeout(resolve, 500))
91+
//streamResponse(options.socketIO && options.socketIOClientId, e.message, options.socketIO, options.socketIOClientId)
92+
return formatResponse(e.message)
93+
}
94+
}
95+
96+
const executor = prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }, options.chatHistory)
97+
98+
const loggerHandler = new ConsoleCallbackHandler(options.logger)
99+
const callbacks = await additionalCallbacks(nodeData, options)
100+
101+
let res: ChainValues = {}
102+
let sourceDocuments: ICommonObject[] = []
103+
let usedTools: IUsedTool[] = []
104+
105+
if (options.socketIO && options.socketIOClientId) {
106+
const handler = new CustomChainHandler(options.socketIO, options.socketIOClientId)
107+
res = await executor.invoke({ input }, { callbacks: [loggerHandler, handler, ...callbacks] })
108+
if (res.sourceDocuments) {
109+
options.socketIO.to(options.socketIOClientId).emit('sourceDocuments', flatten(res.sourceDocuments))
110+
sourceDocuments = res.sourceDocuments
111+
}
112+
if (res.usedTools) {
113+
options.socketIO.to(options.socketIOClientId).emit('usedTools', res.usedTools)
114+
usedTools = res.usedTools
115+
}
116+
} else {
117+
res = await executor.invoke({ input }, { callbacks: [loggerHandler, ...callbacks] })
118+
if (res.sourceDocuments) {
119+
sourceDocuments = res.sourceDocuments
120+
}
121+
if (res.usedTools) {
122+
usedTools = res.usedTools
123+
}
124+
}
125+
126+
await memory.addChatMessages(
127+
[
128+
{
129+
text: input,
130+
type: 'userMessage'
131+
},
132+
{
133+
text: res?.output,
134+
type: 'apiMessage'
135+
}
136+
],
137+
this.sessionId
138+
)
139+
140+
let finalRes = res?.output
141+
142+
if (sourceDocuments.length || usedTools.length) {
143+
finalRes = { text: res?.output }
144+
if (sourceDocuments.length) {
145+
finalRes.sourceDocuments = flatten(sourceDocuments)
146+
}
147+
if (usedTools.length) {
148+
finalRes.usedTools = usedTools
149+
}
150+
return finalRes
151+
}
152+
153+
return finalRes
154+
}
155+
}
156+
157+
const prepareAgent = (
158+
nodeData: INodeData,
159+
flowObj: { sessionId?: string; chatId?: string; input?: string },
160+
chatHistory: IMessage[] = []
161+
) => {
162+
const model = nodeData.inputs?.model as ChatOpenAI
163+
const memory = nodeData.inputs?.memory as FlowiseMemory
164+
const systemMessage = nodeData.inputs?.systemMessage as string
165+
let tools = nodeData.inputs?.tools
166+
tools = flatten(tools)
167+
const memoryKey = memory.memoryKey ? memory.memoryKey : 'chat_history'
168+
const inputKey = memory.inputKey ? memory.inputKey : 'input'
169+
170+
const prompt = ChatPromptTemplate.fromMessages([
171+
['system', systemMessage ? systemMessage : `You are a helpful AI assistant.`],
172+
new MessagesPlaceholder(memoryKey),
173+
['human', `{${inputKey}}`],
174+
new MessagesPlaceholder('agent_scratchpad')
175+
])
176+
177+
const modelWithTools = model.bind({ tools: tools.map(convertToOpenAITool) })
178+
179+
const runnableAgent = RunnableSequence.from([
180+
{
181+
[inputKey]: (i: { input: string; steps: ToolsAgentStep[] }) => i.input,
182+
agent_scratchpad: (i: { input: string; steps: ToolsAgentStep[] }) => formatToOpenAIToolMessages(i.steps),
183+
[memoryKey]: async (_: { input: string; steps: ToolsAgentStep[] }) => {
184+
const messages = (await memory.getChatMessages(flowObj?.sessionId, true, chatHistory)) as BaseMessage[]
185+
return messages ?? []
186+
}
187+
},
188+
prompt,
189+
modelWithTools,
190+
new OpenAIToolsAgentOutputParser()
191+
])
192+
193+
const executor = AgentExecutor.fromAgentAndTools({
194+
agent: runnableAgent,
195+
tools,
196+
sessionId: flowObj?.sessionId,
197+
chatId: flowObj?.chatId,
198+
input: flowObj?.input,
199+
verbose: process.env.DEBUG === 'true' ? true : false
200+
})
201+
202+
return executor
203+
}
204+
205+
module.exports = { nodeClass: OpenAIToolAgent_Agents }
Lines changed: 9 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)