AI agents are popping up everywhere these days, and there's a solid reason behind it. They're completely changing how we work with software, going way past basic chat systems to become tools that can genuinely take action for us.
You'll find everything from support bots handling return requests to programming helpers that can solve problems across entire codebases. These agents are quickly becoming the bridge that connects people with complicated technology.
Here's what's interesting though: a lot of what people call "agents" really aren't agents in the true sense. They're basically chatbots running on predetermined responses. Today, we're going to create an actual agent that can analyze situations, make choices, and take independent action. The best part is we'll break it down so anyone starting out can understand it completely.
Ready to get started? But before we jump in:
What Actually Defines a Real AI Agent?
What are we talking about when we say "genuine agent"?
The main difference between an actual agent and basic automation comes down to independence and flexible thinking. Here's a way to picture it:
- Workflows work like following a recipe step by step—when you run out of an ingredient, you're stuck.
- Agents function like an experienced chef who can substitute ingredients and adjust the dish based on what's available.
Workflows handle straightforward, predictable tasks really well, but they typically struggle when faced with unpredictable situations or changing requirements, which is where agents really shine.
What We're Going to Create
Using the OpenAI SDK, we'll put together a straightforward stock information agent that can handle questions about stocks and companies. Here's what it will do:
- Get current stock prices through the Yahoo Finance API.
- Look up company executives from stock information.
- Convert company names into their trading symbols.
- Request clarification when questions aren't clear enough.
What transforms this into a genuine agent is how it independently chooses:
- Which function to apply for different questions.
- When to combine several functions in sequence.
- When to reach out to users for additional details.
- How to recover from problems and try alternative methods.
What You'll Need to Get Started
Here's your setup checklist:
- Python version 3.7 or newer.
- An OpenAI API key (grab one from platform.openai.com).
- Some familiarity with Python basics.
Set up a new project folder and add the necessary packages:
mkdir stock-info-agent
cd stock-info-agent
pip install openai yfinance python-dotenv
Create a .env file in your project directory:
OPENAI_API_KEY=your_api_key_here
Building the Agent: A Step-by-Step Walkthrough
Let's construct this agent piece by piece, making sure we understand every part as we go.
1. Setting Up the Agent Class
First, we create our agent class with OpenAI integration:
import json
from typing import Optional, Dict, Any, List
from openai import OpenAI
import yfinance as yf
from dotenv import load_dotenv
import os
load_dotenv()
class StockInfoAgent:
def __init__(self):
self.client = OpenAI(api_key=os.getenv('OPENAI_API_KEY'))
self.conversation_history = []
The conversation_history is crucial – it allows our agent to maintain context across multiple interactions, understanding, for example, that "their CEO" refers to the company discussed earlier.
2. Creating the Tools
Our agent needs tools to interact with the real world. Let's create them:
Stock Price Tool
def get_stock_price(self, ticker_symbol: str) -> Optional[str]:
"""Fetches the current stock price for the given ticker_symbol."""
try:
stock = yf.Ticker(ticker_symbol.upper())
info = stock.info
current_price = info.get('currentPrice') or info.get('regularMarketPrice')
if current_price:
return f"{current_price:.2f} USD"return None
except Exception as e:
print(f"Error fetching stock price: {e}")
return None
This function connects to Yahoo Finance for live stock price data. Pay attention to how it manages errors reliable agents need to handle problems smoothly without breaking down.
CEO Finder Tool
def get_company_ceo(self, ticker_symbol: str) -> Optional[str]:
"""Fetches the name of the CEO for the company associated with the ticker_symbol."""
try:
stock = yf.Ticker(ticker_symbol.upper())
info = stock.info
# Look for CEO in various possible fields
ceo = None
for field in ['companyOfficers', 'officers']:
if field in info:
officers = info[field]
if isinstance(officers, list):
for officer in officers:
if isinstance(officer, dict):
title = officer.get('title', '').lower()
if 'ceo' in title or 'chief executive' in title:
ceo = officer.get('name')
break
return ceo
except Exception as e:
print(f"Error fetching CEO info: {e}")
return None
This tool searches through different data structures to find the CEO, as Yahoo Finance doesn't have a standardized format.
Ticker Symbol Finder Tool
This tool is crucial for handling natural language queries about companies:
def find_ticker_symbol(self, company_name: str) -> Optional[str]:
"""Tries to identify the stock ticker symbol for a given company_name."""
try:
# Use yfinance Lookup to search for the company
lookup = yf.Lookup(company_name)
stock_results = lookup.get_stock(count=5)
if not stock_results.empty:
return stock_results.index[0]
# If no stocks found, try all instruments
all_results = lookup.get_all(count=5)
if not all_results.empty:
return all_results.index[0]
except Exception as e:
print(f"Error searching for ticker: {e}")
return None
This tool allows users to refer to companies by name ("Apple", "Tesla", "that EV company") instead of needing to know ticker symbols.
The Clarification Tool
This is what makes our agent truly interactive, allowing it to ask the user for clarification when needed.
def ask_user_for_clarification(self, question_to_user: str) -> str:
"""Poses the question_to_user to the actual user and returns their typed response."""
print(f"\nAgent needs clarification: {question_to_user}")
response = input("Your response: ")
return response
3. Defining Tools for OpenAI
The agent needs to know what tools are available and how to use them. We define them in OpenAI's function calling format:
def create_tool_definitions(self) -> List[Dict[str, Any]]:
"""Creates OpenAI function calling definitions for the tools."""
return [
{
"type": "function",
"function": {
"name": "get_stock_price",
"description": "Fetches the current stock price for the given ticker symbol",
"parameters": {
"type": "object",
"properties": {
"ticker_symbol": {
"type": "string",
"description": "The stock ticker symbol (e.g., 'AAPL', 'MSFT')"
}
},
"required": ["ticker_symbol"]
}
}
},
# ... (other tool definitions)
]
These definitions are like instruction manuals for the AI they tell it what each tool does and what parameters it needs.
4. The Brain: Processing User Queries
Here's where the magic happens the agent's decision-making loop:
def process_user_query(self, user_query: str) -> str:
"""Processes a user query using the OpenAI API with function calling."""
self.conversation_history.append({"role": "user", "content": user_query})
system_prompt = """You are a helpful stock information assistant. You have access to tools that can:
1. Get current stock prices
2. Find company CEOs
3. Find ticker symbols for company names
4. Ask users for clarification when needed
Use these tools to help answer user questions about stocks and companies.
If information is ambiguous, ask for clarification."""
while True:
messages = [
{"role": "system", "content": system_prompt},
*self.conversation_history
]
# Call OpenAI API with function calling
response = self.client.chat.completions.create(
model="gpt-4-turbo-preview",
messages=messages,
tools=self.create_tool_definitions(),
tool_choice="auto" # Let the model decide which tool to use
)
response_message = response.choices[0].message
# If no tool calls, we're done
if not response_message.tool_calls:
self.conversation_history.append({"role": "assistant", "content": response_message.content})
return response_message.content
# Execute the tool the agent chose
tool_call = response_message.tool_calls[0]
function_name = tool_call.function.name
function_args = json.loads(tool_call.function.arguments)
print(f"\nExecuting tool: {function_name} with args: {function_args}")
# Execute the tool
result = self.execute_tool(function_name, function_args)
# Add everything to conversation history
self.conversation_history.append({
"role": "assistant",
"content": None,
"tool_calls": [{
"id": tool_call.id,
"type": "function",
"function": {
"name": function_name,
"arguments": json.dumps(function_args)
}
}]
})
self.conversation_history.append({
"tool_call_id": tool_call.id,
"role": "tool",
"name": function_name,
"content": str(result) if result is not None else "No result found"
})
The key insight here is the while True loop—the agent keeps thinking and acting until it has a complete answer. It might use one tool, or five, or ask for clarification multiple times. This is true autonomy.
See Our Agent In Action
Here's a real conversation that demonstrates our agent's capabilities very well:
You: Who is the CEO of the EV company from China and what is its stock price?
Executing tool: ask_user_for_clarification with args: {'question_to_user': 'Are you referring to NIO, XPeng, or another Chinese EV company?'}
Agent needs clarification: Are you referring to NIO, XPeng, or another Chinese EV company?
Your response: BYD
Executing tool: find_ticker_symbol with args: {'company_name': 'BYD'}
Executing tool: get_company_ceo with args: {'ticker_symbol': 'BYDDF'}
Executing tool: get_stock_price with args: {'ticker_symbol': 'BYDDF'}
Agent: The CEO of BYD, the Chinese EV company, is Mr. Chuan-Fu Wang, and its current stock price is $59.50 USD.
The agent worked independently by:
- Understanding that "EV company from China" wasn't specific enough.
- Requesting clarification on which exact company.
- Located BYD's trading symbol.
- Pulled up the CEO details.
- Got the latest stock price.
- Put together a comprehensive response.
How to Use the Agent
Running the agent is simple, navigate to the project directory and run the following command:
Try these example queries to see different behaviors:
Simple query:
You: What's Apple's stock price?
Agent: Apple's current stock price is $182.63 USD.
Ambiguous query requiring clarification:
You: Who runs that big EV company?
Agent: Are you asking about the CEO of Tesla?
You: Yes
Agent: The CEO of Tesla is Mr. Elon R. Musk.
Complex multi-tool query:
You: Compare the stock prices of Microsoft and Apple
Agent: Microsoft (MSFT) is currently trading at $415.26 USD, while Apple (AAPL) is trading at $182.63 USD.
Extending Your Agent
The modular design makes it easy to add new capabilities:
Add Market Analysis
def get_price_change(self, ticker_symbol: str, period: str = "1d") -> Dict[str, Any]:
"""Get price change over a period"""
stock = yf.Ticker(ticker_symbol)
hist = stock.history(period=period)
if len(hist) >= 2:
start_price = hist['Close'].iloc[0]
end_price = hist['Close'].iloc[-1]
change = end_price - start_price
percent_change = (change / start_price) * 100
return {
"change": f"${change:.2f}",
"percent": f"{percent_change:.2f}%"}
Add News Integration
def get_company_news(self, ticker_symbol: str) -> List[Dict[str, str]]:
"""Get recent news about the company"""
stock = yf.Ticker(ticker_symbol)
news = stock.news
return [{"title": item['title'], "link": item['link']} for item in news[:5]]
Best Practices for Creating Agents
- Write Clear Function Descriptions: Explain each tool like you're talking to a teammate
- Handle Problems Smoothly: Always plan for API issues and missing information
- Keep Track of Conversations: Store previous exchanges for natural flow
- Be Open with Users: Let them see which functions are running
- Begin with Basics: Only add complicated features when they're actually necessary
For more helpful tips, check out Anthropic's guide.
The Complete Code
import json
from typing import Optional, Dict, Any, List
from openai import OpenAI
import yfinance as yf
from dotenv import load_dotenv
import os
# Load environment variables
load_dotenv()
class StockInfoAgent:
def __init__(self):
self.client = OpenAI(api_key=os.getenv('OPENAI_API_KEY'))
self.conversation_history = []
def get_stock_price(self, ticker_symbol: str) -> Optional[str]:
"""Fetches the current stock price for the given ticker_symbol."""
try:
stock = yf.Ticker(ticker_symbol.upper())
info = stock.info
current_price = info.get('currentPrice') or info.get('regularMarketPrice')
if current_price:
return f"{current_price:.2f} USD"return None
except Exception as e:
print(f"Error fetching stock price: {e}")
return None
def get_company_ceo(self, ticker_symbol: str) -> Optional[str]:
"""Fetches the name of the CEO for the company associated with the ticker_symbol."""
try:
stock = yf.Ticker(ticker_symbol.upper())
info = stock.info
# Look for CEO in various possible fields
ceo = None
for field in ['companyOfficers', 'officers']:
if field in info:
officers = info[field]
if isinstance(officers, list):
for officer in officers:
if isinstance(officer, dict):
title = officer.get('title', '').lower()
if 'ceo' in title or 'chief executive' in title:
ceo = officer.get('name')
break
# Fallback to general company info
if not ceo and 'longBusinessSummary' in info:
ceo = None
return ceo
except Exception as e:
print(f"Error fetching CEO info: {e}")
return None
def find_ticker_symbol(self, company_name: str) -> Optional[str]:
"""Tries to identify the stock ticker symbol for a given company_name."""
try:
# Use yfinance Lookup to search for the company
lookup = yf.Lookup(company_name)
stock_results = lookup.get_stock(count=5)
if not stock_results.empty:
return stock_results.index[0]
# If no stocks found, try all instruments
all_results = lookup.get_all(count=5)
if not all_results.empty:
return all_results.index[0]
except Exception as e:
print(f"Error searching for ticker: {e}")
return None
def ask_user_for_clarification(self, question_to_user: str) -> str:
"""Poses the question_to_user to the actual user and returns their typed response."""
print(f"\nAgent needs clarification: {question_to_user}")
response = input("Your response: ")
return response
def create_tool_definitions(self) -> List[Dict[str, Any]]:
"""Creates OpenAI function calling definitions for the tools."""
return [
{
"type": "function",
"function": {
"name": "get_stock_price",
"description": "Fetches the current stock price for the given ticker symbol",
"parameters": {
"type": "object",
"properties": {
"ticker_symbol": {
"type": "string",
"description": "The stock ticker symbol (e.g., 'AAPL', 'MSFT')"
}
},
"required": ["ticker_symbol"]
}
}
},
{
"type": "function",
"function": {
"name": "get_company_ceo",
"description": "Fetches the name of the CEO for the company associated with the ticker symbol",
"parameters": {
"type": "object",
"properties": {
"ticker_symbol": {
"type": "string",
"description": "The stock ticker symbol"
}
},
"required": ["ticker_symbol"]
}
}
},
{
"type": "function",
"function": {
"name": "find_ticker_symbol",
"description": "Tries to identify the stock ticker symbol for a given company name",
"parameters": {
"type": "object",
"properties": {
"company_name": {
"type": "string",
"description": "The name of the company"
}
},
"required": ["company_name"]
}
}
},
{
"type": "function",
"function": {
"name": "ask_user_for_clarification",
"description": "Poses a question to the user and returns their response",
"parameters": {
"type": "object",
"properties": {
"question_to_user": {
"type": "string",
"description": "The question to ask the user"
}
},
"required": ["question_to_user"]
}
}
}
]
def execute_tool(self, tool_name: str, arguments: Dict[str, Any]) -> Any:
"""Executes the specified tool with given arguments."""
if tool_name == "get_stock_price":
return self.get_stock_price(arguments["ticker_symbol"])
elif tool_name == "get_company_ceo":
return self.get_company_ceo(arguments["ticker_symbol"])
elif tool_name == "find_ticker_symbol":
return self.find_ticker_symbol(arguments["company_name"])
elif tool_name == "ask_user_for_clarification":
return self.ask_user_for_clarification(arguments["question_to_user"])
else:
return None
def process_user_query(self, user_query: str) -> str:
"""Processes a user query using the OpenAI API with function calling."""
# Add user message to conversation history
self.conversation_history.append({"role": "user", "content": user_query})
system_prompt = """You are a helpful stock information assistant. You have access to tools that can:
1. Get current stock prices
2. Find company CEOs
3. Find ticker symbols for company names
4. Ask users for clarification when needed
Use these tools to help answer user questions about stocks and companies. If information is ambiguous, ask for clarification."""
while True:
messages = [
{"role": "system", "content": system_prompt},
*self.conversation_history
]
# Call OpenAI API with function calling
response = self.client.chat.completions.create(
model="gpt-4-turbo-preview",
messages=messages,
tools=self.create_tool_definitions(),
tool_choice="auto"
)
response_message = response.choices[0].message
# If no tool calls, we're done
if not response_message.tool_calls:
self.conversation_history.append({"role": "assistant", "content": response_message.content})
return response_message.content
# Execute the first tool call
tool_call = response_message.tool_calls[0]
function_name = tool_call.function.name
function_args = json.loads(tool_call.function.arguments)
print(f"\nExecuting tool: {function_name} with args: {function_args}")
# Execute the tool
result = self.execute_tool(function_name, function_args)
# Add the assistant's message with tool calls to history
self.conversation_history.append({
"role": "assistant",
"content": None,
"tool_calls": [{
"id": tool_call.id,
"type": "function",
"function": {
"name": function_name,
"arguments": json.dumps(function_args)
}
}]
})
# Add tool result to history
self.conversation_history.append({
"tool_call_id": tool_call.id,
"role": "tool",
"name": function_name,
"content": str(result) if result is not None else "No result found"
})
def chat(self):
"""Interactive chat loop."""
print("Stock Information Agent")
print("Ask me about stock prices, company CEOs, or any stock-related questions!")
print("Type 'quit' to exit.\n")
while True:
user_input = input("You: ")
if user_input.lower() in ['quit', 'exit', 'bye']:
print("Goodbye!")
break
try:
response = self.process_user_query(user_input)
print(f"\nAgent: {response}\n")
except Exception as e:
print(f"\nError: {e}\n")
if __name__ == "__main__":
agent = StockInfoAgent()
agent.chat()
Access Full Code Here: GitHub File
Wrapping Up
You did it! You've created a genuine AI agent that can analyze, choose, and take action on its own. This goes beyond a simple chatbot running scripts. You've built an intelligent system that handles unclear questions, seeks clarification, and connects different tools to tackle complicated tasks.
The main points to remember:
- Real agents figure out their own approach to solving problems
- Straightforward, flexible designs work better than complicated frameworks
- Build with essential tools first and expand only when necessary
- Keeping conversation history and managing errors properly are essential
With what you've learned here, you can create agents for any field, whether that's analyzing finances, helping customers, or building personal helpers. The opportunities are limitless when you give AI the power to think and act independently.
Good luck with your projects, and we're excited to see what you create!