prototype v0.2

free JSON format, dirty parsing, tool superclass, memory injection, new rate limiter, agent params

frdel committed Jun 26, 2024 at 22:33 UTC be11dbbab621a6c3a80096dfc48d594f143fd9eb
21 files changed +243 -144
agent.py
+33 -18
@@ -1,5 +1,4 @@
1 -import json
2 -import time, importlib, inspect
1 +import time, importlib, inspect, os, json
2 import traceback
3 from typing import Optional, Dict, TypedDict
4 from tools.helpers import extract_tools, rate_limiter, files, errors
@@ -9,6 +8,7 @@ from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
8 from langchain_core.messages import HumanMessage, SystemMessage
9 from langchain_core.language_models.chat_models import BaseChatModel
10 from langchain_core.embeddings import Embeddings
11 +from tools.helpers.rate_limiter import RateLimiter
12
13 # rate_limit = rate_limiter.rate_limiter(30,160000) #TODO! implement properly
14
@@ -20,32 +20,36 @@ class Agent:
20
21 def __init__(self,
22 agent_number: int,
23 - chat_llm:BaseChatModel,
23 + chat_model:BaseChatModel,
24 embeddings_model:Embeddings,
25 memory_subdir: str = "",
26 auto_memory_count: int = 3,
27 auto_memory_skip: int = 2,
28 rate_limit_seconds: int = 60,
29 + rate_limit_requests: int = 30,
30 rate_limit_input_tokens: int = 0,
31 rate_limit_output_tokens: int = 0,
31 - msgs_keep_max: int =25,
32 - msgs_keep_start: int =5,
33 - msgs_keep_end: int =10,
32 + msgs_keep_max: int = 25,
33 + msgs_keep_start: int = 5,
34 + msgs_keep_end: int = 10,
35 + max_tool_response_length: int = 3000,
36 **kwargs):
37
38 # agent config
39 self.agent_number = agent_number
38 - self.chat_model = chat_llm
40 + self.chat_model = chat_model
41 self.embeddings_model = embeddings_model
42 self.memory_subdir = memory_subdir
43 self.auto_memory_count = auto_memory_count
44 self.auto_memory_skip = auto_memory_skip
45 self.rate_limit_seconds = rate_limit_seconds
46 + self.rate_limit_requests = rate_limit_requests
47 self.rate_limit_input_tokens = rate_limit_input_tokens
48 self.rate_limit_output_tokens = rate_limit_output_tokens
49 self.msgs_keep_max = msgs_keep_max
50 self.msgs_keep_start = msgs_keep_start
51 self.msgs_keep_end = msgs_keep_end
52 + self.max_tool_response_length = max_tool_response_length
53
54 # non-config vars
55 self.agent_name = f"Agent {self.agent_number}"
@@ -57,9 +61,12 @@ class Agent:
61 self.last_message = ""
62 self.intervention_message = ""
63 self.intervention_status = False
60 -
64 + self.rate_limiter = RateLimiter(max_calls=rate_limit_requests,max_input_tokens=rate_limit_input_tokens,max_output_tokens=rate_limit_output_tokens,window_seconds=rate_limit_seconds)
65 self.data = {} # free data object all the tools can use
66
67 + os.chdir(files.get_abs_path("./work_dir")) #change CWD to work_dir
68 +
69 +
70 def message_loop(self, msg: str):
71 try:
72 printer = PrintStyle(italic=True, font_color="#b3ffd9", padding=False)
@@ -84,10 +91,11 @@ class Agent:
91
92 inputs = {"messages": self.history}
93 chain = prompt | self.chat_model
87 - formatted_inputs = prompt.format(messages=self.history)
88 -
89 - # rate_limit(len(formatted_inputs)/4) #wait for rate limiter - A helpful rule of thumb is that one token generally corresponds to ~4 characters of text for common English text. This translates to roughly ¾ of a word (so 100 tokens ~= 75 words).
94
95 + formatted_inputs = prompt.format(messages=self.history)
96 + tokens = int(len(formatted_inputs)/4)
97 + self.rate_limiter.limit_call_and_input(tokens)
98 +
99 # output that the agent is starting
100 PrintStyle(bold=True, font_color="green", padding=True, background_color="white").print(f"{self.agent_name}: Starting a message:")
101
@@ -101,7 +109,9 @@ class Agent:
109 if content:
110 printer.stream(content) # output the agent response stream
111 agent_response += content # concatenate stream into the response
104 -
112 +
113 + self.rate_limiter.set_output_tokens(int(len(agent_response)/4))
114 +
115 if not self.handle_intervention(agent_response):
116 if self.last_message == agent_response: #if assistant_response is the same as last message in history, let him know
117 self.append_message(agent_response) # Append the assistant's response to the history
@@ -156,9 +166,13 @@ class Agent:
166 if output_label:
167 PrintStyle(bold=True, font_color="orange", padding=True, background_color="white").print(f"{self.agent_name}: {output_label}:")
168 printer = PrintStyle(italic=True, font_color="orange", padding=False)
159 -
169 +
170 + formatted_inputs = prompt.format()
171 + tokens = int(len(formatted_inputs)/4)
172 + self.rate_limiter.limit_call_and_input(tokens)
173 +
174 for chunk in chain.stream({}):
161 - if self.handle_intervention(response): break # wait for intervention and handle it, if paused
175 + if self.handle_intervention(): break # wait for intervention and handle it, if paused
176
177 if isinstance(chunk, str): content = chunk
178 elif hasattr(chunk, "content"): content = str(chunk.content)
@@ -167,6 +181,8 @@ class Agent:
181 if printer: printer.stream(content)
182 response+=content
183
184 + self.rate_limiter.set_output_tokens(int(len(response)/4))
185 +
186 return response
187
188 def get_last_message(self):
@@ -205,7 +221,6 @@ class Agent:
221
222 return self.history
223
208 -
224 def handle_intervention(self, progress:str="") -> bool:
225 while self.paused: time.sleep(0.1) # wait if paused
226 if self.intervention_message and not self.intervention_status: # if there is an intervention message, but not yet processed
@@ -229,8 +244,8 @@ class Agent:
244
245 if self.handle_intervention(): return # wait if paused and handle intervention message if needed
246
232 - tool.before_execution()
233 - response = tool.execute()
247 + tool.before_execution(**tool_args)
248 + response = tool.execute(**tool_args)
249 tool.after_execution(response)
250 if response.break_loop: return response.message
251
@@ -267,5 +282,5 @@ class Agent:
282 "raw_memories": memories
283 }
284 cleanup_prompt = files.read_file("./prompts/msg.memory_cleanup.md").replace("{", "{{")
270 - clean_memories = self.send_adhoc_message(cleanup_prompt,json.dumps(input), output_label="Memory cleanup summary")
285 + clean_memories = self.send_adhoc_message(cleanup_prompt,json.dumps(input), output_label="Memory injection")
286 return clean_memories
\ No newline at end of file
example.env
+5 -1
@@ -1,4 +1,8 @@
1 API_KEY_OPENAI=
2 API_KEY_ANTHROPIC=
3 API_KEY_GROQ=
4 -API_KEY_PERPLEXITY=
\ No newline at end of file
4 +API_KEY_PERPLEXITY=
5 +
6 +
7 +TOKENIZERS_PARALLELISM=true
8 +PYDEVD_DISABLE_FILE_VALIDATION=1
\ No newline at end of file
main.py
+15 -3
@@ -32,9 +32,21 @@ def chat():
32 embedding_llm = models.get_embedding_hf()
33
34 # create the first agent
35 - agent0 = Agent(agent_number=0,
36 - chat_llm=chat_llm,
37 - embeddings_model=embedding_llm)
35 + agent0 = Agent( agent_number=0,
36 + chat_model=chat_llm,
37 + embeddings_model=embedding_llm,
38 + # memory_subdir = "",
39 + # auto_memory_count = 3,
40 + # auto_memory_skip = 2,
41 + # rate_limit_seconds = 60,
42 + rate_limit_requests = 30,
43 + rate_limit_input_tokens = 160000,
44 + rate_limit_output_tokens = 8000,
45 + # msgs_keep_max = 25,
46 + # msgs_keep_start = 5,
47 + # msgs_keep_end = 10,
48 + # max_tool_response_length = 3000,
49 + )
50
51 # start the conversation loop
52 while True:
prompts/agent.system.md
+48 -7
@@ -1,5 +1,7 @@
1 # Your role
2 -- You are autonomous JSON AI task solver
2 +- You are autonomous JSON AI task solving agent enhanced with knowledge and execution tools
3 +- You are given task by your superior and you solve it using your subordinates and tools
4 +- You never just talk about solutions, never inform user about intentions, you are the one to execute actions using your tools and get things done
5
6 # Communication
7 - Your response is a JSON containing the following fields:
@@ -11,14 +13,14 @@
13 - Each tool has specific arguments listed in Available tools section
14 - No text before or after the JSON object. End message there.
15
14 -## Response example that must be used every time
16 +## Response example
17 ~~~json
18 {
19 "thoughts": [
18 - "The user has requested...",
19 - "I need to follow these steps...",
20 - "I need to use the tool...",
21 - "Then I will..."
20 + "The user has requested extracting a zip file downloaded yesterday.",
21 + "Steps to solution are...",
22 + "I will process step by step...",
23 + "Analysis of step..."
24 ],
25 "tool_name": "name_of_tool",
26 "tool_args": {
@@ -26,4 +28,43 @@
28 "arg2": "val2"
29 }
30 }
29 -~~~
\ No newline at end of file
31 +~~~
32 +
33 +# Step by step instruction manual to problem solving
34 +- Do not follow for simple questions, only for tasks need solving.
35 +- Explain each step using your **thoughts** argument.
36 +
37 +0. Outline the plan by repeating these instructions.
38 +1. Check the memory output of your **knowledge_tool**. Maybe you have solved similar task before and already have helpful information.
39 +2. Check the online sources output of your **knowledge_tool**.
40 + - Look for straightforward solutions compatible with your available tools.
41 + - Always look for opensource python/nodejs/terminal tools and packages first.
42 +3. Break task into subtasks that can be solved independently.
43 +4. Solution / delegation
44 + - If your role is suitable for the curent subtask, use your tools to solve it.
45 + - If a different role would be more suitable for the subtask, use **call_subordinate** tool to delegate the subtask to subordinate agent and instruct him about his role.
46 +5. Completing the task
47 + - Consolidate all subtasks and explain the status.
48 + - Verify the result using your tools if possible (check created files etc.)
49 + - Do not accept failure, search for error solution and try again with fixed input or different ways.
50 + - If there is helpful information discovered during the solution, save it into your memory using tool **memorize** for later.
51 + - Report back to your user using **response** tool, describe the result and provide all necessary information. Do not just output your response, you must use the tool for that.
52 +
53 +# General operation manual
54 +- Use your reasoning and process each problem in a step-by-step manner using your **thoughts** argument.
55 +- Always check your previous messages and prevent repetition. Always move towards solution.
56 +- Never assume success. You always need to do a check with a positive result.
57 +- Avoid solutions that require credentials, user interaction, GUI usage etc. All has to be done using code and terminal.
58 +- When asked about your memory, it always refers to **knowledge_tool** and **memorize** tool, never your internal knowledge.
59 +
60 +# Cooperation and delegation
61 +- Agents can have roles like scientist, coder, writer etc.
62 +- If your user has assigned you a role in the first message, you have to follow these instructions and play your role.
63 +- Your role will not be suitable for some subtasks, in that case you can delegate the subtask to subordinate agent and instruct him about his role using **call_subordinate** tool.
64 +- Always be very descriptive when explaining your subordinate agent's role and task. Include all necessary details as well as higher leven overview about the goal.
65 +- Communicate back and forth with your subordinate and superior using **call_subordinate** and **response** tools.
66 +- Communication is the key to succesfull solution.
67 +
68 +# Tips and tricks
69 +- Focus on python/nodejs/linux libraries when searching for solutions. You can use them with your tools and make solutions easy.
70 +- Sometimes you don't need tools, some things can be determined.
prompts/agent.tools.md
+21
@@ -20,6 +20,27 @@ Always verify memory by online.
20 }
21 ~~~
22
23 +### call_subordinate:
24 +Use subordinate agents to solve subtasks.
25 +Use "message" argument to send message. Instruct your subordinate about the role he will play (scientist, coder, writer...) and his task in detail.
26 +Use "reset" argument with "true" to start with new subordinate or "false" to continue with existing. For brand new tasks use "true", for followup conversation use "false".
27 +Explain to your subordinate what is the higher level goal and what is his part.
28 +Give him detailed instructions as well as good overview to understand what to do.
29 +**Example usage**:
30 +~~~json
31 +{
32 + "thoughts": [
33 + "The result seems to be ok but...",
34 + "I will ask my subordinate to fix...",
35 + ],
36 + "tool_name": "call_subordinate",
37 + "tool_args": {
38 + "message": "Well done, now edit...",
39 + "reset": "false"
40 + }
41 +}
42 +~~~
43 +
44 ### knowledge_tool:
45 Provide "question" argument and get both online and memory response.
46 This tool is very powerful and can answer very specific questions directly.
prompts/fw.msg_timeout.md
+15 -2
@@ -1,3 +1,16 @@
1 -User is not responding to your message.
1 +# User is not responding to your message.
2 If you have a task in progress, continue on your own.
3 -I you don't have a task, use the <task_done$> message.
\ No newline at end of file
3 +I you don't have a task, use the **task_done** tool with **text** argument.
4 +
5 +# Example
6 +~~~json
7 +{
8 + "thoughts": [
9 + "There's no more work for me, I will ask for another task",
10 + ],
11 + "tool_name": "task_done",
12 + "tool_args": {
13 + "text": "I have no more work, please tell me if you need anything.",
14 + }
15 +}
16 +~~~
\ No newline at end of file
prompts/msg.memory_cleanup.md
+16 -3
@@ -8,6 +8,19 @@
8
9 # Expected output format
10 - Return filtered list of bullet points of key elements in the memories
11 -- Include every important detail relevant to conversation
12 -- Include code snippets if relevant
13 -- Omit any unrelevant information
\ No newline at end of file
11 +- Do not include memory contents, only their summaries to inform the user that he has memories of the topic.
12 +- If there are relevant memories, instruct user to use "knowledge_tool" to get more details.
13 +
14 +# Example output 1 (relevant memories):
15 +~~~md
16 +1. Guide how to create a web app including code.
17 +2. Javascript snippets from snake game development.
18 +3. SVG image generation for game sprites with examples.
19 +
20 +Check your knowledge_tool for more details.
21 +~~~
22 +
23 +# Example output 2 (no relevant memories):
24 +~~~text
25 +No relevant memories on the topic found.
26 +~~~
\ No newline at end of file
test.py deleted
-26
@@ -1,26 +0,0 @@
1 -def extract_json_string(content):
2 - start = content.find('{')
3 - if start == -1:
4 - print("No JSON content found.")
5 - return ""
6 -
7 - # Find the first '{'
8 - end = content.rfind('}')
9 - if end == -1:
10 - # If there's no closing '}', return from start to the end
11 - return content[start:]
12 - else:
13 - # If there's a closing '}', return the substring from start to end
14 - return content[start:end+1]
15 -
16 -# Test cases
17 -test_cases = [
18 - 'Some text before {"key1": "value1", "key2": 123, "key3": true, "key4": null} some text after',
19 - '{"key1": "value1", "key2": 123, "key3": true, "key4": null', # Incomplete JSON
20 - '{"nested": {"key": "value"}, "list": [1, 2, 3], "bool": true}',
21 - 'text without json',
22 -]
23 -
24 -# Run the test cases
25 -results = [extract_json_string(tc) for tc in test_cases]
26 -print(results)
tools/call_subordinate.py renamed
+6 -4
@@ -5,12 +5,14 @@ from tools.helpers.print_style import PrintStyle
5
6 class Delegation(Tool):
7
8 - def execute(self, **kwargs):
8 + def execute(self, message="", reset="", **kwargs):
9 # create subordinate agent using the data object on this agent and set superior agent to his data object
10 - if self.agent.get_data("subordinate") is None or self.args["reset"].lower().strip() == "true":
10 + if self.agent.get_data("subordinate") is None or str(reset).lower().strip() == "true":
11 # subordinate = Agent(system_prompt=self.agent.system_prompt, tools_prompt=self.agent.tools_prompt, number=self.agent.number+1)
12 - subordinate = Agent(**self.agent.__dict__, agent_number=self.agent.agent_number+1)
12 + config = self.agent.__dict__.copy()
13 + config["agent_number"] = self.agent.agent_number+1
14 + subordinate = Agent(**config)
15 subordinate.set_data("superior", self.agent)
16 self.agent.set_data("subordinate", subordinate)
17 # run subordinate agent message loop
16 - return self.agent.get_data("subordinate").message_loop(self.args["task"])
\ No newline at end of file
18 + return Response( message=self.agent.get_data("subordinate").message_loop(message), break_loop=False)
\ No newline at end of file
tools/code_execution_tool.py
+3 -3
@@ -8,9 +8,10 @@ from tools.helpers.print_style import PrintStyle
8
9 class CodeExecution(Tool):
10
11 - def execute(self):
11 + def execute(self,**kwargs):
12
13 - os.chdir(files.get_abs_path("./work_dir")) #change CWD to work_dir
13 + # os.chdir(files.get_abs_path("./work_dir")) #change CWD to work_dir
14 +
15 runtime = self.args["runtime"].lower().strip()
16 if runtime == "python":
17 response = self.execute_python_code(self.args["code"])
@@ -21,7 +22,6 @@ class CodeExecution(Tool):
22 else:
23 response = files.read_file("./prompts/fw.code_runtime_wrong.md", runtime=runtime)
24
24 - response = messages.truncate_text(response.strip(), 2000) # TODO parameterize
25 if not response: response = files.read_file("./prompts/fw.code_no_output.md")
26 return Response(message=response, break_loop=False)
27
tools/helpers/dirty_json.py
+4 -1
@@ -84,7 +84,7 @@ class DirtyJson:
84 return self._parse_object()
85 elif self.current_char == '[':
86 return self._parse_array()
87 - elif self.current_char in ['"', "'"]:
87 + elif self.current_char in ['"', "'", "`"]:
88 if self._peek(2) == self.current_char * 2: # type: ignore
89 return self._parse_multiline_string()
90 return self._parse_string()
@@ -122,6 +122,7 @@ class DirtyJson:
122 self.stack.pop()
123 return
124 if self.current_char is None:
125 + self.stack.pop()
126 return # End of input reached while parsing object
127
128 key = self._parse_key()
@@ -144,6 +145,7 @@ class DirtyJson:
145 continue
146 elif self.current_char != '}':
147 if self.current_char is None:
148 + self.stack.pop()
149 return # End of input reached after value
150 # Allow missing comma between key-value pairs
151 continue
@@ -262,6 +264,7 @@ class DirtyJson:
264 while self.current_char is not None and self.current_char not in [':', ',', '}', ']']:
265 result += self.current_char
266 self._advance()
267 + self._advance()
268 return result.strip()
269
270 def _peek(self, n):
tools/helpers/errors.py
+3 -1
@@ -1,6 +1,8 @@
1 +import re
2 +import traceback
3
4 def format_error(e: Exception, max_entries=2):
3 - traceback_text = str(e.with_traceback(None))
5 + traceback_text = traceback.format_exc()
6 # Split the traceback into lines
7 lines = traceback_text.split('\n')
8
tools/helpers/rate_limiter.py
+36 -6
@@ -2,12 +2,13 @@ import time
2 from collections import deque
3 from dataclasses import dataclass
4 from typing import List, Tuple
5 +from .print_style import PrintStyle
6
7 @dataclass
8 class CallRecord:
9 timestamp: float
10 input_tokens: int
10 - output_tokens: int
11 + output_tokens: int = 0 # Default to 0, will be set separately
12
13 class RateLimiter:
14 def __init__(self, max_calls: int, max_input_tokens: int, max_output_tokens: int, window_seconds: int = 60):
@@ -27,22 +28,51 @@ class RateLimiter:
28 output_tokens = sum(record.output_tokens for record in self.call_records)
29 return calls, input_tokens, output_tokens
30
30 - def _wait_if_needed(self, current_time: float):
31 + def _wait_if_needed(self, current_time: float, new_input_tokens: int):
32 while True:
33 self._clean_old_records(current_time)
34 calls, input_tokens, output_tokens = self._get_counts()
35
35 - if calls < self.max_calls and input_tokens < self.max_input_tokens and output_tokens < self.max_output_tokens:
36 + wait_reasons = []
37 + if self.max_calls > 0 and calls >= self.max_calls:
38 + wait_reasons.append("max calls")
39 + if self.max_input_tokens > 0 and input_tokens + new_input_tokens > self.max_input_tokens:
40 + wait_reasons.append("max input tokens")
41 + if self.max_output_tokens > 0 and output_tokens >= self.max_output_tokens:
42 + wait_reasons.append("max output tokens")
43 +
44 + if not wait_reasons:
45 break
46
47 oldest_record = self.call_records[0]
48 wait_time = oldest_record.timestamp + self.window_seconds - current_time
49 if wait_time > 0:
50 + PrintStyle(font_color="yellow", padding=True).print(f"Rate limit exceeded. Waiting for {wait_time:.2f} seconds due to: {', '.join(wait_reasons)}")
51 time.sleep(wait_time)
52 current_time = time.time()
53
44 - def limit(self, input_token_count: int, output_token_count: int):
54 + def limit_call_and_input(self, input_token_count: int) -> CallRecord:
55 current_time = time.time()
46 - self._wait_if_needed(current_time)
47 - self.call_records.append(CallRecord(current_time, input_token_count, output_token_count))
56 + self._wait_if_needed(current_time, input_token_count)
57 + new_record = CallRecord(current_time, input_token_count)
58 + self.call_records.append(new_record)
59 + return new_record
60 +
61 + def set_output_tokens(self, output_token_count: int):
62 + if self.call_records:
63 + self.call_records[-1].output_tokens += output_token_count
64 + return self
65 +
66 +# Example usage
67 +rate_limiter = RateLimiter(max_calls=5, max_input_tokens=1000, max_output_tokens=2000)
68
69 +def rate_limited_function(input_token_count: int, output_token_count: int):
70 + # First, limit the call and input tokens (this may wait)
71 + rate_limiter.limit_call_and_input(input_token_count)
72 +
73 + # Your function logic here
74 + print(f"Function called with {input_token_count} input tokens")
75 +
76 + # After processing, set the output tokens (this doesn't wait)
77 + rate_limiter.set_output_tokens(output_token_count)
78 + print(f"Function completed with {output_token_count} output tokens")
tools/helpers/tool.py
+7 -6
@@ -2,7 +2,7 @@ from abc import abstractmethod
2 from typing import TypedDict
3 from agent import Agent
4 from tools.helpers.print_style import PrintStyle
5 -from tools.helpers import files
5 +from tools.helpers import files, messages
6
7 class Response:
8 def __init__(self, message: str, break_loop: bool) -> None:
@@ -18,19 +18,20 @@ class Tool:
18 self.message = message
19
20 @abstractmethod
21 - def execute(self) -> Response:
21 + def execute(self,**kwargs) -> Response:
22 pass
23
24 - def before_execution(self):
24 + def before_execution(self, **kwargs):
25 PrintStyle(font_color="#1B4F72", padding=True, background_color="white", bold=True).print(f"{self.agent.agent_name}: Using tool '{self.name}':")
26 if self.args and isinstance(self.args, dict):
27 for key, value in self.args.items():
28 PrintStyle(font_color="#85C1E9", bold=True).stream(self.nice_key(key)+": ")
29 - PrintStyle(font_color="#85C1E9", padding="\n" in value).stream(value)
29 + PrintStyle(font_color="#85C1E9", padding=isinstance(value,str) and "\n" in value).stream(value)
30 PrintStyle().print()
31
32 - def after_execution(self, response: Response):
33 - msg_response = files.read_file("./prompts/fw.tool_response.md", tool_name=self.name, tool_response=response.message)
32 + def after_execution(self, response: Response, **kwargs):
33 + text = messages.truncate_text(response.message.strip(), self.agent.max_tool_response_length)
34 + msg_response = files.read_file("./prompts/fw.tool_response.md", tool_name=self.name, tool_response=text)
35 self.agent.append_message(msg_response, human=True)
36 PrintStyle(font_color="#1B4F72", background_color="white", padding=True, bold=True).print(f"{self.agent.agent_name}: Response from tool '{self.name}':")
37 PrintStyle(font_color="#85C1E9").print(response.message)
tools/knowledge_tool.py
+3 -3
@@ -9,11 +9,11 @@ from tools.helpers.tool import Tool, Response
9 from tools.helpers import files
10
11 class Knowledge(Tool):
12 - def execute(self):
12 + def execute(self, question="", **kwargs):
13 with concurrent.futures.ThreadPoolExecutor() as executor:
14 # Schedule the two functions to be run in parallel
15 - future_online = executor.submit(online_knowledge_tool.process_question, self.args["question"])
16 - future_memory = executor.submit(memory_tool.process_query, self.agent, self.args["question"])
15 + future_online = executor.submit(online_knowledge_tool.process_question, question)
16 + future_memory = executor.submit(memory_tool.process_query, self.agent, question)
17
18 # Wait for both functions to complete
19 online_result = future_online.result()
tools/memorize.py
+1 -1
@@ -4,7 +4,7 @@ from tools.helpers.tool import Tool, Response
4 from tools import memory_tool
5
6 class Memorize(Tool):
7 - def execute(self):
7 + def execute(self,**kwargs):
8
9 memory_tool.process_query(self.agent, str(self.args), "save")
10
tools/memory_tool.py
+1 -1
@@ -8,7 +8,7 @@ from tools.helpers.print_style import PrintStyle
8 db: VectorDB | None = None
9
10 class Memory(Tool):
11 - def execute(self):
11 + def execute(self,**kwargs):
12 #TODO separate param for memory tool result count
13 result = process_query(self.agent, self.args["memory"],self.args["action"], result_count=self.agent.auto_memory_count)
14 return Response(message="\n\n".join(result), break_loop=False)
tools/online_knowledge_tool.py
+1 -1
@@ -3,7 +3,7 @@ from tools.helpers import perplexity_search
3 from tools.helpers.tool import Tool, Response
4
5 class OnlineKnowledge(Tool):
6 - def execute(self):
6 + def execute(self,**kwargs):
7 return Response(
8 message=process_question(self.args["question"]),
9 break_loop=False,
tools/response.py
+4 -56
@@ -9,64 +9,12 @@ from tools.helpers.print_style import PrintStyle
9
10 class ResponseTool(Tool):
11
12 - def execute(self):
12 + def execute(self,**kwargs):
13 # superior = self.agent.get_data("superior")
14 # if superior:
15 + self.agent.set_data("timeout", 60)
16 return Response(message=self.args["text"], break_loop=True)
17 # else:
18
18 - def after_execution(self, response):
19 - pass # do add anything to the history or output
20 -
21 -
22 -# def execute(agent:Agent, message: str, _tools, _tool_index, timeout=15, **kwargs):
23 -
24 -# # for models that like to use multiple tools in one response, we do a little trick to help the flow
25 -# # if there are tools producing output before this message or any other tools after this message,
26 -# # this message will be sent as information only and will not stop the loop
27 -# # if this is the last tool in the iteration and no outputing tools we used before, we stop the loop
28 -# # and wait for user input
29 -
30 -
31 -# # tools called before this one that produce output
32 -# outputing_tools_before = filter_tools(
33 -# filters=[
34 -# {"name":"memory_tool","action":"load"},
35 -# {"name":"online_knowledge_tool"},
36 -# {"name":"delegation"},
37 -# ],
38 -# tools=_tools[:_tool_index]
39 -# )
40 -
41 -# # tools called after this one
42 -# other_tools_after = any(tool["name"] != kwargs["_name"] for tool in _tools[_tool_index:]) #are there other tools than messages used after this one?
43 -
44 -# agent.set_data("timeout", timeout) # set the timeout for response
45 -
46 -# #if there are other tools used, message is only for information, it does not stop the loop
47 -# if outputing_tools_before or other_tools_after:
48 -# return non_blocking_message(agent, message, timeout=timeout, **kwargs)
49 -# else: #if there are only messages used in this iteration, collect them into the loop result
50 -# return blocking_message(agent, message, timeout=timeout, **kwargs)
51 -
52 -# def blocking_message(agent:Agent, message: str, timeout=15, **kwargs):
53 -# agent.add_result(message)
54 -# return files.read_file("./prompts/fw.msg_sent.md")
55 -
56 -# def non_blocking_message(agent:Agent, message: str, timeout=15, **kwargs):
57 -# if agent.get_data("superior"): # add to superior messages if it is an agent
58 -# msg_for_user = files.read_file("./prompts/fw.msg_from_subordinate.md",name=agent.name,message=message)
59 -# agent.get_data("superior").append_message(msg_for_user, human=True)
60 -
61 -# # output to console
62 -# PrintStyle(font_color="white",background_color="#1D8348", bold=True, padding=True).print(f"{agent.name}: reponse:")
63 -# PrintStyle(font_color="white").print(f"{message}")
64 -
65 -# return files.read_file("./prompts/fw.msg_info_sent.md") #return message o
66 -
67 -# def filter_tools(filters, tools):
68 -# filtered_data = []
69 -# for data_item in tools:
70 -# if any(all(data_item.get(key) == value for key, value in filter_item.items()) for filter_item in filters):
71 -# filtered_data.append(data_item)
72 -# return filtered_data
\ No newline at end of file
19 + def after_execution(self, response, **kwargs):
20 + pass # do add anything to the history or output
\ No newline at end of file
tools/task_done.py new
+20
@@ -0,0 +1,20 @@
1 +from agent import Agent
2 +from tools.helpers import files
3 +from tools.helpers.print_style import PrintStyle
4 +
5 +from agent import Agent
6 +from tools.helpers.tool import Tool, Response
7 +from tools.helpers import files
8 +from tools.helpers.print_style import PrintStyle
9 +
10 +class TaskDone(Tool):
11 +
12 + def execute(self,**kwargs):
13 + # superior = self.agent.get_data("superior")
14 + # if superior:
15 + self.agent.set_data("timeout", 0)
16 + return Response(message=self.args["text"], break_loop=True)
17 + # else:
18 +
19 + def after_execution(self, response, **kwargs):
20 + pass # do add anything to the history or output
\ No newline at end of file
tools/unknown.py
+1 -1
@@ -2,7 +2,7 @@ from tools.helpers.tool import Tool, Response
2 from tools.helpers import files
3
4 class Unknown(Tool):
5 - def execute(self):
5 + def execute(self, **kwargs):
6 return Response(
7 message=files.read_file("prompts/fw.tool_not_found.md",
8 tool_name=self.name,