Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
321 changes: 135 additions & 186 deletions app/agents/agent.py

Large diffs are not rendered by default.

13 changes: 8 additions & 5 deletions app/agents/roles/coding_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
from app.agents.agent import Agent
from app.agents.tools import (
read_file_tool, read_file_tool_def,
edit_file_tool, edit_file_tool_def,
write_to_file_tool, write_to_file_tool_def,
list_files_tool, list_files_tool_def,
run_tests_tool, run_tests_tool_def
)


Expand All @@ -15,9 +16,10 @@ def __init__(self, name: str, role: str, goal: str, **kwargs):
# Добавляем специфику роли в цель, чтобы она попала в основной промпт
refined_goal = (
f"{goal}\n\n"
"IMPORTANT: Your primary goal is to make precise, targeted changes (surgical edits). "
"Do not rewrite entire files. Instead, identify the specific function, method, "
"or block of code that needs changing and use the 'edit_file_tool' to replace only that part."
"IMPORTANT: Your primary goal is to write high-quality, efficient, and clean Python code. "
"You must follow the provided coding standards. "
"When you need to modify a file, read its content first, then provide the full, complete, updated content to the 'write_to_file_tool'. "
"This tool will overwrite the entire file with your new content."
)

super().__init__(
Expand All @@ -28,8 +30,9 @@ def __init__(self, name: str, role: str, goal: str, **kwargs):
)
# Self-register tools
self.add_tool(read_file_tool, read_file_tool_def)
self.add_tool(edit_file_tool, edit_file_tool_def)
self.add_tool(list_files_tool, list_files_tool_def)
self.add_tool(write_to_file_tool, write_to_file_tool_def)
self.add_tool(run_tests_tool, run_tests_tool_def)

# Старый system_prompt и _create_initial_messages больше не нужны,
# так как вся логика теперь в базовом классе Agent.
73 changes: 45 additions & 28 deletions app/agents/roles/standard_roles.py
Original file line number Diff line number Diff line change
@@ -1,43 +1,60 @@
"""
This module defines the standard roles for specialized agents in the system.
"""
"""This module defines the standard agent roles and their configurations."""

# Configuration for an agent specialized in file system operations
# Default configuration for a general-purpose agent
DEFAULT_AGENT = {
"name": "DefaultAgent",
"role": "A helpful and versatile AI assistant.",
"goal": "Fulfill the user's request to the best of your ability."
}

# Base configurations with specific tools for each agent role.
# The factory will use the 'tools' list to equip each agent.

# Configuration for a file system expert agent
FILESYSTEM_EXPERT = {
"name": "FileSystemExpert",
"role": "An expert in browsing and reading files on a local file system.",
"goal": "To help users understand the project structure by listing and reading files.",
"tools": ["list_files", "read_file", "save_memory"]
"role": "An expert in interacting with the local file system.",
"goal": "Manage files and directories, such as creating, reading, and writing files.",
"tools": ["read_file", "list_files", "write_to_file", "update_file"],
}

# Configuration for an agent specialized in web searching
# Configuration for a web search expert agent
WEB_SEARCH_EXPERT = {
"name": "WebSearchExpert",
"role": "An expert in searching the web for real-time information.",
"goal": "To find the most relevant and up-to-date information online in response to a user's query.",
"tools": ["web_search", "save_memory"]
"role": "An expert in finding information on the web.",
"goal": "Answer questions and provide information by searching the web.",
"tools": ["web_search"],
}

# Add other specialized agent configurations here as needed.
# For example, a CodeWriterAgent, a DatabaseExpert, etc.
# Configuration for a coding agent
CODING_AGENT = {
"name": "CodingAgent",
"role": "A professional coder who writes high-quality, efficient, and clean Python code.",
"goal": "Write high-quality, efficient, and clean Python code according to provided standards.",
"tools": ["read_file", "list_files", "write_to_file", "run_tests", "update_file"],
}

