Interactive Agent
Interactive Agents
Interactive agents represent a significant advancement in AI system design, enabling dynamic interactions with users through iterative processing and validation of inputs. This tutorial will guide you through building an interactive agent using Synalinks, capable of processing mathematical queries and returning numerical answers through a series of interactions.
Understanding the Foundation
Interactive agents address the need for dynamic and iterative processing of user inputs, allowing for more flexible and responsive AI systems. Unlike autonomous agents, interactive agents require user validation at each step, ensuring accuracy and relevance in their responses.
The architecture of an interactive 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.
- The validation stage requires user input to validate the tool calls and their arguments.
- The output stage returns the result to the user.
sequenceDiagram
participant User
participant Agent
participant Tool
User->>Agent: Query
Agent->>User: Propose tool call
User->>Agent: Validate/Edit args
Agent->>Tool: Execute
Tool-->>Agent: Result
Agent->>User: Response
Note over User,Agent: Repeat until done
Interactive agents are transforming the landscape of AI by facilitating dynamic and iterative interactions with users.
Exploring Interactive Agent Architecture
Synalinks offers a streamlined approach to building interactive agents, thanks to its modular and flexible architecture. Each user interaction initiates a new cycle in the Directed Acyclic Graph (DAG), and tool calls are executed only after receiving user validation.
In non-autonomous mode (also called human in the loop or interactive mode), the
user needs to validate/edit the tool arguments and send it back to the agent. In
this mode, the agent requires a ChatMessages data model as input and outputs
ChatMessages back to the user (containing both tool results and assistant
response). The agent ignores the max_iterations argument, as it will only
perform one step at a time.
Creating an Interactive Agent
Define tools as async functions with complete docstrings. Important Tool Constraints:
-
No Optional Parameters: All parameters must be required. LLM providers require all tool parameters in their JSON schemas.
-
Complete Docstring Required: Every parameter must be documented in the
Args:section. The Tool extracts descriptions to build the JSON schema.
Use ChatMessages as input and set autonomous=False:
inputs = synalinks.Input(data_model=synalinks.ChatMessages)
outputs = await synalinks.FunctionCallingAgent(
tools=tools,
language_model=language_model,
return_inputs_with_trajectory=True,
autonomous=False, # Human-in-the-loop mode
)(inputs)
Running the Conversation Loop
Process messages iteratively, validating tool calls at each step:
input_messages = synalinks.ChatMessages(
messages=[synalinks.ChatMessage(role="user", content="Calculate 2 + 2")]
)
for iteration in range(MAX_ITERATIONS):
response = await agent(input_messages)
assistant_message = response.get("messages")[-1]
tool_calls = assistant_message.get("tool_calls")
if not tool_calls:
print(assistant_message.get("content")) # Final response
break
# Display tool calls for user validation
for tool_call in tool_calls:
print(f"Tool: {tool_call.get('name')}({tool_call.get('arguments')})")
# In a real app: let user approve/modify/reject here
# After validation, append and continue
input_messages.messages.append(assistant_message)
Key Takeaways
- Dynamic Interaction: Interactive agents facilitate dynamic and iterative processing of user inputs.
- Modular Design: Synalinks modular architecture simplifies the development of interactive agents.
- Structured Data Models: The use of structured ChatMessages models ensures consistency and predictability.
- Tool Integration and Validation: Integration of tools and validation of their arguments provide a robust foundation.
- User-Driven Processing: Interactive agents dynamically process information and execute tasks based on user interactions.
Program Visualization
API References
calculate(expression)
async
Perform calculations based on a mathematical expression.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
expression
|
str
|
The mathematical expression to calculate, such as '2 + 2'. The expression can contain numbers, operators (+, -, *, /), parentheses, and spaces. |
required |
Source code in examples/11_interactive_agent.py
Source
import asyncio
from dotenv import load_dotenv
import synalinks
# Activate logging for monitoring interactions
synalinks.enable_logging()
MAX_ITERATIONS = 5
# Define the calculation tool
@synalinks.utils.register_synalinks_serializable()
async def calculate(expression: str):
"""Perform calculations based on a mathematical expression.
Args:
expression (str): The mathematical expression to calculate, such as
'2 + 2'. The expression can contain numbers, operators (+, -, *, /),
parentheses, and spaces.
"""
# Check for valid characters in the expression
if not all(char in "0123456789+-*/(). " for char in expression):
return {
"result": None,
"log": (
"Invalid characters detected in the expression. "
"Only numbers, operators (+, -, *, /), parentheses, and spaces "
"are allowed."
),
}
try:
# Safely evaluate the mathematical expression
result = round(float(eval(expression, {"__builtins__": None}, {})), 2)
return {
"result": result,
"log": "Calculation successful",
}
except Exception as e:
return {
"result": None,
"log": f"Calculation error: {e}",
}
async def main():
load_dotenv()
# Enable observability for tracing
synalinks.enable_observability(
tracking_uri="http://localhost:5000",
experiment_name="interactive_math_agent",
)
# Initialize the language model
language_model = synalinks.LanguageModel(
model="gemini/gemini-3.1-flash-lite-preview",
)
# Define the tools available to the agent
tools = [
synalinks.Tool(calculate),
]
# ==========================================================================
# Create the interactive agent
# ==========================================================================
print("Creating the interactive agent...")
# Set up the input structure using ChatMessages
inputs = synalinks.Input(data_model=synalinks.ChatMessages)
# Create the interactive agent
# In non-autonomous mode, the agent returns ChatMessages containing both
# tool results and assistant response
outputs = await synalinks.FunctionCallingAgent(
tools=tools,
language_model=language_model,
return_inputs_with_trajectory=True,
autonomous=False,
)(inputs)
# Define the agent program
agent = synalinks.Program(
inputs=inputs,
outputs=outputs,
name="interactive_math_agent",
description="An agent designed to handle mathematical queries interactively",
)
synalinks.utils.plot_program(
agent,
to_folder="examples",
show_module_names=True,
show_trainable=True,
show_schemas=True,
)
# ==========================================================================
# Run the interactive conversation
# ==========================================================================
print("Running the interactive agent...")
# Initialize the conversation with a user query
input_messages = synalinks.ChatMessages(
messages=[
synalinks.ChatMessage(
role="user",
content="Calculate the sum of 152648 and 485.",
)
]
)
# Process the conversation through multiple iterations
for iteration in range(MAX_ITERATIONS):
print(f"\n--- Iteration {iteration + 1} ---")
response = await agent(input_messages)
# The response contains all messages including tool results
# The last message is the assistant response
assistant_message = response.get("messages")[-1]
# Check if the agent wants to call tools
tool_calls = assistant_message.get("tool_calls")
if not tool_calls:
# No tool calls - the agent is done and has a final response
print("\nAgent final response:")
print(f" {assistant_message.get('content')}")
print("\nConversation complete.")
break
# =======================================================================
# Human-in-the-loop validation step
# In a real application, you would present these tool calls to the user
# via a UI or CLI and let them approve, modify, or reject them.
# =======================================================================
print("\nAgent wants to call the following tools:")
for i, tool_call in enumerate(tool_calls):
tool_name = tool_call.get("name")
tool_args = tool_call.get("arguments")
print(f" [{i + 1}] {tool_name}({tool_args})")
# Simulate user validation (in a real app, this would be interactive)
print("\n[Simulated] User validates the tool calls...")
print("[Simulated] Proceeding with execution...")
# After validation, append the assistant message with tool_calls
# The agent will execute the tools and continue in the next iteration
input_messages.messages.append(assistant_message)
if __name__ == "__main__":
asyncio.run(main())
