Agent Types
Enthusiast provides a flexible and extensible agent system built on abstract base classes that define the core architecture and capabilities. The system supports multiple agent types, each designed for different use cases and reasoning strategies.
Overview
The agent system in Enthusiast is built on a foundation of abstract base classes that provide:
- Standardized Interface: Common methods and properties all agents must implement
- Configuration Management: Runtime configuration through structured schemas
- Tool Integration: Seamless integration with the tool system
- Memory Management: Built-in conversation memory and context management
- Extensibility: Easy creation of custom agent types
Base Agent
Core Components
Every agent in Enthusiast consists of:
- Language Model: The underlying LLM for reasoning and text generation
- Tools: Collection of tools the agent can use
- Prompt Template: Instructions and context for the agent
- Injector: Access to external resources and services
- Callback Handlers(Optional): Handling LLM and agent events
Core Interface
The BaseAgent
class defines the fundamental interface all agents must implement:
Required Class Variables
The ExtraArgsClassBaseMeta
metaclass enforces that all agent implementations define:
AGENT_ARGS
(RequiredFieldsModel)
Configuration schema for agent-specific parameters:
class AgentConfiguration(RequiredFieldsModel):
max_iterations: int = Field(title="Max Iterations", description="Maximum reasoning steps", default=10)
enable_debugging: bool = Field(title="Enable Debugging", description="Enable detailed logging", default=False)
class ExampleAgent(BaseAgent):
AGENT_ARGS = AgentConfiguration
PROMPT_INPUT
(RequiredFieldsModel)
Schema for prompt input variables:
class PromptInputSchema(RequiredFieldsModel):
product_type: str = Field(title="Product type", description="Product type agent with work with")
context: str = Field(title="Context", description="Additional context information")
output_format: str = Field(title="Output Format", description="Expected response format")
class ExampleAgent(BaseAgent):
PROMPT_INPUT = PromptInputSchema
PROMPT_EXTENSION
(RequiredFieldsModel)
Schema for prompt extension variables:
class PromptExtensionSchema(RequiredFieldsModel):
system_instructions: str = Field(title="System Instructions", description="Agent behavior instructions")
restrictions: str = Field(title="Restriction instructions", description="Restricted agent behaviours")
class ExampleAgent(BaseAgent):
PROMPT_EXTENSION = PromptExtensionSchema
TOOLS
(list[BaseTool])
List of tools configurations:
class ExampleAgent(BaseAgent):
TOOLS = [
LLMToolConfig(tools_class=ExampleTool),
LLMToolConfig(tools_class=ExampleTool),
]
Those arguments can be accessed by agent like this:
def get_answer(self, input_text: str) -> str:
agent_output = self._agent_executor.invoke(
{"input": input_text, "product_type": self.PROMPT_INPUT.product_type}
)
return agent_output["output"]
ReAct Agent
Overview
The BaseReActAgent
implements the ReAct (Reasoning + Acting) pattern, which enables agents to:
- Reason: Think through problems step by step
- Act: Use tools to gather information or perform actions
- Observe: Process tool results and continue reasoning
- Iterate: Repeat the process until a solution is reached
It also comes with attached ReAct-style output parser and structured tools description, which allows to use it with multiple input arguments tools with ease.
Implementation
class BaseReActAgent(BaseAgent):
def get_answer(self, input_text: str) -> str:
# Build and execute the agent
agent_executor = self._build_agent_executor()
response = agent_executor.invoke(
{"input": input_text},
config=self._build_invoke_config()
)
return response["output"]
def _build_tools(self) -> list[BaseTool]:
"""Convert internal tools to LangChain tools"""
return [tool.as_tool() for tool in self._tools]
def _build_memory(self) -> BaseMemory:
"""Use limited memory for ReAct reasoning"""
return self._injector.chat_limited_memory
def _build_invoke_config(self) -> dict[str, Any]:
"""Configure callback handlers"""
if self._callback_handler:
return {"callbacks": [self._callback_handler]}
return {}
def _build_agent_executor(self) -> AgentExecutor:
"""Create the LangChain agent executor"""
tools = self._build_tools()
agent = create_react_agent(
tools=tools,
llm=self._llm,
prompt=self._prompt,
tools_renderer=render_text_description_and_args,
output_parser=StructuredReActOutputParser(),
)
return AgentExecutor(agent=agent, tools=tools, memory=self._build_memory())
ReAct Reasoning Process
The ReAct agent follows a structured reasoning process:
- Input Analysis: Parse and understand the user's request
- Reasoning: Think through the problem step by step
- Action Selection: Choose appropriate tools to use
- Tool Execution: Execute selected tools with parameters
- Observation: Process tool results and observations
- Iteration: Continue reasoning based on new information
- Final Answer: Provide a comprehensive response
Structured Output Parsing
The ReAct agent uses a structured output parser that expects:
{
"action": "tool_name",
"action_input": {
"parameter1": "value1",
"parameter2": "value2"
}
}
Or for final answers:
Final Answer: The comprehensive response to the user's question
Summary
The Enthusiast agent system provides a robust foundation for building intelligent, tool-using agents:
- BaseAgent: Abstract base class with required interface and validation
- BaseReActAgent: Concrete implementation of the ReAct reasoning pattern
By following the established patterns and implementing the required interfaces, developers can create powerful, specialized agents that leverage the full capabilities of the Enthusiast framework.