Autonomous Agents
Autonomous agents represent a significant advancement in AI system design, combining the power of language models with the ability to perform tasks autonomously. This tutorial will guide you through building an autonomous agent using Synalinks, capable of processing mathematical queries and returning numerical answers.
Understanding the Foundation
Autonomous agents address a fundamental limitation of traditional systems by enabling them to perform tasks without constant human intervention. While language models excel at generating coherent text, they often require additional tools and logic to perform specific tasks autonomously. Autonomous agents bridge this gap by dynamically processing information and executing tasks based on predefined tools.
The architecture of an autonomous agent follows several core stages. The input stage captures the user's query or command. The processing stage uses predefined tools and logic to process the input and generate a response. Finally, the output stage returns the result to the user.
Synalinks streamlines this complex process through its modular architecture, allowing you to compose components with precision while maintaining flexibility for different use cases.
Understanding Autonomous Agent Architecture
Synalinks simplifies the implementation of autonomous agents through its modular architecture, allowing you to compose components with precision and flexibility.
The foundation of any autonomous agent begins with defining your data models. These models structure how information flows through your pipeline and ensure consistency across components.
import synalinks
import asyncio
import uuid
synalinks.enable_logging()
class Query(synalinks.DataModel):
query: str = synalinks.Field(
description="The user query",
)
class NumericalFinalAnswer(synalinks.DataModel):
final_answer: float = synalinks.Field(
description="The correct final numerical answer",
)
@synalinks.utils.register_synalinks_serializable()
async def calculate(expression: str):
"""Calculate the result of a mathematical expression.
Args:
expression (str): The mathematical expression to calculate, such as
'2 + 2'. The expression can contain numbers, operators (+, -, *, /),
parentheses, and spaces.
"""
if not all(char in "0123456789+-*/(). " for char in expression):
return {
"result": None,
"log": (
"Error: invalid characters in expression. "
"The expression can only contain numbers, operators (+, -, *, /),"
" parentheses, and spaces NOT letters."
),
}
try:
# Evaluate the mathematical expression safely
result = round(float(eval(expression, {"__builtins__": None}, {})), 2)
return {
"result": result,
"log": "Successfully executed",
}
except Exception as e:
return {
"result": None,
"log": f"Error: {e}",
}
async def main():
language_model = synalinks.LanguageModel(
model="ollama/mistral",
)
tools = [
synalinks.Tool(calculate),
]
inputs = synalinks.Input(data_model=Query)
outputs = await synalinks.FunctionCallingAgent(
data_model=NumericalFinalAnswer,
tools=tools,
language_model=language_model,
max_iterations=5,
return_inputs_with_trajectory=True,
autonomous=True,
)(inputs)
agent = synalinks.Program(
inputs=inputs,
outputs=outputs,
name="math_agent",
description="A math agent",
)
input_query = Query(query="How much is 152648 + 485?")
response = await agent(input_query)
print(response.prettify_json())
if __name__ == "__main__":
asyncio.run(main())
Result
{
"query": "How much is 152648 + 485?",
"messages": [
{
"role": "assistant",
"content": "I will calculate the sum of the given numbers.",
"tool_call_id": null,
"tool_calls": [
{
"id": "79e1bd1f-d7b3-4807-9508-1fbd2e1365ac",
"name": "calculate",
"arguments": {
"expression": "152648 + 485"
}
}
]
},
{
"role": "tool",
"content": {
"result": 153133.0,
"log": "Successfully executed"
},
"tool_call_id": "79e1bd1f-d7b3-4807-9508-1fbd2e1365ac",
"tool_calls": []
},
{
"role": "assistant",
"content": "Upon observing the input, I note that it contains a mathematical expression '152648 + 485'. I have already executed a tool call to calculate this sum in the previous step. To avoid unnecessary repetition and maintain efficiency, I will return an empty tool calls array.",
"tool_call_id": null,
"tool_calls": []
}
],
"final_answer": 153133
}
The Query
and NumericalFinalAnswer
data models serve as the input and output contracts for your autonomous agent. The Query
model captures user questions, while the NumericalFinalAnswer
model structures the system's responses.
This explicit modeling ensures type safety and makes your pipeline's behavior predictable.
The calculate function represents the core tool that the agent uses to perform mathematical calculations. It evaluates a mathematical expression and returns the result, ensuring that the agent can process numerical queries accurately.
The FunctionCallingAgent
component processes the user's query using the predefined tools and logic. It returns the result of the calculation and maintains the original input for downstream processing.
Key Takeaways
- Autonomous Task Execution: Autonomous agents solve the fundamental problem of performing tasks without constant human intervention, enabling systems to process information and execute tasks dynamically.
- Synalinks Modular Implementation: The framework simplifies the development of autonomous agents through composable components like
FunctionCallingAgent
, allowing you to build sophisticated pipelines with clear data flow and maintainable architecture. - Explicit Data Model Contracts: Using structured
Query
andNumericalFinalAnswer
models ensures type safety and predictable behavior throughout your pipeline, preventing data inconsistencies and enabling reliable processing across all components. - Tool Integration: The tutorial demonstrates how to integrate tools like the calculate function into your autonomous agent, providing a robust foundation for processing specific types of queries.
- Dynamic Processing: The example shows how autonomous agents can dynamically process information and execute tasks based on predefined tools and logic, enabling them to perform complex operations autonomously.