diff --git a/README.md b/README.md index d113c35..1caf2ed 100644 --- a/README.md +++ b/README.md @@ -20,16 +20,17 @@ ## ✨ Key features -| Feature | Why it matters | -| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------- | -| **Composable `Agent` model** | Chain or orchestrate agents via `SimpleSequentialAgents`, `AgentManager`, and `AutoAgentManager` for flexible workflows | -| **Auto-routing agent** | `AutoAgentManager` uses a language model to dynamically decide the next agent in the loop | -| **Web-augmented agent** | `InternetAgent` uses `googlesearch`, `requests`, and summarizing agents to fetch and condense live web data | -| **Parallel summarization** | `fast_start` method uses `ThreadPoolExecutor` to speed up web content processing | -| **Prompt-driven summaries** | Summarization is driven by customizable system prompts and token-limited chunking for accurate context | -| **Simple, Pythonic design** | Agents are lightweight Python classes with callable message interfaces—no metaclasses or hidden magic | -| **Memory, BaseMemory** | `BaseMemory` provides foundational memory management for agents, storing conversation history and message objects. It supports adding, retrieving, and clearing memory, offering a flexible design for session-based context, interaction history, or task-specific memory across multiple agent invocations. Ideal for scenarios where the agent needs to recall past interactions for continuity. | -| **SummarizerMemory with summarizer agent** | `SummarizerMemory` extends `BaseMemory` by integrating a summarizing `Agent` that automatically condenses long histories when memory limits are exceeded. This enables agents to maintain compact, relevant context over time, ensuring efficiency without losing key information. | +| Feature | Why it matters | +| -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Composable `Agent` model** | Chain or orchestrate agents via `SimpleSequentialAgents`, `AgentManager`, and `AutoAgentManager` for flexible workflows | +| **Auto-routing agent** | `AutoAgentManager` uses a language model to dynamically decide the next agent in the loop | +| **Web-augmented agent** | `InternetAgent` uses `googlesearch`, `requests`, and summarizing agents to fetch and condense live web data | +| **Parallel summarization** | `fast_start` method uses `ThreadPoolExecutor` to speed up web content processing | +| **Prompt-driven summaries** | Summarization is driven by customizable system prompts and token-limited chunking for accurate context | +| **Simple, Pythonic design** | Agents are lightweight Python classes with callable message interfaces—no metaclasses or hidden magic | +| **Memory, BaseMemory** | `BaseMemory` provides foundational memory management for agents, storing conversation history and message objects. It supports adding, retrieving, and clearing memory, offering a flexible design for session-based context, interaction history, or task-specific memory across multiple agent invocations. Ideal for scenarios where the agent needs to recall past interactions for continuity. | +| **SummarizerMemory with summarizer agent** | `SummarizerMemory` extends `BaseMemory` by integrating a summarizing `Agent` that automatically condenses long histories when memory limits are exceeded. This enables agents to maintain compact, relevant context over time, ensuring efficiency without losing key information. | +| **SmartAgentBuilder for automated agent creation** | `SmartAgentBuilder` automates breaking down a high-level task into structured subtasks, then creates specialized agents for each subtask using a sequential pipeline. It ensures that each agent is precisely configured with a strict role, and outputs an `AutoAgentManager` to run them in coordination. | diff --git a/example/AutoAgent.ipynb b/example/AutoAgent.ipynb index 8422b6e..7cb1ba8 100644 --- a/example/AutoAgent.ipynb +++ b/example/AutoAgent.ipynb @@ -18,7 +18,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "6057cd04", "metadata": {}, "outputs": [ @@ -29,16 +29,16 @@ "Routing from user -> time_reader \n", " content: what time is it?\n", "Routing from time_reader -> date_converter \n", - " content: The current time is 17:03:07.\n" + " content: The current time is 11:28 PM.\n" ] }, { "data": { "text/plain": [ - "'زمان فعلی ۱۷:۰۳:۰۷ است. \\n[#finish#]'" + "'زمان کنونی ۱۱:۲۸ شب است. \\n[#finish#]'" ] }, - "execution_count": 7, + "execution_count": 2, "metadata": {}, "output_type": "execute_result" } @@ -69,6 +69,14 @@ "res = manager.start()\n", "res.content" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b792a066", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { diff --git a/example/SmartAgentBuilder.ipynb b/example/SmartAgentBuilder.ipynb new file mode 100644 index 0000000..42691c6 --- /dev/null +++ b/example/SmartAgentBuilder.ipynb @@ -0,0 +1,162 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "67b890e9", + "metadata": {}, + "source": [ + "# SmartAgentBuilder\n", + "\n", + "In this example you can see how this module can break tasks and create agents atumatically for you. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "579d2068", + "metadata": {}, + "outputs": [], + "source": [ + "base_url= \"\" # use your own base_url from api provider or local provider like ollama.\n", + "api_key = \"\" # use your own api_key.\n", + "provider = \"openai\" # openai for openai like provider (vLLM or openrouter) and ollama for local use.\n", + "model = \"gpt-4o-mini\"" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "10561d55", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Routing from user -> task_generator\n", + "Routing from task_generator -> agent_creator\n", + "Agents are created : broken_word_extractor | correction_suggester | text_compiler\n" + ] + } + ], + "source": [ + "from iragent.agent import AgentFactory\n", + "from iragent.models import SmartAgentBuilder\n", + "\n", + "agent_factory = AgentFactory(\n", + " base_url=base_url,\n", + " api_key=api_key,\n", + " model=model,\n", + " provider=\"openai\"\n", + ")\n", + "\n", + "sab = SmartAgentBuilder(\n", + " agent_factory=agent_factory,\n", + ")\n", + "task = \"\"\"\n", + "I have a text which contain broken words and i need to correct them. So i want to just pass the broken text,\n", + "then get the corrected text. \n", + "Input: broken text.\n", + "output: corrected text.\n", + "\"\"\"\n", + "\n", + "manager = sab.create_agent(\n", + " task=task,\n", + ")\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "1a982ff9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Routing from user -> broken_word_extractor \n", + " content: Ths is a smple txt whre sme of the wrds are mssing lttrs an the sntnce strctre is nt prfct. It may be hrder to undrstand, bt you cn stil knd of figr out wht it mens.\n", + "Routing from broken_word_extractor -> correction_suggester \n", + " content: ['Ths', 'smple', 'txt', 'sme', 'wrds', 'mssing', 'lttrs', 'sntnce', 'strctre', 'nt', 'prfct', 'hrder', 'undrstand', 'bt', 'cn', 'stil', 'knd', 'figr', 'wht', 'mens']\n", + "Routing from correction_suggester -> text_compiler \n", + " content: Here are the suggested corrections for each broken word provided:\n", + "\n", + "1. Ths - This\n", + "2. smple - simple\n", + "3. txt - text\n", + "4. sme - some\n", + "5. wrds - words\n", + "6. mssing - missing\n", + "7. lttrs - letters\n", + "8. sntnce - sentence\n", + "9. strctre - structure\n", + "10. nt - not\n", + "11. prfct - perfect\n", + "12. hrder - harder\n", + "13. und\n", + "Routing from text_compiler -> text_compiler \n", + " content: This simple text has some words missing letters, sentence structure is not perfect, harder to understand.\n", + "Routing from text_compiler -> text_compiler \n", + " content: This simple text has some words missing letters, and the sentence structure is not perfect, making it harder to understand.\n", + "Routing from text_compiler -> text_compiler \n", + " content: This simple text has some words missing letters, and the sentence structure is not perfect, making it harder to understand.\n" + ] + } + ], + "source": [ + "msg = manager.start(message =\"Ths is a smple txt whre sme of the wrds are mssing lttrs an the sntnce strctre is nt prfct. It may be hrder to undrstand, bt you cn stil knd of figr out wht it mens.\")" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "62c70fda", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'This simple text has some words missing letters, and the sentence structure is not perfect, making it harder to understand.'" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "msg.content" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c626eacf", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/example/SmartPrompt.ipynb b/example/SmartPrompt.ipynb new file mode 100644 index 0000000..b6b7c4e --- /dev/null +++ b/example/SmartPrompt.ipynb @@ -0,0 +1,155 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "0904114a", + "metadata": {}, + "outputs": [], + "source": [ + "base_url= \"\" # use your own base_url from api provider or local provider like ollama.\n", + "api_key = \"\" # use your own api_key.\n", + "provider = \"openai\" # openai for openai like provider (vLLM or openrouter) and ollama for local use.\n", + "model = \"gpt-4o-mini\"" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "686c8d4a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Routing from user -> prompt_maker \n", + " content: \n", + " Here is the input example :\n", + " Yesterday I go shopping and buy some banana and apple and oranges.\n", + " \n", + " Here is the output example i have expected from system.\n", + " apple-banana-orange\n", + "\n", + " \n", + "Routing from prompt_maker -> prompt_reader \n", + " content: Extract the unique fruits mentioned in the input sentence and format them in a hyphen-separated string, sorted alphabetically.\n", + "Routing from prompt_reader -> prompt_maker \n", + " content: Feedback for prompt_maker:\n", + "\n", + "1. **Clarity**: The prompt could benefit from clearer instructions regarding the expected format of the input sentence. For example, specifying whether the fruits will be in a list, a paragraph, or another format would help in understanding how to extract them.\n", + "\n", + "2. **Examples**: Providing an example input sentence along with the expected output would enhance comprehension. This would illustrate the extraction process and the formatting requirements.\n", + "\n", + "3. **Handling Variations**: The prompt does not address potential variations in fruit names (e.g., \"apple\" vs. \"ApPle\") or plural forms (e.g., \"bananas\" vs. \"banana\"). Including instructions on how to handle these variations would improve the robustness of the extraction process.\n", + "\n", + "4. **Error Handling**: There is no mention of how to handle cases where no fruits are found in the input sentence. Clarifying what the output should be in such cases would make the prompt more comprehensive.\n", + "\n", + "5. **Scope**: The prompt assumes that the input will only contain fruits. It might be useful to specify whether to ignore other types of words or if there are any constraints on the types of fruits to be considered.\n", + "\n", + "Overall, refining these aspects would lead to a more effective and user-friendly prompt.\n", + "Routing from prompt_maker -> prompt_reader \n", + " content: You are a text extraction system designed to identify and extract fruit names from a given input sentence. Your task is to process the input and return a list of fruits mentioned, formatted as a comma-separated string. \n", + "\n", + "**Instructions:**\n", + "1. Identify and extract all fruit names from the input sentence. \n", + "2. The output should be in lowercase and include only the names of the fruits, separated by commas.\n", + "3. Handle variations in fruit names (e.g., \"apple\" vs. \"ApPle\") and plural forms (e.g., \"bananas\" vs. \"banana\") by standardizing them to their singular, lowercase form.\n", + "4. If no fruits are found in the input sentence, return an empty string.\n", + "5. Ignore any other types of words or items that are not fruits.\n", + "\n", + "**Example:**\n", + "Input: \"I went to the market and bought some bananas and apples.\"\n", + "Output: \"banana, apple\"\n", + "\n", + "Now, process the following input: \n", + "{input_data}\n", + "Routing from prompt_reader -> prompt_maker \n", + " content: Feedback for the prompt maker:\n", + "\n", + "1. **Clarity and Specificity**: The prompt is generally clear, but it could benefit from more specific examples of fruit names to ensure that the system can recognize a wider variety of fruits. Including a list of common fruits or examples of less common fruits could help improve the extraction accuracy.\n", + "\n", + "2. **Handling Ambiguities**: The prompt does not address how to handle ambiguous terms that may refer to fruits in some contexts but not in others (e.g., \"peach\" in \"peach tree\"). Providing guidance on how to deal with such cases would enhance the robustness of the extraction system.\n", + "\n", + "3. **Input Format**: The placeholder `{input_data}` is not defined in the prompt. It would be helpful to specify the expected format of the input data (e.g., a complete sentence, a list of sentences) to avoid confusion.\n", + "\n", + "4. **Output Format**: While the output format is specified as a comma-separated string, it might be useful to clarify whether there should be a space after each comma or if the fruits should be listed without any spaces.\n", + "\n", + "5. **Error Handling**: The prompt mentions returning an empty string if no fruits are found, but it does not specify how to handle potential errors in input (e.g., if the input is not a valid sentence). Including error handling instructions could improve the system's reliability.\n", + "\n", + "Overall, while the prompt provides a solid foundation for a text extraction system, addressing these areas could enhance its effectiveness and clarity.\n" + ] + } + ], + "source": [ + "from iragent.agent import AgentFactory\n", + "from iragent.models import SmartPrompt\n", + "\n", + "agent_factory = AgentFactory(\n", + " base_url=base_url,\n", + " api_key=api_key,\n", + " model=model,\n", + " provider=\"openai\"\n", + ")\n", + "\n", + "sp = SmartPrompt(agent_factory=agent_factory)\n", + "i = \"Yesterday I go shopping and buy some banana and apple and oranges.\"\n", + "o = \"apple-banana-orange\"\n", + "msg = sp.generate(\n", + " input=i,\n", + " output=o\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "1026a3b8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Extract all fruit names from the provided input sentence. The input will be a complete sentence containing various items, including fruits. Return the extracted fruit names as a comma-separated string without spaces after the commas. If no fruits are found, return an empty string. Handle ambiguous terms carefully, ensuring that only recognized fruit names are included. The input format will be a single sentence, and ensure to validate that the input is a proper sentence before processing. \\n\\nExample input: \"I went to the market and bought oranges, peaches, and grapes.\"\\nExpected output: \"oranges,peaches,grapes\"'" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "msg.content" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "00083c29", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/iragent/__pycache__/agent.cpython-312.pyc b/iragent/__pycache__/agent.cpython-312.pyc index 2a60845..be38d43 100644 Binary files a/iragent/__pycache__/agent.cpython-312.pyc and b/iragent/__pycache__/agent.cpython-312.pyc differ diff --git a/iragent/__pycache__/memory.cpython-312.pyc b/iragent/__pycache__/memory.cpython-312.pyc index d7c85c3..3a1a0c3 100644 Binary files a/iragent/__pycache__/memory.cpython-312.pyc and b/iragent/__pycache__/memory.cpython-312.pyc differ diff --git a/iragent/__pycache__/models.cpython-312.pyc b/iragent/__pycache__/models.cpython-312.pyc index e459526..51abd78 100644 Binary files a/iragent/__pycache__/models.cpython-312.pyc and b/iragent/__pycache__/models.cpython-312.pyc differ diff --git a/iragent/__pycache__/prompts.cpython-312.pyc b/iragent/__pycache__/prompts.cpython-312.pyc index f190880..fe2cf77 100644 Binary files a/iragent/__pycache__/prompts.cpython-312.pyc and b/iragent/__pycache__/prompts.cpython-312.pyc differ diff --git a/iragent/__pycache__/tools.cpython-312.pyc b/iragent/__pycache__/tools.cpython-312.pyc index 19a0f47..84ac073 100644 Binary files a/iragent/__pycache__/tools.cpython-312.pyc and b/iragent/__pycache__/tools.cpython-312.pyc differ diff --git a/iragent/__pycache__/utility.cpython-312.pyc b/iragent/__pycache__/utility.cpython-312.pyc index b2c5ee4..4a73a2c 100644 Binary files a/iragent/__pycache__/utility.cpython-312.pyc and b/iragent/__pycache__/utility.cpython-312.pyc differ diff --git a/iragent/models.py b/iragent/models.py index d51303b..67b431d 100644 --- a/iragent/models.py +++ b/iragent/models.py @@ -4,14 +4,52 @@ from googlesearch import search from tqdm import tqdm -from .agent import Agent +from .agent import Agent, AgentFactory from .memory import BaseMemory from .message import Message -from .prompts import AUTO_AGENT_PROMPT, SUMMARIZER_PROMPT -from .utility import chunker, fetch_url +from .prompts import ( + AGENT_GENERATOR, + AUTO_AGENT_PROMPT, + SMART_PROMPT_READER, + SMART_PROMPT_WRITER, + SUMMARIZER_PROMPT, + TASK_GENERATOR, +) +from .tools import simple_termination +from .utility import chunker, create_agents, fetch_url class SimpleSequentialAgents: + """ + A lightweight wrapper for running multiple agents in a fixed, + predefined sequence. + + This class sets up a chain of agents where each agent's output is + automatically routed to the next agent in the list, until all + agents have executed in order. + + Internally, it uses `AgentManager` to handle message passing + and execution, with the number of rounds set to the number of agents. + + Attributes: + history (list): Stores the conversation or execution history. + agent_manager (AgentManager): Manages the sequential execution + of agents. + + Args: + agents (List[Agent]): The list of agents to execute in sequence. + Each agent will have its `next_agent` attribute set to the + name of the following agent in the list. + init_message (str): The initial message content passed to the + first agent. + + Methods: + start() -> List[Message]: + Runs the agents in sequential order, starting with the + initial message and passing outputs along the chain. + Returns the list of `Message` objects representing the + results of each agent's execution. + """ def __init__(self, agents: List[Agent], init_message: str): self.history = [] # We just follow sequencially the agents. @@ -30,6 +68,42 @@ def start(self) -> List[Message]: class AgentManager: + """ + A simple multi-agent execution manager that routes messages between + agents in a fixed sequence, with optional early termination. + + Unlike `AutoAgentManager`, this class does not dynamically decide + the next agent to route to — it executes in a predefined order + based on the message's `reciever` field. + + Attributes: + termination_fn (Callable): Optional function to determine when the + workflow should stop early. + max_round (int): Maximum number of message-passing iterations allowed. + agents (dict[str, Agent]): Dictionary of available agents, keyed by + agent name. + init_msg (Message): The initial message passed to the first agent. + + Args: + init_message (str): The initial request or instruction from the user. + agents (List[Agent]): The list of agents participating in the workflow. + first_agent (Agent): The first agent to receive the initial message. + max_round (int, optional): Maximum number of routing rounds. Defaults to 3. + termination_fn (Callable, optional): A function to check if the process + should terminate early. Defaults to None. + + Methods: + start() -> Message: + Executes the multi-agent workflow starting with `init_message`. + The process: + - Sends the message to the current agent. + - Evaluates termination conditions after each response. + - Passes the output directly to the next agent defined by the + `reciever` field in the message. + - Stops when the termination function returns True or the + maximum round count is reached. + Returns the final `Message` from the last executed agent. + """ def __init__( self, init_message: str, @@ -65,6 +139,53 @@ def start(self) -> Message: class AutoAgentManager: + """ + A multi-agent orchestration manager that routes messages between agents + in an automated workflow, with support for termination conditions and + dynamic agent selection. + + This class coordinates the execution of multiple agents in a round-based + loop. It starts with an initial message sent to the first agent, then uses + an internal `agent_manager` to determine the next agent to route the + response to. The process continues until: + - The termination function returns True, or + - The maximum number of rounds is reached. + + Attributes: + auto_agent (Agent): An internal controller agent responsible for + deciding which agent should handle the next step. + first_agent (Agent): The first agent to receive the initial message. + termination_fn (Callable): Optional function to determine when the + workflow should stop. + max_round (int): Maximum number of message-passing iterations allowed. + agents (dict[str, Agent]): Dictionary of available agents, keyed by + agent name. + init_msg (Message): The initial message passed to the first agent. + termination_word (str): Optional keyword used by `termination_fn` to + detect completion. + + Args: + init_message (str): The initial request or instruction from the user. + agents (List[Agent]): The list of agents participating in the workflow. + first_agent (Agent): The starting agent for message routing. + max_round (int, optional): Maximum number of routing rounds. Defaults to 3. + termination_fn (Callable, optional): A function to check if the process + should terminate early. Defaults to None. + termination_word (str, optional): Keyword used in termination checks. + Defaults to None. + + Methods: + start(message: str = None) -> Message: + Executes the multi-agent workflow starting from either the + `init_message` or a provided `message`. + The process: + - Sends the message to the current agent. + - Evaluates termination conditions after each response. + - Uses `auto_agent` to determine the next agent in the sequence. + - Stops when the termination function returns True or the + maximum round count is reached. + Returns the final `Message` from the last executed agent. + """ def __init__( self, init_message: str, @@ -76,14 +197,15 @@ def __init__( ) -> None: self.auto_agent = Agent( "agent_manager", - system_prompt="You are the Auto manager.", + system_prompt="You are the agent manager.", model=first_agent.model, base_url=first_agent.base_url, api_key=first_agent.api_key, temprature=0.1, - max_token=32, + max_token=1024, memory=BaseMemory ) + self.first_agent = first_agent self.termination_fn = termination_fn self.max_round = max_round self.agents = {agent.name: agent for agent in agents} @@ -96,12 +218,12 @@ def __init__( ) self.termination_word = termination_word - def start(self) -> Message: + def start(self, message = None) -> Message: list_agents_info = "\n".join( f"- [{agent_name}]-> system_prompt :{self.agents[agent_name].system_prompt}" for agent_name in self.agents.keys() ) - last_msg = self.init_msg + last_msg = Message(sender="user",reciever=self.first_agent.name,content=message) if message is not None else self.init_msg for _ in range(self.max_round): if last_msg.reciever not in self.agents.keys(): raise ValueError(f"No agent named {last_msg.reciever}") @@ -115,7 +237,7 @@ def start(self) -> Message: last_msg = res for _ in range(self.max_round): - next_agent = self.auto_agent.call_message( + next_agent = (self.auto_agent.call_message( Message( sender="auto_router", reciever="agent_manager", @@ -123,7 +245,7 @@ def start(self) -> Message: list_agents_info, last_msg.sender, last_msg.content ), ) - ).content + )).content if next_agent in self.agents.keys(): break last_msg.reciever = next_agent @@ -369,3 +491,152 @@ def _summarize_page(self, result, query: str): [s for s in summaries if s.strip() != "No relevant information found."] ), ) + + +class SmartPrompt: + """ + A utility class for generating optimized system prompts based on example + inputs and desired outputs using a two-agent collaborative workflow. + + This class leverages: + 1. `writer` – An agent that crafts an initial prompt tailored to produce + the desired output from a given input. + 2. `reader` – An agent that reviews and refines the generated prompt for + clarity, accuracy, and adherence to the intended task. + + Both agents are orchestrated by an `AutoAgentManager` to enable iterative + collaboration until the prompt meets the defined termination condition. + + Attributes: + writer (Agent): The prompt creation agent. + reader (Agent): The prompt review and refinement agent. + manager (AutoAgentManager): Coordinates the interaction between + `writer` and `reader` agents. + + Args: + agent_factory (AgentFactory): The factory used to create new agents. + + Methods: + generate(input: str, output: str) -> str: + Generates a refined prompt based on an example input and its + expected output. The process: + - Passes the input and output examples to the manager. + - Iteratively runs writer and reader agents. + - Returns the final crafted system prompt. + """ + def __init__(self, agent_factory: AgentFactory) -> None: + self.writer = agent_factory.create_agent( + name="prompt_maker", + temprature=0.1, + system_prompt=SMART_PROMPT_WRITER, + max_token = 512 + ) + self.reader = agent_factory.create_agent( + name="prompt_reader", + temprature=0.1, + system_prompt=SMART_PROMPT_READER, + max_token = 512 + ) + self.manager = AutoAgentManager( + init_message="", + agents= [self.writer,self.reader], + first_agent=self.writer, + max_round=5, + termination_fn=simple_termination, + termination_word="[#finish#]" + ) + + def generate(self, input: str, output: str) -> str: + msg = """ + Here is the input example : + {} + + Here is the output example i have expected from system. + {} + + """ + self.manager.init_msg.content = msg.format(input, output) + return self.manager.start() + + +class SmartAgentBuilder: + """ + A utility class for automatically generating and managing task-specific agents + based on a given high-level task description. + + This class uses a sequential pipeline of two agents: + 1. `task_generator` – Breaks down a high-level task into smaller, + structured subtasks. + 2. `agent_generator` – Creates dedicated agents for each subtask with + specific system prompts and configurations. + + The resulting agents are combined into an `AutoAgentManager` for coordinated + execution, ensuring that the output from one agent feeds into the next. + + Attributes: + agent_factory (AgentFactory): Factory for creating agents with predefined + settings. + task_generator (Agent): The agent responsible for generating subtasks + from a high-level task description. + agent_generator (Agent): The agent responsible for creating specialized + agents based on generated subtasks. + sequencial_agent (SimpleSequentialAgents): Manages the execution of + `task_generator` followed by `agent_generator`. + agents (list): Stores created agents. + + Args: + agent_factory (AgentFactory): The factory used to create new agents. + max_token (int, optional): Maximum token limit for each agent. Defaults + to 1024. + + Methods: + create_agent(task: str, init_message: str = None) -> list[Agent]: + Generates agents for a given high-level task, creates an + AutoAgentManager to run them, and returns the configured manager. + The process: + - Pass the task to the sequential pipeline. + - Convert generated subtask definitions into real agents. + - Initialize an AutoAgentManager for coordinated multi-agent execution. + """ + def __init__(self, agent_factory: AgentFactory, max_token: int=1024) -> None: + self.agent_factory = agent_factory + self.task_generator = self.agent_factory.create_agent( + name="task_generator", + temprature=0.1, + max_token = max_token, + system_prompt = TASK_GENERATOR, + ) + self.agent_generator = self.agent_factory.create_agent( + name = "agent_creator", + temprature=0.0, + max_token = max_token, + system_prompt= AGENT_GENERATOR, + response_format = {"type": "json_object"} + ) + self.sequencial_agent = SimpleSequentialAgents( + agents= [self.task_generator, self.agent_generator], + init_message= "" + ) + self.agents = [] + + def create_agent(self, task: str, init_message: str = None) -> list[Agent]: + self.sequencial_agent.agent_manager.init_msg.content = task + agents_list = (self.sequencial_agent.start()).content + + agents_object = create_agents( + agents_list=agents_list["agents"], + agent_factory= self.agent_factory + ) + print(f"Agents are created : {' | '.join([a.name for a in agents_object])}") + manager = AutoAgentManager( + init_message= init_message if init_message is not None else None, + agents= agents_object, + first_agent=agents_object[0], + max_round=2 * len(agents_object), + termination_fn=simple_termination, + termination_word="[#finish#]" + ) + return manager + + + diff --git a/iragent/prompts.py b/iragent/prompts.py index 37b3f10..049460f 100644 --- a/iragent/prompts.py +++ b/iragent/prompts.py @@ -38,4 +38,99 @@ - Write the summary clearly and informatively so future context remains understandable. Only return the summary. Do not explain what you’re doing or include any commentary. +""" + +SMART_PROMPT_WRITER=""" +You are a smart prompt writer who write system_prompt based on input and expected output. +Just write the prompt. + +IMPORTANT: Make the prompts short. +data is like : +input_data: +hello i go shopping and buy some bananas and apples. + +expected_output: +bannas and apples. +""" +SMART_PROMPT_READER=""" +You are a smart prompt evaluator that evaluate the written prompt based on input and output. +So user provide you the prompt and input and output. You find the weakness. +Think general and do not focus only on that input and output. +if the prompt was not good reaturn your feeadback to prompt_maker. +IMPORTANT: Response short. +""" + +TASK_GENERATOR = """ +You are the planner. Your job is to break the user’s main task into smaller, manageable tasks. +Tasks will later be assigned to agents, so design them according to the capabilities of large language models (LLMs). + +Guidelines for Task Creation: + • Break the main task into related subtasks, ensuring the output of each task feeds into the next. + • Avoid tasks that are too large (overly broad) or too small (trivial). + • Ensure all tasks are logically connected and contribute to completing the overall goal. + +Return the tasks as an object with the following structure: +```json +{ + tasks: [ + { + input: "", + output: "", + description:"" + } + ] +} +``` +Rules + • tasks must be an array. + • Each task must contain: + • input – What this task receives as input. + • output – What this task produces as output. + • description – A short, clear explanation of the task’s purpose. + • The sequence of tasks should form a logical workflow. +""" + +AGENT_GENERATOR = """ +You are responsible for creating one agent for each task provided. For each agent, you must define two variables: + 1. name – The agent’s name, based on the task. Use lowercase letters and underscores (_) instead of spaces. Example: word_corrector, page_reader. + 2. system_prompt – The agent’s instruction set, which strictly defines its role. + +VERY IMPORTANT: +Last agent write the response then MUST end it's answer with keyword: [#finish#] + +In writing system prompt +INPUT: +The user will provide tasks in JSON format as follows: +```json +{ + tasks: [ + { + input: "", + output: "", + description:"" + } + ] +} +```josn + +OUTPUT: +You must create an agents array in JSON format, like this: +```json +{ + "agents": [ + { + name: "", + system_prompt: "" + } + ] +} +``` +Rules for Agent Creation + • The agents key is mandatory. + • Agent names must use underscores (_) instead of spaces. + • System prompts must be: + • Very strict — the agent must never perform actions outside the assigned role. + • Focused on one single task only — no explanations or unrelated actions. + • Optionally designed to work step-by-step if it helps execution. + • Do not include any explanations in the output — only perform the task. """ \ No newline at end of file diff --git a/iragent/tools.py b/iragent/tools.py index f754330..363ee2d 100644 --- a/iragent/tools.py +++ b/iragent/tools.py @@ -1,5 +1,6 @@ from datetime import datetime +from .agent import AgentFactory from .message import Message @@ -27,3 +28,15 @@ def simple_termination(word: str, message: Message) -> bool: return True else: return False + +def agent_test(input: str, prompt: str, cfg: dict) -> str: + """ + This function create an agent with that system_prompt and pass the input to this and get the output. + """ + agent_factory = AgentFactory(cfg) + agent = agent_factory.create_agent( + name= "Solo", + system_prompt = prompt + ) + msg = Message(content=input) + return agent.call_message(msg).content diff --git a/iragent/utility.py b/iragent/utility.py index 87e46f1..3b04bc9 100644 --- a/iragent/utility.py +++ b/iragent/utility.py @@ -4,6 +4,8 @@ from bs4 import BeautifulSoup from nltk.tokenize import sent_tokenize, word_tokenize +from .agent import Agent, AgentFactory + def fetch_url(url: str, parser: str = "lxml") -> str: """ @@ -40,3 +42,14 @@ def chunker(text: str, token_limit: int = 512) -> List[str]: chunks.append(" ".join(current_chunk)) return chunks + +def create_agents(agents_list: list[dict], agent_factory: AgentFactory) -> list[Agent]: + agents: list[Agent] = [] + for agent in agents_list: + agents.append( + agent_factory.create_agent( + name=agent["name"], + system_prompt = agent["system_prompt"] + ) + ) + return agents \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 1573a36..31b99ab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "iragent" -version = "0.1.4" +version = "0.1.5" description = "A simple multi-agent framework" authors = [{ name = "Parsa Bakhtiari", email = "spacenavard1@gmail.com" }] readme = "README.md" @@ -21,7 +21,7 @@ requires = ["setuptools", "wheel"] build-backend = "setuptools.build_meta" [tool.ruff] -line-length = 88 # global setting +line-length = 88 [tool.ruff.lint] -extend-select = ["I"] # “isort” import‑sorting rules +extend-select = ["I"]