ALL_ROLES = {
"FileSystemExpert": FILESYSTEM_EXPERT,
"WebSearchExpert": WEB_SEARCH_EXPERT,
# Configuration for a reviewer agent
REVIEWER_AGENT = {
"name": "ReviewerAgent",
"role": "A meticulous reviewer who ensures code quality, adherence to standards, and correctness.",
"goal": "Ensure code quality, adherence to standards, and correctness.",
"tools": ["read_file"],
}

# Configuration for the planner agent
PLANNER_AGENT = {
"name": "PlannerAgent",
"role": "A master planner who specializes in breaking down complex goals into a sequence of actionable steps for a team of specialized agents.",
"goal": "To create a clear, step-by-step JSON plan that efficiently leads to the user's desired outcome.",
"tools": [] # The planner does not use tools, it only thinks.
# Configuration for a testing agent
TESTING_AGENT = {
"name": "TestingAgent",
"role": "Software Quality Assurance Engineer",
"goal": "Thoroughly test code to find bugs and ensure reliability.",
"tools": ["run_tests", "read_file"],
}

# Configuration for the evaluator agent
EVALUATOR_AGENT = {
"name": "EvaluatorAgent",
"role": "A meticulous evaluator who analyzes multiple execution plans and selects the most optimal one.",
"goal": "To choose the most efficient, logical, and safe plan from a given set of options.",
"tools": [] # The evaluator only thinks and chooses.
# A dictionary of all available agent roles that the Orchestrator can assign tasks to.
ALL_ROLES = {
"FileSystemExpert": FILESYSTEM_EXPERT,
"WebSearchExpert": WEB_SEARCH_EXPERT,
"CodingAgent": CODING_AGENT,
"ReviewerAgent": REVIEWER_AGENT,
"TestingAgent": TESTING_AGENT,
}
50 changes: 28 additions & 22 deletions app/agents/roles/task_decomposer.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,46 +19,52 @@ def __init__(self, name: str, role: str, goal: str, **kwargs):
)
self.system_prompt = """
You are an expert project manager. Your task is to break down a high-level user goal into a concise, step-by-step plan.
Each step must be a single, clear action assigned to one of the available roles.
Each step must be a single, clear action assigned to an appropriate agent.
Combine simple, related actions into a single, comprehensive step. For example, instead of one step to create a file and another to write to it, create a single step that does both.

# AVAILABLE ROLES:
- CodingAgent: Writes, modifies, and fixes code.
- TestingAgent: Runs tests and reports results.
- ReviewerAgent: Performs code reviews, checking for quality and adherence to standards.
- EvaluatorAgent: Analyzes test failures and creates bug reports.
# AVAILABLE ROLES & THEIR KEY TOOLS:
- **FileSystemExpert**: Works with files. Key tool: `write_to_file_tool(path, content)`.
- **CodingAgent**: Writes, modifies, and fixes Python code. Key tool: `write_to_file_tool(path, content)`.
- **TestingAgent**: Runs tests. Key tool: `run_tests_tool(path)`.
- **ReviewerAgent**: Performs code reviews. Key tool: `read_file_tool(path)`.

# OUTPUT FORMAT:
Your output MUST be a single JSON object with a single key "plan", which contains a list of steps. Do not include any other text, explanation, or markdown code fences.

# EXAMPLE:
# EXAMPLE 1: Simple file operation
Goal: "Create a file named 'hello.txt' and write 'Hello World' in it."

Your output:
{
"plan": [
{
"step": 1,
"agent": "FileSystemExpert",
"task": "Create a new file 'hello.txt' with the content 'Hello World'."
}
]
}

# EXAMPLE 2: More complex coding task
Goal: "Create a function to add two numbers and test it."

Your output:
{
"plan": [
{
"step": 1,
"assignee": "CodingAgent",
"task": "Create a new function 'add(a, b)' in 'app/utils/math.py'",
"description": "Implement the core logic for the addition function."
"agent": "CodingAgent",
"task": "Create a new file 'app/utils/math.py' with an 'add(a, b)' function that returns the sum of two numbers."
},
{
"step": 2,
"assignee": "ReviewerAgent",
"task": "Review the 'add' function in 'app/utils/math.py'",
"description": "Ensure the code quality and correctness of the new function."
"agent": "CodingAgent",
"task": "Create a new test file 'tests/test_math.py' to test the 'add' function. Include at least one test case."
},
{
"step": 3,
"assignee": "CodingAgent",
"task": "Create a new test file 'tests/test_math.py' with tests for the 'add' function",
"description": "Write unit tests to verify the behavior of the 'add' function."
},
{
"step": 4,
"assignee": "TestingAgent",
"task": "Run the tests in 'tests/test_math.py'",
"description": "Execute the newly created tests to confirm the function works as expected."
"agent": "TestingAgent",
"task": "Run the tests in 'tests/test_math.py'."
}
]
}
Expand Down
126 changes: 67 additions & 59 deletions app/agents/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,13 @@ def read_file_tool(input_data: Dict[str, Any]) -> str:

def list_files_tool(input_data: Dict[str, Any]) -> str:
"""
Рекурсивно выводит дерево файлов и директорий по указанному пути,
игнорируя служебные файлы/директории. Помогает понять структуру проекта.

Выводит список файлов и директорий по указанному пути.

Args:
input_data (Dict[str, Any]): Словарь, который может содержать ключ 'path'
с путем к директории. По умолчанию - текущая.
Returns:
Отформатированное дерево файлов и директории в виде строки.
Отформатированное дерево файлов и директорий в виде строки.
"""
path = input_data.get("path", ".")
if not os.path.isdir(path):
Expand All @@ -58,66 +57,32 @@ def list_files_tool(input_data: Dict[str, Any]) -> str:
return output.strip()


def edit_file_tool(input_data: Dict[str, Any]) -> str:
def write_to_file_tool(input_data: Dict[str, Any]) -> str:
"""
Создает, перезаписывает, добавляет или заменяет контент в файле.
- 'overwrite': Полностью перезаписывает файл.
- 'append': Добавляет контент в конец файла.
- 'replace': Заменяет один фрагмент строки на другой.
Создает новый файл или полностью перезаписывает существующий.

Args:
input_data (Dict[str, Any]): Словарь, содержащий:
'path' (str): Путь к файлу.
'mode' (str): Режим работы ('overwrite', 'append', 'replace').
'content' (str, optional): Содержимое для 'overwrite' или 'append'.
'old_content' (str, optional): Исходный фрагмент для 'replace'.
'new_content' (str, optional): Новый фрагмент для 'replace'.
'content' (str): Содержимое для записи.
"""
path = input_data.get("path")
mode = input_data.get("mode", "overwrite")
content = input_data.get("content")

if not path:
return "Ошибка: Аргумент 'path' обязателен."
if mode not in ['overwrite', 'append', 'replace']:
return "Ошибка: Недопустимый режим. Используйте 'overwrite', 'append' или 'replace'."
if not path or content is None:
return "Ошибка: Аргументы 'path' и 'content' обязательны."

try:
# Убедимся, что директория для файла существует
directory = os.path.dirname(path)
if directory:
os.makedirs(directory, exist_ok=True)

if mode == 'replace':
old_content = input_data.get("old_content")
new_content = input_data.get("new_content")
if old_content is None or new_content is None:
return "Ошибка: Для режима 'replace' необходимы 'old_content' и 'new_content'."

with open(path, "r", encoding="utf-8") as f:
file_content = f.read()

if old_content not in file_content:
return f"Ошибка: Исходный фрагмент 'old_content' не найден в файле '{path}'."

file_content = file_content.replace(old_content, new_content, 1)

with open(path, "w", encoding="utf-8") as f:
f.write(file_content)
return f"Файл '{path}' успешно обновлен в режиме 'replace'."

else: # overwrite or append
content = input_data.get("content")
if content is None:
return f"Ошибка: Для режима '{mode}' обязателен аргумент 'content'."

write_mode = "w" if mode == "overwrite" else "a"
with open(path, write_mode, encoding="utf-8") as f:
f.write(content)
return f"Файл '{path}' успешно обновлен в режиме '{mode}'."

except FileNotFoundError:
return f"Ошибка: Файл не найден по пути '{path}'."

with open(path, "w", encoding="utf-8") as f:
f.write(content)
return f"Файл '{path}' успешно создан/перезаписан."
except Exception as e:
return f"Ошибка: Не удалось выполнить операцию с файлом '{path}': {e}"
return f"Ошибка: Не удалось записать в файл '{path}': {e}"


def delete_file_tool(input_data: Dict[str, Any]) -> str:
Expand Down Expand Up @@ -181,6 +146,30 @@ def run_tests_tool(input_data: Dict[str, Any]) -> str:
return f"Критическая ошибка: Не удалось запустить тесты. Причина: {e}"


def update_file_tool(input_data: Dict[str, Any]) -> str:
"""
Appends content to an existing file. If the file doesn't exist, it creates it.

Args:
input_data (Dict[str, Any]): A dictionary containing:
'path' (str): The path to the file.
'content' (str): The content to append to the file.
"""
path = input_data.get("path")
content = input_data.get("content")

if not path or content is None:
return "Error: 'path' and 'content' are required arguments."

try:
# 'a' mode appends to the file, and creates it if it doesn't exist.
with open(path, "a", encoding="utf-8") as f:
f.write("\n\n" + content)
return f"Content successfully appended to '{path}'."
except Exception as e:
return f"Error: Could not update file '{path}': {e}"


# Определения инструментов (Tool Definitions)
read_file_tool_def = {
"type": "function",
Expand Down Expand Up @@ -212,21 +201,18 @@ def run_tests_tool(input_data: Dict[str, Any]) -> str:
},
}

edit_file_tool_def = {
write_to_file_tool_def = {
"type": "function",
"function": {
"name": "edit_file_tool",
"description": "Создает, перезаписывает, добавляет или заменяет контент в файле. Режимы: 'overwrite', 'append', 'replace'.",
"name": "write_to_file_tool",
"description": "Создает новый файл или полностью перезаписывает существующий указанным контентом.",
"parameters": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "Полный путь к файлу."},
"mode": {"type": "string", "enum": ["overwrite", "append", "replace"], "description": "Режим записи."},
"content": {"type": "string", "description": "Содержимое для 'overwrite' или 'append'."},
"old_content": {"type": "string", "description": "Исходный фрагмент для 'replace'."},
"new_content": {"type": "string", "description": "Новый фрагмент для 'replace'."},
"path": {"type": "string", "description": "Полный путь к файлу (включая имя файла)."},
"content": {"type": "string", "description": "Полное содержимое для записи в файл."},
},
"required": ["path", "mode"],
"required": ["path", "content"],
},
},
}
Expand Down Expand Up @@ -260,3 +246,25 @@ def run_tests_tool(input_data: Dict[str, Any]) -> str:
},
},
}

update_file_tool_def = {
"type": "function",
"function": {
"name": "update_file_tool",
"description": "Appends content to an existing file. Creates the file if it does not exist.",
"parameters": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "The path to the file to be updated."
},
"content": {
"type": "string",
"description": "The content to append to the file."
}
},
"required": ["path", "content"]
}
}
}
Loading
Loading