This commit is contained in:
marko-kraemer 2024-10-27 22:04:42 +01:00
parent cad8edd402
commit 46f60e8b57
13 changed files with 219 additions and 7 deletions

View File

@ -1,10 +1,13 @@
import asyncio
import json
from agentpress.thread_manager import ThreadManager
from tools.files_tool import FilesTool
from state_manager import StateManager
async def run_agent(
thread_manager: ThreadManager,
thread_id: int,
state_manager: StateManager,
max_iterations: int = 10
):
@ -28,9 +31,22 @@ async def run_agent(
iteration += 1
await pre_iteration()
system_message = {"role": "system", "content": "You are a helpful assistant that can create, read, update, and delete files."}
# Get entire state store
state = await state_manager.export_store()
state_info = f"Current state store:\n{json.dumps(state, indent=2)}"
system_message = {
"role": "system",
"content": f"You are a helpful assistant that can create, read, update, and delete files.\n\n{state_info}"
}
model_name = "gpt-4o"
# Include entire state in additional message
# additional_message = {
# "role": "user",
# "content": state_info
# }
response = await thread_manager.run_thread(
thread_id=thread_id,
system_message=system_message,
@ -38,7 +54,7 @@ async def run_agent(
temperature=0.7,
max_tokens=150,
tool_choice="auto",
additional_message=None,
# additional_message=additional_message,
execute_tools_async=False,
use_tools=True,
execute_model_tool_calls=True
@ -52,7 +68,12 @@ async def run_agent(
if __name__ == "__main__":
async def main():
thread_manager = ThreadManager()
state_manager = StateManager("state.json")
thread_id = await thread_manager.create_thread()
# Read current file contents
files = await state_manager.get("files")
await thread_manager.add_message(thread_id, {"role": "user", "content": "Please create a file with a random name with the content 'Hello, world!'"})
thread_manager.add_tool(FilesTool)
@ -60,6 +81,7 @@ if __name__ == "__main__":
await run_agent(
thread_manager=thread_manager,
thread_id=thread_id,
state_manager=state_manager,
max_iterations=5
)

51
state.json Normal file
View File

@ -0,0 +1,51 @@
{
"files": {
"random_file_808.txt": "The only thing we have to fear is fear itself.",
"random_file_202.txt": "All that glitters is not gold.",
"random_file_606.txt": "You miss 100% of the shots you don't take.",
"random_file_404.txt": "In the end, we only regret the chances we didn't take.",
"random_file_1010.txt": "The greatest glory in living lies not in never falling, but in rising every time we fall.",
"random_file_789.txt": "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
"random_file_116.txt": "The pen is mightier than the sword.",
"random_file_117.txt": "Beauty is in the eye of the beholder.",
"random_file_303.txt": "A journey of a thousand miles begins with a single step.",
"random_file_101.txt": "To be or not to be, that is the question.",
"random_file_115.txt": "When in Rome, do as the Romans do.",
"random_file_129.txt": "Hello, world!",
"random_file_707.txt": "Not all those who wander are lost.",
"random_file_505.txt": "The best time to plant a tree was 20 years ago. The second best time is now.",
"random_file_909.txt": "Do not go where the path may lead, go instead where there is no path and leave a trail.",
"random_file_128.txt": "In the middle of difficulty lies opportunity",
"random_file_114.txt": "A picture is worth a thousand words.",
"random_file_138.txt": "May",
"random_file_111.txt": "Hello, world!",
"random_file_139.txt": "Success usually comes to those who are too busy to be looking for it.",
"random_file_113.txt": "Actions speak louder than words.",
"random_file_112.txt": "The early bird catches the worm.",
"random_file_137.txt": "Life is really simple, but we insist on making it complicated.",
"random_file_123.txt": "Hello, world!",
"random_file_122.txt": "Life is what happens when you're busy making other plans.",
"random_file_136.txt": "The way to get started is to quit talking and begin doing.",
"random_file_120.txt": "The only limit to our realization of tomorrow is our doubts of today.",
"random_file_134.txt": "What lies behind us and what lies before us",
"random_file_135.txt": "To succeed in life, you need three things: a wishbone, a backbone and a funny bone.",
"random_file_121.txt": "The future belongs to those who believe in the beauty of their dreams.",
"random_file_119.txt": "When the going gets tough, the tough get going.",
"random_file_125.txt": "Happiness is not something ready-made. It comes from your own actions.",
"random_file_131.txt": "Keep your face always toward the sunshine\u2014and shadows will fall behind you.",
"random_file_124.txt": "The purpose of our lives is to be happy",
"random_file_118.txt": "The early bird catches the worm.",
"random_file_456.txt": "The quick brown fox jumps over the lazy dog.",
"random_file_132.txt": "The best preparation for tomorrow is doing your best today.",
"random_file_126.txt": "Turn your wounds into wisdom.",
"random_file_127.txt": "Success is not how high you have climbed, but how you make a positive difference to the world.",
"random_file_133.txt": "Nothing is impossible, the word itself says, \u2018I\u2019m possible!\u2019",
"random_file_140.txt": "Don't be afraid to give up the good to go for the great.",
"random_file_141.txt": "I find that the harder I work, the more luck I seem to have.",
"random_file_143.txt": "The only impossible journey is the one you never begin.",
"random_file_142.txt": "Success is not",
"random_file_146.txt": "You must be the",
"random_file_145.txt": "The best way to predict the future is to create it.",
"random_file_144.txt": "In the end, we will remember not the words of our enemies, but the silence of our friends."
}
}

94
state_manager.py Normal file
View File

@ -0,0 +1,94 @@
import json
import os
import logging
from typing import Any
from asyncio import Lock
from contextlib import asynccontextmanager
class StateManager:
def __init__(self, store_file: str = "state.json"):
"""
Initialize StateManager with custom store file name.
Args:
store_file: Name of the JSON file to store state (default: "state.json")
"""
self.lock = Lock()
self.store_file = store_file
logging.info(f"StateManager initialized with store file: {store_file}")
@asynccontextmanager
async def store_scope(self):
try:
# Read current state
if os.path.exists(self.store_file):
with open(self.store_file, 'r') as f:
store = json.load(f)
else:
store = {}
yield store
# Write updated state
with open(self.store_file, 'w') as f:
json.dump(store, f, indent=2)
logging.debug("Store saved successfully")
except Exception as e:
logging.error("Error in store operation", exc_info=True)
raise
async def set(self, key: str, data: Any):
"""
Store any JSON-serializable data with a simple key.
Args:
key: Simple string key like "config" or "settings"
data: Any JSON-serializable data (dict, list, str, int, bool, etc)
"""
async with self.lock:
async with self.store_scope() as store:
try:
store[key] = data # Will be JSON serialized when written to file
logging.info(f'Updated store key: {key}')
return data
except Exception as e:
logging.error(f'Error in set: {str(e)}')
raise
async def get(self, key: str) -> Any:
"""
Get data for a key.
Args:
key: Simple string key like "config" or "settings"
"""
async with self.store_scope() as store:
if key in store:
data = store[key]
logging.info(f'Retrieved key: {key}')
return data
logging.info(f'Key not found: {key}')
return None
async def delete(self, key: str):
"""Delete data for a key"""
async with self.lock:
async with self.store_scope() as store:
if key in store:
del store[key]
logging.info(f"Deleted key: {key}")
else:
logging.info(f"Key not found for deletion: {key}")
async def export_store(self) -> dict:
"""Export entire store"""
async with self.store_scope() as store:
logging.info(f"Store content: {store}")
return store
async def clear_store(self):
"""Clear entire store"""
async with self.lock:
async with self.store_scope() as store:
store.clear()
logging.info("Cleared store")

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
{"messages": [{"role": "user", "content": "Please create a file with a random name with the content 'Hello, world!'"}, {"role": "assistant", "content": "", "tool_calls": [{"id": "call_NllUgRoFVHDhwFtuFcyZtbeQ", "type": "function", "function": {"name": "create_file", "arguments": "{\"file_path\":\"random_file_4812.txt\",\"content\":\"Hello, world!\"}"}}]}, {"role": "tool", "tool_call_id": "call_NllUgRoFVHDhwFtuFcyZtbeQ", "name": "create_file", "content": "ToolResult(success=True, output=\"File 'random_file_4812.txt' created successfully.\")"}, {"role": "user", "content": "CREATE MORE RANDOM FILES WITH RANDOM CONTENTS. JSUT CREATE IT \u2013 NO QUESTINS PLEASE.'"}, {"role": "assistant", "content": "", "tool_calls": [{"id": "call_HKp4nWYTRDSkVHivYTsHywL2", "type": "function", "function": {"name": "create_file", "arguments": "{\"file_path\": \"random_file_6573.txt\", \"content\": \"Lorem ipsum dolor sit amet, consectetur adipiscing elit.\"}"}}, {"id": "call_cJaBUWK5ybYgReV29niQb6hf", "type": "function", "function": {"name": "create_file", "arguments": "{\"file_path\": \"random_file_2910.txt\", \"content\": \"The quick brown fox jumps over the lazy dog.\"}"}}, {"id": "call_2WFuVQoDrkF3ayX6fMSlbJpn", "type": "function", "function": {"name": "create_file", "arguments": "{\"file_path\": \"random_file_8347.txt\", \"content\": \"To be, or not to be, that is the question.\"}"}}]}, {"role": "tool", "tool_call_id": "call_HKp4nWYTRDSkVHivYTsHywL2", "name": "create_file", "content": "ToolResult(success=True, output=\"File 'random_file_6573.txt' created successfully.\")"}, {"role": "tool", "tool_call_id": "call_cJaBUWK5ybYgReV29niQb6hf", "name": "create_file", "content": "ToolResult(success=True, output=\"File 'random_file_2910.txt' created successfully.\")"}, {"role": "tool", "tool_call_id": "call_2WFuVQoDrkF3ayX6fMSlbJpn", "name": "create_file", "content": "ToolResult(success=True, output=\"File 'random_file_8347.txt' created successfully.\")"}, {"role": "user", "content": "CREATE MORE RANDOM FILES WITH RANDOM CONTENTS. JSUT CREATE IT \u2013 NO QUESTINS PLEASE.'"}, {"role": "assistant", "content": "", "tool_calls": [{"id": "call_DjuN5efYZ9OcS6Z7Mz51wRdO", "type": "function", "function": {"name": "create_file", "arguments": "{\"file_path\": \"random_file_5021.txt\", \"content\": \"In the beginning God created the heaven and the earth.\"}"}}, {"id": "call_VWbUUI8yseHYELnYwkPW49xy", "type": "function", "function": {"name": "create_file", "arguments": "{\"file_path\": \"random_file_9384.txt\", \"content\": \"All human beings are born free and equal in dignity and rights.\"}"}}, {"id": "call_z6MV5G5YLX33YEq43rA5kwLt", "type": "function", "function": {"name": "create_file", "arguments": "{\"file_path\": \"random_file_1726.txt\", \"content\": \"The only thing we have to fear is fear itself.\"}"}}]}, {"role": "tool", "tool_call_id": "call_DjuN5efYZ9OcS6Z7Mz51wRdO", "name": "create_file", "content": "ToolResult(success=True, output=\"File 'random_file_5021.txt' created successfully.\")"}, {"role": "tool", "tool_call_id": "call_VWbUUI8yseHYELnYwkPW49xy", "name": "create_file", "content": "ToolResult(success=True, output=\"File 'random_file_9384.txt' created successfully.\")"}, {"role": "tool", "tool_call_id": "call_z6MV5G5YLX33YEq43rA5kwLt", "name": "create_file", "content": "ToolResult(success=True, output=\"File 'random_file_1726.txt' created successfully.\")"}, {"role": "user", "content": "CREATE MORE RANDOM FILES WITH RANDOM CONTENTS. JSUT CREATE IT \u2013 NO QUESTINS PLEASE.'"}]}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,12 +1,39 @@
import os
import asyncio
from datetime import datetime
from agentpress.tool import Tool, ToolResult, tool_schema
from state_manager import StateManager
class FilesTool(Tool):
def __init__(self):
super().__init__()
self.workspace = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'workspace')
os.makedirs(self.workspace, exist_ok=True)
self.state_manager = StateManager("state.json")
asyncio.create_task(self._init_workspace_state())
async def _init_workspace_state(self):
"""Initialize or update the workspace state in JSON"""
files_state = {}
# Walk through workspace and record all files
for root, _, files in os.walk(self.workspace):
for file in files:
full_path = os.path.join(root, file)
# Get path relative to workspace: e.g. "src/components/file.jsx"
rel_path = os.path.relpath(full_path, self.workspace)
try:
with open(full_path, 'r') as f:
content = f.read()
files_state[rel_path] = content
except Exception as e:
print(f"Error reading file {rel_path}: {e}")
await self.state_manager.set("files", files_state)
async def _update_workspace_state(self):
"""Update the workspace state after any file operation"""
await self._init_workspace_state()
@tool_schema({
"name": "create_file",
@ -25,9 +52,12 @@ class FilesTool(Tool):
full_path = os.path.join(self.workspace, file_path)
if os.path.exists(full_path):
return self.fail_response(f"File '{file_path}' already exists. Use update_file to modify existing files.")
os.makedirs(os.path.dirname(full_path), exist_ok=True)
with open(full_path, 'w') as f:
f.write(content)
await self._update_workspace_state()
return self.success_response(f"File '{file_path}' created successfully.")
except Exception as e:
return self.fail_response(f"Error creating file: {str(e)}")
@ -45,10 +75,13 @@ class FilesTool(Tool):
})
async def read_file(self, file_path: str) -> ToolResult:
try:
full_path = os.path.join(self.workspace, file_path)
with open(full_path, 'r') as f:
content = f.read()
return self.success_response({"file_path": file_path, "content": content})
workspace_state = await self.state_manager.get("workspace")
if file_path in workspace_state["files"]:
return self.success_response({
"file_path": file_path,
"content": workspace_state["files"][file_path]["content"]
})
return self.fail_response(f"File '{file_path}' not found in workspace state.")
except Exception as e:
return self.fail_response(f"Error reading file: {str(e)}")
@ -69,11 +102,12 @@ class FilesTool(Tool):
full_path = os.path.join(self.workspace, file_path)
with open(full_path, 'w') as f:
f.write(content)
await self._update_workspace_state()
return self.success_response(f"File '{file_path}' updated successfully.")
except Exception as e:
return self.fail_response(f"Error updating file: {str(e)}")
@tool_schema({
"name": "delete_file",
"description": "Delete a file from the workspace",
@ -89,6 +123,8 @@ class FilesTool(Tool):
try:
full_path = os.path.join(self.workspace, file_path)
os.remove(full_path)
await self._update_workspace_state()
return self.success_response(f"File '{file_path}' deleted successfully.")
except Exception as e:
return self.fail_response(f"Error deleting file: {str(e)}")