Created
September 24, 2025 10:51
-
-
Save colindotfun/17545898c904de3409160bdebb8d4fe4 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import json | |
| import uuid | |
| from typing import Optional, Dict, Any, List | |
| from openai import OpenAI | |
| import yfinance as yf | |
| from dotenv import load_dotenv | |
| import os | |
| from helicone_helpers import HeliconeManualLogger | |
| # Load environment variables | |
| load_dotenv() | |
| class StockInfoAgent: | |
| def __init__(self): | |
| # Initialize OpenAI client with Helicone | |
| self.client = OpenAI( | |
| api_key=os.getenv('OPENAI_API_KEY'), | |
| base_url="https://oai.helicone.ai/v1", | |
| default_headers={ | |
| "Helicone-Auth": f"Bearer {os.getenv('HELICONE_API_KEY')}" | |
| } | |
| ) | |
| # Initialize Helicone manual logger for tool calls | |
| self.helicone_logger = HeliconeManualLogger( | |
| api_key=os.getenv('HELICONE_API_KEY'), | |
| headers={ | |
| "Helicone-Property-Type": "Stock-Info-Agent", | |
| } | |
| ) | |
| self.conversation_history = [] | |
| self.session_id = None | |
| self.session_headers = {} | |
| def start_new_session(self): | |
| """Initialize a new session for tracking.""" | |
| self.session_id = str(uuid.uuid4()) | |
| self.session_headers = { | |
| "Helicone-Session-Id": self.session_id, | |
| "Helicone-Session-Name": "Stock Information Chat", | |
| "Helicone-Session-Path": "/stock-chat", | |
| "Helicone-Property-Environment": "production" | |
| } | |
| print(f"Started new session: {self.session_id}") | |
| def get_stock_price(self, ticker_symbol: str) -> Optional[str]: | |
| """Fetches the current stock price for the given ticker_symbol with Helicone logging.""" | |
| def price_operation(result_recorder): | |
| try: | |
| stock = yf.Ticker(ticker_symbol.upper()) | |
| info = stock.info | |
| current_price = info.get('currentPrice') or info.get('regularMarketPrice') | |
| if current_price: | |
| result = f"{current_price:.2f} USD" | |
| result_recorder.append_results({ | |
| "ticker": ticker_symbol.upper(), | |
| "price": current_price, | |
| "formatted_price": result, | |
| "status": "success" | |
| }) | |
| return result | |
| else: | |
| result_recorder.append_results({ | |
| "ticker": ticker_symbol.upper(), | |
| "error": "Price not found", | |
| "status": "error" | |
| }) | |
| return None | |
| except Exception as e: | |
| result_recorder.append_results({ | |
| "ticker": ticker_symbol.upper(), | |
| "error": str(e), | |
| "status": "error" | |
| }) | |
| print(f"Error fetching stock price: {e}") | |
| return None | |
| # Log the tool call with Helicone | |
| return self.helicone_logger.log_request( | |
| provider=None, | |
| request={ | |
| "_type": "tool", | |
| "toolName": "get_stock_price", | |
| "input": {"ticker_symbol": ticker_symbol}, | |
| "metadata": { | |
| "source": "yfinance", | |
| "operation": "get_current_price" | |
| } | |
| }, | |
| operation=price_operation, | |
| additional_headers={ | |
| **self.session_headers, | |
| "Helicone-Session-Path": f"/stock-chat/price/{ticker_symbol.lower()}" | |
| } | |
| ) | |
| def get_company_ceo(self, ticker_symbol: str) -> Optional[str]: | |
| """Fetches the name of the CEO for the company with Helicone logging.""" | |
| def ceo_operation(result_recorder): | |
| 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 | |
| result_recorder.append_results({ | |
| "ticker": ticker_symbol.upper(), | |
| "ceo": ceo, | |
| "status": "success" if ceo else "not_found" | |
| }) | |
| return ceo | |
| except Exception as e: | |
| result_recorder.append_results({ | |
| "ticker": ticker_symbol.upper(), | |
| "error": str(e), | |
| "status": "error" | |
| }) | |
| print(f"Error fetching CEO info: {e}") | |
| return None | |
| return self.helicone_logger.log_request( | |
| provider=None, | |
| request={ | |
| "_type": "tool", | |
| "toolName": "get_company_ceo", | |
| "input": {"ticker_symbol": ticker_symbol}, | |
| "metadata": { | |
| "source": "yfinance", | |
| "operation": "get_company_officers" | |
| } | |
| }, | |
| operation=ceo_operation, | |
| additional_headers={ | |
| **self.session_headers, | |
| "Helicone-Session-Path": f"/stock-chat/ceo/{ticker_symbol.lower()}" | |
| } | |
| ) | |
| def find_ticker_symbol(self, company_name: str) -> Optional[str]: | |
| """Tries to identify the stock ticker symbol with Helicone logging.""" | |
| def ticker_search_operation(result_recorder): | |
| 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: | |
| ticker = stock_results.index[0] | |
| result_recorder.append_results({ | |
| "company_name": company_name, | |
| "ticker": ticker, | |
| "search_type": "stock", | |
| "results_count": len(stock_results), | |
| "status": "success" | |
| }) | |
| return ticker | |
| # If no stocks found, try all instruments | |
| all_results = lookup.get_all(count=5) | |
| if not all_results.empty: | |
| ticker = all_results.index[0] | |
| result_recorder.append_results({ | |
| "company_name": company_name, | |
| "ticker": ticker, | |
| "search_type": "all_instruments", | |
| "results_count": len(all_results), | |
| "status": "success" | |
| }) | |
| return ticker | |
| result_recorder.append_results({ | |
| "company_name": company_name, | |
| "error": "No ticker found", | |
| "status": "not_found" | |
| }) | |
| return None | |
| except Exception as e: | |
| result_recorder.append_results({ | |
| "company_name": company_name, | |
| "error": str(e), | |
| "status": "error" | |
| }) | |
| print(f"Error searching for ticker: {e}") | |
| return None | |
| return self.helicone_logger.log_request( | |
| provider=None, | |
| request={ | |
| "_type": "tool", | |
| "toolName": "find_ticker_symbol", | |
| "input": {"company_name": company_name}, | |
| "metadata": { | |
| "source": "yfinance_lookup", | |
| "operation": "ticker_search" | |
| } | |
| }, | |
| operation=ticker_search_operation, | |
| additional_headers={ | |
| **self.session_headers, | |
| "Helicone-Session-Path": f"/stock-chat/search/{company_name.lower().replace(' ', '-')}" | |
| } | |
| ) | |
| 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"] | |
| } | |
| } | |
| } | |
| ] | |
| 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"]) | |
| else: | |
| return None | |
| def process_user_query(self, user_query: str) -> str: | |
| """Processes a user query using the OpenAI API with function calling and Helicone logging.""" | |
| # Add user message to conversation history | |
| self.conversation_history.append({"role": "user", "content": user_query}) | |
| # System prompt to guide the agent's behavior | |
| 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 one at a time 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 | |
| ] | |
| def openai_operation(result_recorder): | |
| # Call OpenAI API with function calling | |
| response = self.client.chat.completions.create( | |
| model="gpt-4o-mini-2024-07-18", | |
| messages=messages, | |
| tools=self.create_tool_definitions(), | |
| tool_choice="auto" | |
| ) | |
| # Log the response | |
| result_recorder.append_results({ | |
| "model": "gpt-4o-mini-2024-07-18", | |
| "response": response.choices[0].message.model_dump(), | |
| "usage": response.usage.model_dump() if response.usage else None | |
| }) | |
| return response | |
| # Log the OpenAI call | |
| response = self.helicone_logger.log_request( | |
| provider="openai", | |
| request={ | |
| "model": "gpt-4o-mini-2024-07-18", | |
| "messages": messages, | |
| "tools": self.create_tool_definitions(), | |
| "tool_choice": "auto" | |
| }, | |
| operation=openai_operation, | |
| additional_headers={ | |
| **self.session_headers, | |
| "Helicone-Prompt-Id": "stock-agent-reasoning" | |
| } | |
| ) | |
| 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 (this will be logged separately by each tool method) | |
| 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 with session tracking.""" | |
| print("Stock Information Agent with Helicone Monitoring") | |
| print("Ask me about stock prices, company CEOs, or any stock-related questions!") | |
| print("Type 'quit' to exit.\n") | |
| # Start a new session | |
| self.start_new_session() | |
| 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() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment