24Arys11's picture
adjusted prompts; bug fix: logger file handling encoding; improved logging
d426005
from llama_index.core.tools import FunctionTool
from llama_index.core.workflow import Context
import logging
import os
import re
from typing import List
from args import Args, LLMInterface
from llm_factory import LLMFactory
from llama_index.core.agent.workflow import AgentWorkflow
class IAgent():
def __init__(self, temperature, max_tokens, sys_prompt_filename, llm_itf: LLMInterface):
self.name = self._format_name(sys_prompt_filename)
self.temperature, self.max_tokens = temperature, max_tokens
# Load the system prompt from a file
system_prompt_path = os.path.join(os.getcwd(), "system_prompts", sys_prompt_filename)
self.system_prompt = ""
with open(system_prompt_path, "r") as file:
self.system_prompt = file.read().strip()
# Initialize the tool agents
self.tools = self.setup_tools()
self.slaves: List[IAgent] = self.setup_slaves()
# Define the LLM and agent
self.llm = LLMFactory.create(llm_itf, self.system_prompt, temperature, max_tokens)
self.agent = self._setup_agent()
self.ctx = Context(self.agent)
@staticmethod
def _format_name(sys_prompt_filename: str) -> str:
# Remove file extension
name_without_ext = os.path.splitext(sys_prompt_filename)[0]
# Remove numbers and special characters from the beginning
cleaned_name = re.sub(r'^[^a-zA-Z]+', '', name_without_ext)
return cleaned_name
def setup_tools(self) -> List[FunctionTool]:
"""
Set up the tools for this agent.
Override this method in subclasses to define custom tools.
By default, returns an empty list.
Returns:
List: A list of tools this agent can use
"""
return []
def setup_slaves(self) -> List:
"""
Set up the slave agents for this agent.
Override this method in subclasses to define custom sub-agents.
By default, returns an empty list.
Returns:
List: A list of slave agents this agent can use
"""
return []
def _setup_agent(self) -> AgentWorkflow:
"""
Initializes and returns an agent workflow based on the presence of tools and slaves.
If both `self.tools` and `self.slaves` are empty, it sets up a default agent using the provided language model (`self.llm`).
Otherwise, it creates an agent workflow using the combined list of tools and slaves with the language model.
Returns:
AgentWorkflow: An instance of the agent workflow configured with the appropriate tools and language model.
"""
# Create tools from slaves: each tool calls slave.query(question) asynchronously
slave_tools = []
for slave in self.slaves:
slave_tool = FunctionTool.from_defaults(
name=f"call_{slave.name}",
description=f"Calls agent {slave.name} with a given query.",
fn=slave.query
)
slave_tools.append(slave_tool)
self.tools.extend(slave_tools)
return AgentWorkflow.from_tools_or_functions(
self.tools,
llm=self.llm
)
def get_system_prompt(self) -> str:
"""
Retrieves the system prompt.
Returns:
str: The system prompt string.
"""
return self.system_prompt
async def query(self, question: str, has_context = True) -> str:
"""
Asynchronously queries the agent with a given question and returns the response.
Args:
question (str): The question to be sent to the agent.
Returns:
str: The response from the agent as a string.
"""
if Args.LOGGER is None:
raise RuntimeError("LOGGER must be defined before querying the agent.")
separator = "=============================="
Args.LOGGER.log(logging.INFO, f"\n{separator}\nAgent '{self.name}' has been queried !\nINPUT:\n{question}\n")
if has_context:
response = await self.agent.run(question, ctx=self.ctx)
else:
response = await self.agent.run(question)
response = str(response)
Args.LOGGER.log(logging.INFO, f"\nAgent '{self.name}' produced OUTPUT:\n{response}\n{separator}\n")
return response
def clear_context(self):
"""
Clears the current context of the agent, resetting any conversation history.
This is useful when starting a new conversation or when the context needs to be refreshed.
"""
if self.ctx is not None:
self.ctx = Context(self.agent)
if not self.slaves:
return
for slave in self.slaves:
slave.clear_context()