|
1 |
| -import { |
2 |
| - HandlerContext, |
3 |
| - StartEvent, |
4 |
| - StopEvent, |
5 |
| - Workflow, |
6 |
| - WorkflowEvent, |
7 |
| -} from "@llamaindex/workflow"; |
8 |
| -import { |
9 |
| - BaseToolWithCall, |
10 |
| - ChatMemoryBuffer, |
11 |
| - ChatMessage, |
12 |
| - ChatResponseChunk, |
13 |
| - Settings, |
14 |
| - ToolCall, |
15 |
| - ToolCallLLM, |
16 |
| -} from "llamaindex"; |
17 |
| -import { callTools, chatWithTools } from "./tools"; |
18 |
| -import { AgentInput, AgentRunEvent } from "./type"; |
19 |
| - |
20 |
| -class InputEvent extends WorkflowEvent<{ |
21 |
| - input: ChatMessage[]; |
22 |
| -}> {} |
23 |
| - |
24 |
| -class ToolCallEvent extends WorkflowEvent<{ |
25 |
| - toolCalls: ToolCall[]; |
26 |
| -}> {} |
27 |
| - |
28 |
| -type FunctionCallingAgentContextData = { |
29 |
| - streaming: boolean; |
| 1 | +import { ChatMessage } from "llamaindex"; |
| 2 | +import { getTool } from "../engine/tools"; |
| 3 | +import { FunctionCallingAgent } from "./single-agent"; |
| 4 | +import { getQueryEngineTools } from "./tools"; |
| 5 | + |
| 6 | +export const createResearcher = async (chatHistory: ChatMessage[]) => { |
| 7 | + const queryEngineTools = await getQueryEngineTools(); |
| 8 | + const tools = [ |
| 9 | + await getTool("wikipedia_tool"), |
| 10 | + await getTool("duckduckgo_search"), |
| 11 | + await getTool("image_generator"), |
| 12 | + ...(queryEngineTools ? queryEngineTools : []), |
| 13 | + ].filter((tool) => tool !== undefined); |
| 14 | + |
| 15 | + return new FunctionCallingAgent({ |
| 16 | + name: "researcher", |
| 17 | + tools: tools, |
| 18 | + systemPrompt: `You are a researcher agent. You are given a research task. |
| 19 | + |
| 20 | +If the conversation already includes the information and there is no new request for additional information from the user, you should return the appropriate content to the writer. |
| 21 | +Otherwise, you must use tools to retrieve information or images needed for the task. |
| 22 | +
|
| 23 | +It's normal for the task to include some ambiguity. You must always think carefully about the context of the user's request to understand what are the main content needs to be retrieved. |
| 24 | +Example: |
| 25 | + Request: "Create a blog post about the history of the internet, write in English and publish in PDF format." |
| 26 | + ->Though: The main content is "history of the internet", while "write in English and publish in PDF format" is a requirement for other agents. |
| 27 | + Your task: Look for information in English about the history of the Internet. |
| 28 | + This is not your task: Create a blog post or look for how to create a PDF. |
| 29 | +
|
| 30 | + Next request: "Publish the blog post in HTML format." |
| 31 | + ->Though: User just asking for a format change, the previous content is still valid. |
| 32 | + Your task: Return the previous content of the post to the writer. No need to do any research. |
| 33 | + This is not your task: Look for how to create an HTML file. |
| 34 | +
|
| 35 | +If you use the tools but don't find any related information, please return "I didn't find any new information for {the topic}." along with the content you found. Don't try to make up information yourself. |
| 36 | +If the request doesn't need any new information because it was in the conversation history, please return "The task doesn't need any new information. Please reuse the existing content in the conversation history. |
| 37 | +`, |
| 38 | + chatHistory, |
| 39 | + }); |
30 | 40 | };
|
31 | 41 |
|
32 |
| -export type FunctionCallingAgentInput = AgentInput & { |
33 |
| - displayName: string; |
| 42 | +export const createWriter = (chatHistory: ChatMessage[]) => { |
| 43 | + return new FunctionCallingAgent({ |
| 44 | + name: "writer", |
| 45 | + systemPrompt: `You are an expert in writing blog posts. |
| 46 | +You are given the task of writing a blog post based on research content provided by the researcher agent. Do not invent any information yourself. |
| 47 | +It's important to read the entire conversation history to write the blog post accurately. |
| 48 | +If you receive a review from the reviewer, update the post according to the feedback and return the new post content. |
| 49 | +If the content is not valid (e.g., broken link, broken image, etc.), do not use it. |
| 50 | +It's normal for the task to include some ambiguity, so you must define the user's initial request to write the post correctly. |
| 51 | +If you update the post based on the reviewer's feedback, first explain what changes you made to the post, then provide the new post content. Do not include the reviewer's comments. |
| 52 | +Example: |
| 53 | + Task: "Here is the information I found about the history of the internet: |
| 54 | + Create a blog post about the history of the internet, write in English, and publish in PDF format." |
| 55 | + -> Your task: Use the research content {...} to write a blog post in English. |
| 56 | + -> This is not your task: Create a PDF |
| 57 | + Please note that a localhost link is acceptable, but dummy links like "example.com" or "your-website.com" are not valid.`, |
| 58 | + chatHistory, |
| 59 | + }); |
34 | 60 | };
|
35 | 61 |
|
36 |
| -export class FunctionCallingAgent extends Workflow< |
37 |
| - FunctionCallingAgentContextData, |
38 |
| - FunctionCallingAgentInput, |
39 |
| - string | AsyncGenerator<boolean | ChatResponseChunk<object>> |
40 |
| -> { |
41 |
| - name: string; |
42 |
| - llm: ToolCallLLM; |
43 |
| - memory: ChatMemoryBuffer; |
44 |
| - tools: BaseToolWithCall[]; |
45 |
| - systemPrompt?: string; |
46 |
| - writeEvents: boolean; |
47 |
| - role?: string; |
48 |
| - |
49 |
| - constructor(options: { |
50 |
| - name: string; |
51 |
| - llm?: ToolCallLLM; |
52 |
| - chatHistory?: ChatMessage[]; |
53 |
| - tools?: BaseToolWithCall[]; |
54 |
| - systemPrompt?: string; |
55 |
| - writeEvents?: boolean; |
56 |
| - role?: string; |
57 |
| - verbose?: boolean; |
58 |
| - timeout?: number; |
59 |
| - }) { |
60 |
| - super({ |
61 |
| - verbose: options?.verbose ?? false, |
62 |
| - timeout: options?.timeout ?? 360, |
63 |
| - }); |
64 |
| - this.name = options?.name; |
65 |
| - this.llm = options.llm ?? (Settings.llm as ToolCallLLM); |
66 |
| - if (!(this.llm instanceof ToolCallLLM)) { |
67 |
| - throw new Error("LLM is not a ToolCallLLM"); |
68 |
| - } |
69 |
| - this.memory = new ChatMemoryBuffer({ |
70 |
| - llm: this.llm, |
71 |
| - chatHistory: options.chatHistory, |
72 |
| - }); |
73 |
| - this.tools = options?.tools ?? []; |
74 |
| - this.systemPrompt = options.systemPrompt; |
75 |
| - this.writeEvents = options?.writeEvents ?? true; |
76 |
| - this.role = options?.role; |
77 |
| - |
78 |
| - // add steps |
79 |
| - this.addStep( |
80 |
| - { |
81 |
| - inputs: [StartEvent<AgentInput>], |
82 |
| - outputs: [InputEvent], |
83 |
| - }, |
84 |
| - this.prepareChatHistory.bind(this), |
85 |
| - ); |
86 |
| - this.addStep( |
87 |
| - { |
88 |
| - inputs: [InputEvent], |
89 |
| - outputs: [ToolCallEvent, StopEvent], |
90 |
| - }, |
91 |
| - this.handleLLMInput.bind(this), |
92 |
| - ); |
93 |
| - this.addStep( |
94 |
| - { |
95 |
| - inputs: [ToolCallEvent], |
96 |
| - outputs: [InputEvent], |
97 |
| - }, |
98 |
| - this.handleToolCalls.bind(this), |
99 |
| - ); |
100 |
| - } |
101 |
| - |
102 |
| - private get chatHistory() { |
103 |
| - return this.memory.getMessages(); |
104 |
| - } |
105 |
| - |
106 |
| - private async prepareChatHistory( |
107 |
| - ctx: HandlerContext<FunctionCallingAgentContextData>, |
108 |
| - ev: StartEvent<AgentInput>, |
109 |
| - ): Promise<InputEvent> { |
110 |
| - const { message, streaming } = ev.data; |
111 |
| - ctx.data.streaming = streaming ?? false; |
112 |
| - this.writeEvent(`Start to work on: ${message}`, ctx); |
113 |
| - if (this.systemPrompt) { |
114 |
| - this.memory.put({ role: "system", content: this.systemPrompt }); |
115 |
| - } |
116 |
| - this.memory.put({ role: "user", content: message }); |
117 |
| - return new InputEvent({ input: this.chatHistory }); |
118 |
| - } |
119 |
| - |
120 |
| - private async handleLLMInput( |
121 |
| - ctx: HandlerContext<FunctionCallingAgentContextData>, |
122 |
| - ev: InputEvent, |
123 |
| - ): Promise<StopEvent<string | AsyncGenerator> | ToolCallEvent> { |
124 |
| - const toolCallResponse = await chatWithTools( |
125 |
| - this.llm, |
126 |
| - this.tools, |
127 |
| - this.chatHistory, |
128 |
| - ); |
129 |
| - if (toolCallResponse.toolCallMessage) { |
130 |
| - this.memory.put(toolCallResponse.toolCallMessage); |
131 |
| - } |
132 |
| - |
133 |
| - if (toolCallResponse.hasToolCall()) { |
134 |
| - return new ToolCallEvent({ toolCalls: toolCallResponse.toolCalls }); |
135 |
| - } |
136 |
| - |
137 |
| - if (ctx.data.streaming) { |
138 |
| - if (!toolCallResponse.responseGenerator) { |
139 |
| - throw new Error("No streaming response"); |
140 |
| - } |
141 |
| - return new StopEvent(toolCallResponse.responseGenerator); |
142 |
| - } |
143 |
| - |
144 |
| - const fullResponse = await toolCallResponse.asFullResponse(); |
145 |
| - this.memory.put(fullResponse); |
146 |
| - return new StopEvent(fullResponse.content.toString()); |
147 |
| - } |
148 |
| - |
149 |
| - private async handleToolCalls( |
150 |
| - ctx: HandlerContext<FunctionCallingAgentContextData>, |
151 |
| - ev: ToolCallEvent, |
152 |
| - ): Promise<InputEvent> { |
153 |
| - const { toolCalls } = ev.data; |
154 |
| - |
155 |
| - const toolMsgs = await callTools({ |
156 |
| - tools: this.tools, |
157 |
| - toolCalls, |
158 |
| - ctx, |
159 |
| - agentName: this.name, |
160 |
| - }); |
161 |
| - |
162 |
| - for (const msg of toolMsgs) { |
163 |
| - this.memory.put(msg); |
164 |
| - } |
165 |
| - |
166 |
| - return new InputEvent({ input: this.memory.getMessages() }); |
167 |
| - } |
| 62 | +export const createReviewer = (chatHistory: ChatMessage[]) => { |
| 63 | + return new FunctionCallingAgent({ |
| 64 | + name: "reviewer", |
| 65 | + systemPrompt: `You are an expert in reviewing blog posts. |
| 66 | +You are given a task to review a blog post. As a reviewer, it's important that your review aligns with the user's request. Please focus on the user's request when reviewing the post. |
| 67 | +Review the post for logical inconsistencies, ask critical questions, and provide suggestions for improvement. |
| 68 | +Furthermore, proofread the post for grammar and spelling errors. |
| 69 | +Only if the post is good enough for publishing should you return 'The post is good.' In all other cases, return your review. |
| 70 | +It's normal for the task to include some ambiguity, so you must define the user's initial request to review the post correctly. |
| 71 | +Please note that a localhost link is acceptable, but dummy links like "example.com" or "your-website.com" are not valid. |
| 72 | +Example: |
| 73 | + Task: "Create a blog post about the history of the internet, write in English and publish in PDF format." |
| 74 | + -> Your task: Review whether the main content of the post is about the history of the internet and if it is written in English. |
| 75 | + -> This is not your task: Create blog post, create PDF, write in English.`, |
| 76 | + chatHistory, |
| 77 | + }); |
| 78 | +}; |
168 | 79 |
|
169 |
| - private writeEvent( |
170 |
| - msg: string, |
171 |
| - ctx: HandlerContext<FunctionCallingAgentContextData>, |
172 |
| - ) { |
173 |
| - if (!this.writeEvents) return; |
174 |
| - ctx.sendEvent(new AgentRunEvent({ agent: this.name, text: msg })); |
| 80 | +export const createPublisher = async (chatHistory: ChatMessage[]) => { |
| 81 | + const tool = await getTool("document_generator"); |
| 82 | + let systemPrompt = `You are an expert in publishing blog posts. You are given a task to publish a blog post. |
| 83 | +If the writer says that there was an error, you should reply with the error and not publish the post.`; |
| 84 | + if (tool) { |
| 85 | + systemPrompt = `${systemPrompt}. |
| 86 | +If the user requests to generate a file, use the document_generator tool to generate the file and reply with the link to the file. |
| 87 | +Otherwise, simply return the content of the post.`; |
175 | 88 | }
|
176 |
| -} |
| 89 | + return new FunctionCallingAgent({ |
| 90 | + name: "publisher", |
| 91 | + tools: tool ? [tool] : [], |
| 92 | + systemPrompt: systemPrompt, |
| 93 | + chatHistory, |
| 94 | + }); |
| 95 | +}; |
0 commit comments