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,