diff --git a/.gitignore b/.gitignore index 485dee6..85c0cf8 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ .idea +**/__pycache__/** +**/.env \ No newline at end of file diff --git a/examples/timezone-agent/.env.example b/examples/timezone-agent/.env.example new file mode 100644 index 0000000..edde74d --- /dev/null +++ b/examples/timezone-agent/.env.example @@ -0,0 +1,13 @@ +# Arthur Configuration +# Your Arthur instance URL (e.g., https://app.arthur.ai or your self-hosted instance) +ARTHUR_BASE_URL=http://localhost:3030 + +# Your Arthur API key (get this from your Arthur dashboard) +ARTHUR_API_KEY=your_arthur_api_key_here + +# Your Arthur Task ID (you should have created this task already) +ARTHUR_TASK_ID=your_task_id_here + +# OpenAI Configuration +# Your OpenAI API key +OPENAI_API_KEY=your_openai_api_key_here \ No newline at end of file diff --git a/examples/timezone-agent/README.md b/examples/timezone-agent/README.md new file mode 100644 index 0000000..b0dcc80 --- /dev/null +++ b/examples/timezone-agent/README.md @@ -0,0 +1,248 @@ +# Timezone Agent with OpenInference Tracing + +A simple tutorial demonstrating how to set up OpenInference tracing with a LangChain agent and send traces to Arthur. This tutorial includes timezone conversion tools to demonstrate tool usage. + +## What This Tutorial Covers + +1. **Setting up OpenInference tracing** with Arthur endpoint +2. **Instrumenting a LangChain agent** with OpenInference +3. **Injecting Arthur task metadata** into traces +4. **Running a chat agent with timezone tools** that sends traces to Arthur + +## Prerequisites + +- Python 3.12+ +- An [Arthur account](https://platform.arthur.ai/signup?utm_source=github&utm_medium=readme) with API access +- An [OpenAI API key](https://platform.openai.com/settings/organization/api-keys) +- A pre-created Arthur task (you should have the task ID ready) + +## Setup + +### 1. Install Dependencies + +```bash +# From the multiagent-playground root directory +pip install -e . +``` + +### 2. Configure Environment Variables + +Copy the example environment file and fill in your credentials: + +```bash +cd tutorial +cp env.example .env +``` + +Edit `.env` with your actual values: + +```bash +# Arthur Configuration +ARTHUR_BASE_URL=https://app.arthur.ai # or your self-hosted instance +ARTHUR_API_KEY=your_arthur_api_key_here +ARTHUR_TASK_ID=your_task_id_here # ensure that your task is marked as agentic: `is_agentic=True` + +# OpenAI Configuration +OPENAI_API_KEY=your_openai_api_key_here +``` + +### 3. Run the Tutorial + +```bash +python app.py +``` + +## Available Tools + +The agent comes with two timezone-related tools: + +### 1. Current Time Tool +Get the current time in any timezone or city. + +**Examples:** +- "What is the time in Zurich right now?" +- "What time is it in California?" +- "Current time in EST" + +### 2. Timezone Conversion Tool +Convert times between different timezones. + +**Examples:** +- "What is 10pm EDT in California?" +- "Convert 2:30pm EST to Tokyo time" +- "What time is 9am in London when it's 3pm in New York?" + +## Supported Timezones and Cities + +The tools support a wide range of timezones and cities: + +**US Timezones:** EST, EDT, CST, CDT, MST, MDT, PST, PDT +**US Cities/States:** California, New York, Chicago, Denver, etc. + +**International Cities:** Zurich, London, Paris, Tokyo, Beijing, Sydney, etc. +**Countries:** Japan, Germany, France, Australia, Brazil, etc. + +**Common Abbreviations:** UTC, GMT, JST, IST, CET, BST, etc. + +## File Structure + +``` +tutorial/ +├── app.py # Main application with agent setup +├── timezone_tools.py # Timezone functions and LangChain tools +├── timezone_shortcuts.json # Timezone shortcuts and mappings +├── requirements.txt # Python dependencies +├── env.example # Environment variables template +└── README.md # This file +``` + +## How It Works + +### 1. Setting Up Tracing + +The tutorial sets up OpenTelemetry tracing and configures an OTLP exporter to send spans to Arthur. The LangChain agent is instrumented to automatically create spans for all interactions. + +```python +def setup_tracing(): + # 1. Set up OpenTelemetry + tracer_provider = trace_sdk.TracerProvider() + trace_api.set_tracer_provider(tracer_provider) + + # 2. Configure OTLP exporter to send to Arthur + arthur_base_url = os.getenv("ARTHUR_BASE_URL") + bearer_token = os.getenv("ARTHUR_API_KEY") + + otlp_endpoint = f"{arthur_base_url}/v1/traces" + headers = {"Authorization": f"Bearer {bearer_token}"} + + otlp_exporter = OTLPSpanExporter( + endpoint=otlp_endpoint, + headers=headers + ) + + # 3. Add span processor to send spans to Arthur + tracer_provider.add_span_processor( + SimpleSpanProcessor(otlp_exporter) + ) + + # 4. Instrument LangChain + LangChainInstrumentor().instrument() +``` + +### 2. Creating Tools + +The tools are defined in `timezone_tools.py` using LangChain's `@tool` decorator: + +```python +@tool +def get_current_time(timezone: str) -> str: + """Get the current time in a specific timezone or city.""" + return current_time(timezone) + +@tool +def convert_time_between_zones(time_str: str, from_timezone: str, to_timezone: str) -> str: + """Convert a time from one timezone to another.""" + return convert_timezone(time_str, from_timezone, to_timezone) +``` + +### 3. Arthur Integration + +The agent injects Arthur task metadata into all traces, allowing you to view and analyze the interactions in your Arthur dashboard. + +### 4. Injecting Arthur Task Metadata + +Arthur task metadata is automatically injected via the TracerProvider Resource during setup: + +```python +def setup_tracing(): + # Create resource with Arthur task metadata + arthur_task_id = os.getenv("ARTHUR_TASK_ID") + resource = Resource.create({ + "arthur.task": arthur_task_id, + "service.name": "multiagent-playground-tutorial" + }) + + tracer_provider = trace_sdk.TracerProvider(resource=resource) + trace_api.set_tracer_provider(tracer_provider) + # ... rest of setup +``` + +## Key Concepts + +### OpenInference Instrumentation + +- **LangChainInstrumentor**: Automatically instruments LangChain components to create spans +- **Resource**: Embeds metadata (like Arthur task ID) into all traces automatically +- **OTLPSpanExporter**: Sends spans to Arthur via OTLP protocol + +### Arthur Integration + +- **Task ID**: Links all traces to a specific Arthur task +- **Metadata**: Allows you to categorize and filter traces in Arthur +- **Real-time**: Spans are sent to Arthur as they're created + +### Tool Usage + +- **Tool Calls**: Each tool invocation creates a separate span in Arthur +- **Input/Output**: Tool inputs and outputs are captured in the traces +- **Error Handling**: Tool errors are also captured and visible in Arthur + +## What You'll See + +1. **Console Output**: The agent will respond to your questions and use tools when needed +2. **Arthur Dashboard**: Traces will appear in your Arthur task with: + - LLM calls and responses + - Tool invocations (timezone conversions) + - Agent execution steps + - Timing information + +## Example Conversations + +``` +You: What time is it in Zurich right now? +Assistant: Let me check the current time in Zurich for you. +[Tool: get_current_time] +[Tool Result: 2024-12-19 15:30:45 ZURICH] +The current time in Zurich is 2024-12-19 15:30:45 ZURICH. + +You: What is 10pm EDT in California? +Assistant: I'll convert 10pm EDT to California time for you. +[Tool: convert_time_between_zones] +[Tool Result: 2024-12-19 19:00:00 CALIFORNIA] +10pm EDT is 7:00 PM in California (PST). +``` + +## Troubleshooting + +### Common Issues + +1. **"ARTHUR_BASE_URL and ARTHUR_API_KEY must be set"** + - Make sure your `.env` file exists and has the correct values + +2. **"ARTHUR_TASK_ID must be set"** + - Ensure you have created a task in Arthur and copied the task ID + +3. **Connection errors** + - Check that your Arthur URL is correct + - Verify your API key is valid + - Ensure network connectivity + +4. **Tool errors** + - Check that the timezone names are valid + - Ensure time formats are correct (e.g., "10pm", "14:30") + +## Next Steps + +After running this tutorial, you can: + +1. **Add more tools** to your agent and see how they appear in traces +2. **Customize metadata** to include additional context +3. **Scale up** to more complex agent architectures +4. **Analyze traces** in the Arthur dashboard for performance insights + +## Resources + +- [OpenInference Documentation](https://openinference.io/) +- [Arthur Documentation](https://docs.arthur.ai/) +- [LangChain Documentation](https://python.langchain.com/) +- [OpenTelemetry Python](https://opentelemetry.io/docs/languages/python/) \ No newline at end of file diff --git a/examples/timezone-agent/app.py b/examples/timezone-agent/app.py new file mode 100644 index 0000000..1da35ee --- /dev/null +++ b/examples/timezone-agent/app.py @@ -0,0 +1,178 @@ +""" +Simple OpenInference Tracing Tutorial with LangChain + +This tutorial shows how to: +1. Set up OpenInference tracing with Arthur +2. Instrument a LangChain agent +3. Inject Arthur task metadata via TracerProvider Resource +4. Use timezone conversion tools +""" + +import os +from dotenv import load_dotenv +from langchain_openai import ChatOpenAI +from langchain.agents import create_openai_tools_agent, AgentExecutor + +from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder +from openinference.instrumentation.langchain import LangChainInstrumentor +from opentelemetry import trace as trace_api +from opentelemetry.sdk import trace as trace_sdk +from opentelemetry.sdk.trace.export import SimpleSpanProcessor +from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter +from opentelemetry.sdk.resources import Resource + +# Import our timezone tools from the combined module +from timezone_tools import get_current_time, convert_time_between_zones +from validate_env import validate_environment + +# Load environment variables +load_dotenv() + +def setup_tracing(): + """Set up OpenInference tracing with Arthur endpoint.""" + + # 1. Set up OpenTelemetry with Arthur task metadata in Resource + arthur_task_id = os.getenv("ARTHUR_TASK_ID") + if not arthur_task_id: + raise ValueError("ARTHUR_TASK_ID must be set in .env file") + + # Create resource with Arthur task metadata + resource = Resource.create({ + "arthur.task": arthur_task_id, + "service.name": "multiagent-playground-tutorial" + }) + + tracer_provider = trace_sdk.TracerProvider(resource=resource) + trace_api.set_tracer_provider(tracer_provider) + + # 2. Configure OTLP exporter to send to Arthur + arthur_base_url = os.getenv("ARTHUR_BASE_URL") + bearer_token = os.getenv("ARTHUR_API_KEY") + + if not arthur_base_url or not bearer_token: + raise ValueError("ARTHUR_BASE_URL and ARTHUR_API_KEY must be set in .env file") + + # Create OTLP exporter pointing to Arthur + otlp_endpoint = f"{arthur_base_url}/v1/traces" + headers = {"Authorization": f"Bearer {bearer_token}"} + + otlp_exporter = OTLPSpanExporter( + endpoint=otlp_endpoint, + headers=headers + ) + + # 3. Add span processor to send spans to Arthur + tracer_provider.add_span_processor( + SimpleSpanProcessor(otlp_exporter) + ) + + # 4. Instrument LangChain + LangChainInstrumentor().instrument() + + print(f"✅ Tracing set up successfully") + print(f" Endpoint: {otlp_endpoint}") + print(f" Task ID: {arthur_task_id}") + +def create_simple_agent(): + """Create a LangChain agent with timezone tools.""" + + # Initialize the model + model = ChatOpenAI( + model="gpt-4o", + temperature=0, + api_key=os.getenv("OPENAI_API_KEY") + ) + + # Create tools list + tools = [get_current_time, convert_time_between_zones] + + # Create a proper prompt template with system and user messages + prompt = ChatPromptTemplate.from_messages([ + ("system", """You are a helpful assistant with timezone conversion capabilities. + +You can: +1. Get the current time in any timezone or city using the get_current_time tool +2. Convert times between different timezones using the convert_time_between_zones tool + +When users ask about times, use the appropriate tool to provide accurate information. +For example: +- "What time is it in Zurich?" -> use get_current_time +- "What is 10pm EDT in California?" -> use convert_time_between_zones + +Always provide clear, helpful responses with the time information. Be conversational and helpful."""), + MessagesPlaceholder("chat_history", optional=True), + ("human", "{input}"), + MessagesPlaceholder("agent_scratchpad"), + ]) + + # Create the agent with tools + agent = create_openai_tools_agent( + llm=model, + tools=tools, + prompt=prompt + ) + + # Create the executor + agent_executor = AgentExecutor( + agent=agent, + tools=tools, + verbose=True + ) + + return agent_executor + +def chat_with_agent(agent_executor, message: str): + """Chat with the agent - Arthur task metadata is now embedded in TracerProvider Resource.""" + + # Run the agent (Arthur task metadata is automatically included via Resource) + result = agent_executor.invoke({ + "input": message + }) + + return result["output"] + +def main(): + """Main function to run the tutorial.""" + + print("🚀 OpenInference Tracing Tutorial") + print("=" * 40) + print("This agent helps you convert times between different timezones and cities.") + print("All interactions are traced to Arthur for monitoring and analysis.") + print() + print("💡 Examples: 'What time is it in Tokyo?' or 'What is 3pm EST in London?'") + print() + + try: + # Validate environment first + if not validate_environment(): + print("❌ Environment validation failed. Please check your .env file.") + return + + # Set up tracing and create agent + setup_tracing() + agent = create_simple_agent() + + print("✅ Agent ready! Start chatting (type 'quit' to exit):") + print("-" * 40) + + while True: + user_input = input("\nYou: ") + + if user_input.lower() in ['quit', 'exit', 'q']: + print("👋 Goodbye!") + break + + try: + # Get response from agent with tracing + response = chat_with_agent(agent, user_input) + print(f"Assistant: {response}") + + except Exception as e: + print(f"❌ Error: {e}") + + except Exception as e: + print(f"❌ Setup failed: {e}") + print("\nMake sure your .env file is configured correctly!") + +if __name__ == "__main__": + main() diff --git a/examples/timezone-agent/requirements.txt b/examples/timezone-agent/requirements.txt new file mode 100644 index 0000000..db018a7 --- /dev/null +++ b/examples/timezone-agent/requirements.txt @@ -0,0 +1,8 @@ +# Core dependencies for OpenInference tracing tutorial +openinference-instrumentation-langchain>=0.1.40 +langchain-openai>=0.3.7 +langchain>=0.3.20 +opentelemetry-api>=1.30.0 +opentelemetry-sdk>=1.30.0 +opentelemetry-exporter-otlp>=1.30.0 +python-dotenv>=1.1.0 \ No newline at end of file diff --git a/examples/timezone-agent/timezone_shortcuts.json b/examples/timezone-agent/timezone_shortcuts.json new file mode 100644 index 0000000..c1505d0 --- /dev/null +++ b/examples/timezone-agent/timezone_shortcuts.json @@ -0,0 +1,304 @@ +{ + "US_TIMEZONES": { + "EST": "America/New_York", + "EDT": "America/New_York", + "CST": "America/Chicago", + "CDT": "America/Chicago", + "MST": "America/Denver", + "MDT": "America/Denver", + "PST": "America/Los_Angeles", + "PDT": "America/Los_Angeles", + "CALIFORNIA": "America/Los_Angeles", + "CA": "America/Los_Angeles", + "NEW YORK": "America/New_York", + "NY": "America/New_York", + "CHICAGO": "America/Chicago", + "ILLINOIS": "America/Chicago", + "DENVER": "America/Denver", + "COLORADO": "America/Denver" + }, + "INTERNATIONAL": { + "GMT": "GMT", + "UTC": "UTC", + "JST": "Asia/Tokyo", + "JAPAN": "Asia/Tokyo", + "TOKYO": "Asia/Tokyo", + "IST": "Asia/Kolkata", + "INDIA": "Asia/Kolkata", + "MUMBAI": "Asia/Kolkata", + "CET": "Europe/Paris", + "PARIS": "Europe/Paris", + "FRANCE": "Europe/Paris", + "BST": "Europe/London", + "LONDON": "Europe/London", + "UK": "Europe/London", + "ZURICH": "Europe/Zurich", + "SWITZERLAND": "Europe/Zurich", + "BERLIN": "Europe/Berlin", + "GERMANY": "Europe/Berlin", + "ROME": "Europe/Rome", + "ITALY": "Europe/Rome", + "MADRID": "Europe/Madrid", + "SPAIN": "Europe/Madrid", + "AMSTERDAM": "Europe/Amsterdam", + "NETHERLANDS": "Europe/Amsterdam", + "STOCKHOLM": "Europe/Stockholm", + "SWEDEN": "Europe/Stockholm", + "OSLO": "Europe/Oslo", + "NORWAY": "Europe/Oslo", + "COPENHAGEN": "Europe/Copenhagen", + "DENMARK": "Europe/Copenhagen", + "HELSINKI": "Europe/Helsinki", + "FINLAND": "Europe/Helsinki", + "VIENNA": "Europe/Vienna", + "AUSTRIA": "Europe/Vienna", + "PRAGUE": "Europe/Prague", + "CZECH": "Europe/Prague", + "BUDAPEST": "Europe/Budapest", + "HUNGARY": "Europe/Budapest", + "WARSAW": "Europe/Warsaw", + "POLAND": "Europe/Warsaw", + "MOSCOW": "Europe/Moscow", + "RUSSIA": "Europe/Moscow", + "ISTANBUL": "Europe/Istanbul", + "TURKEY": "Europe/Istanbul", + "ATHENS": "Europe/Athens", + "GREECE": "Europe/Athens", + "LISBON": "Europe/Lisbon", + "PORTUGAL": "Europe/Lisbon", + "DUBLIN": "Europe/Dublin", + "IRELAND": "Europe/Dublin", + "REYKJAVIK": "Atlantic/Reykjavik", + "ICELAND": "Atlantic/Reykjavik" + }, + "ASIA_PACIFIC": { + "BEIJING": "Asia/Shanghai", + "CHINA": "Asia/Shanghai", + "SHANGHAI": "Asia/Shanghai", + "HONG KONG": "Asia/Hong_Kong", + "SINGAPORE": "Asia/Singapore", + "SYDNEY": "Australia/Sydney", + "AUSTRALIA": "Australia/Sydney", + "MELBOURNE": "Australia/Melbourne", + "BRISBANE": "Australia/Brisbane", + "PERTH": "Australia/Perth", + "ADELAIDE": "Australia/Adelaide", + "AUCKLAND": "Pacific/Auckland", + "NEW ZEALAND": "Pacific/Auckland", + "WELLINGTON": "Pacific/Auckland", + "SEOUL": "Asia/Seoul", + "SOUTH KOREA": "Asia/Seoul", + "BANGKOK": "Asia/Bangkok", + "THAILAND": "Asia/Bangkok", + "MANILA": "Asia/Manila", + "PHILIPPINES": "Asia/Manila", + "JAKARTA": "Asia/Jakarta", + "INDONESIA": "Asia/Jakarta", + "KUALA LUMPUR": "Asia/Kuala_Lumpur", + "MALAYSIA": "Asia/Kuala_Lumpur", + "HANOI": "Asia/Ho_Chi_Minh", + "VIETNAM": "Asia/Ho_Chi_Minh", + "PHNOM PENH": "Asia/Phnom_Penh", + "CAMBODIA": "Asia/Phnom_Penh", + "VIENTIANE": "Asia/Vientiane", + "LAOS": "Asia/Vientiane", + "YANGON": "Asia/Yangon", + "MYANMAR": "Asia/Yangon", + "DHAKA": "Asia/Dhaka", + "BANGLADESH": "Asia/Dhaka", + "KATHMANDU": "Asia/Kathmandu", + "NEPAL": "Asia/Kathmandu", + "COLOMBO": "Asia/Colombo", + "SRI LANKA": "Asia/Colombo", + "ISLAMABAD": "Asia/Karachi", + "PAKISTAN": "Asia/Karachi", + "KABUL": "Asia/Kabul", + "AFGHANISTAN": "Asia/Kabul", + "TEHRAN": "Asia/Tehran", + "IRAN": "Asia/Tehran", + "DUBAI": "Asia/Dubai", + "UAE": "Asia/Dubai", + "UNITED ARAB EMIRATES": "Asia/Dubai", + "DOHA": "Asia/Qatar", + "QATAR": "Asia/Qatar", + "KUWAIT": "Asia/Kuwait", + "BAHRAIN": "Asia/Bahrain", + "RIYADH": "Asia/Riyadh", + "SAUDI ARABIA": "Asia/Riyadh", + "AMMAN": "Asia/Amman", + "JORDAN": "Asia/Amman", + "BEIRUT": "Asia/Beirut", + "LEBANON": "Asia/Beirut", + "DAMASCUS": "Asia/Damascus", + "SYRIA": "Asia/Damascus", + "BAGHDAD": "Asia/Baghdad", + "IRAQ": "Asia/Baghdad", + "JERUSALEM": "Asia/Jerusalem", + "ISRAEL": "Asia/Jerusalem" + }, + "AFRICA": { + "CAIRO": "Africa/Cairo", + "EGYPT": "Africa/Cairo", + "NAIROBI": "Africa/Nairobi", + "KENYA": "Africa/Nairobi", + "LAGOS": "Africa/Lagos", + "NIGERIA": "Africa/Lagos", + "JOHANNESBURG": "Africa/Johannesburg", + "SOUTH AFRICA": "Africa/Johannesburg", + "CAPE TOWN": "Africa/Johannesburg", + "CASABLANCA": "Africa/Casablanca", + "MOROCCO": "Africa/Casablanca", + "ALGIERS": "Africa/Algiers", + "ALGERIA": "Africa/Algiers", + "TUNIS": "Africa/Tunis", + "TUNISIA": "Africa/Tunis", + "TRIPOLI": "Africa/Tripoli", + "LIBYA": "Africa/Tripoli", + "KHARTOUM": "Africa/Khartoum", + "SUDAN": "Africa/Khartoum", + "ADDIS ABABA": "Africa/Addis_Ababa", + "ETHIOPIA": "Africa/Addis_Ababa", + "DAR ES SALAAM": "Africa/Dar_es_Salaam", + "TANZANIA": "Africa/Dar_es_Salaam", + "KAMPALA": "Africa/Kampala", + "UGANDA": "Africa/Kampala", + "KINSHASA": "Africa/Kinshasa", + "DRC": "Africa/Kinshasa", + "LUANDA": "Africa/Luanda", + "ANGOLA": "Africa/Luanda", + "ACCRA": "Africa/Accra", + "GHANA": "Africa/Accra", + "ABIDJAN": "Africa/Abidjan", + "IVORY COAST": "Africa/Abidjan", + "DAKAR": "Africa/Dakar", + "SENEGAL": "Africa/Dakar", + "BAMAKO": "Africa/Bamako", + "MALI": "Africa/Bamako", + "OUAGADOUGOU": "Africa/Ouagadougou", + "BURKINA FASO": "Africa/Ouagadougou", + "NIAMEY": "Africa/Niamey", + "NIGER": "Africa/Niamey", + "NDJAMENA": "Africa/Ndjamena", + "CHAD": "Africa/Ndjamena", + "BANGUI": "Africa/Bangui", + "CENTRAL AFRICAN REPUBLIC": "Africa/Bangui", + "BRAZZAVILLE": "Africa/Brazzaville", + "REPUBLIC OF CONGO": "Africa/Brazzaville", + "LIBREVILLE": "Africa/Libreville", + "GABON": "Africa/Libreville", + "MALABO": "Africa/Malabo", + "EQUATORIAL GUINEA": "Africa/Malabo", + "YAOUNDE": "Africa/Douala", + "CAMEROON": "Africa/Douala" + }, + "AMERICAS": { + "TORONTO": "America/Toronto", + "ONTARIO": "America/Toronto", + "MONTREAL": "America/Montreal", + "QUEBEC": "America/Montreal", + "VANCOUVER": "America/Vancouver", + "BRITISH COLUMBIA": "America/Vancouver", + "CALGARY": "America/Edmonton", + "ALBERTA": "America/Edmonton", + "WINNIPEG": "America/Winnipeg", + "MANITOBA": "America/Winnipeg", + "HALIFAX": "America/Halifax", + "NOVA SCOTIA": "America/Halifax", + "ST. JOHN'S": "America/St_Johns", + "NEWFOUNDLAND": "America/St_Johns", + "MEXICO CITY": "America/Mexico_City", + "MEXICO": "America/Mexico_City", + "GUADALAJARA": "America/Mexico_City", + "MONTERREY": "America/Mexico_City", + "TIJUANA": "America/Tijuana", + "BAJA CALIFORNIA": "America/Tijuana", + "MERIDA": "America/Merida", + "YUCATAN": "America/Merida", + "CANCUN": "America/Cancun", + "QUINTANA ROO": "America/Cancun", + "GUATEMALA CITY": "America/Guatemala", + "GUATEMALA": "America/Guatemala", + "SAN SALVADOR": "America/El_Salvador", + "EL SALVADOR": "America/El_Salvador", + "TEGUCIGALPA": "America/Tegucigalpa", + "HONDURAS": "America/Tegucigalpa", + "MANAGUA": "America/Managua", + "NICARAGUA": "America/Managua", + "SAN JOSE": "America/Costa_Rica", + "COSTA RICA": "America/Costa_Rica", + "PANAMA CITY": "America/Panama", + "PANAMA": "America/Panama", + "BOGOTA": "America/Bogota", + "COLOMBIA": "America/Bogota", + "CARACAS": "America/Caracas", + "VENEZUELA": "America/Caracas", + "QUITO": "America/Guayaquil", + "ECUADOR": "America/Guayaquil", + "LIMA": "America/Lima", + "PERU": "America/Lima", + "LA PAZ": "America/La_Paz", + "BOLIVIA": "America/La_Paz", + "ASUNCION": "America/Asuncion", + "PARAGUAY": "America/Asuncion", + "SANTIAGO": "America/Santiago", + "CHILE": "America/Santiago", + "BUENOS AIRES": "America/Argentina/Buenos_Aires", + "ARGENTINA": "America/Argentina/Buenos_Aires", + "MONTEVIDEO": "America/Montevideo", + "URUGUAY": "America/Montevideo", + "BRASILIA": "America/Sao_Paulo", + "BRAZIL": "America/Sao_Paulo", + "SAO PAULO": "America/Sao_Paulo", + "RIO DE JANEIRO": "America/Sao_Paulo", + "SALVADOR": "America/Bahia", + "BAHIA": "America/Bahia", + "RECIFE": "America/Recife", + "PERNAMBUCO": "America/Recife", + "FORTALEZA": "America/Fortaleza", + "CEARA": "America/Fortaleza", + "MANAUS": "America/Manaus", + "AMAZONAS": "America/Manaus", + "BELEM": "America/Belem", + "PARA": "America/Belem", + "PORTO ALEGRE": "America/Sao_Paulo", + "RIO GRANDE DO SUL": "America/Sao_Paulo", + "CURITIBA": "America/Sao_Paulo", + "PARANA": "America/Sao_Paulo", + "BELO HORIZONTE": "America/Sao_Paulo", + "MINAS GERAIS": "America/Sao_Paulo", + "GOIANIA": "America/Sao_Paulo", + "GOIAS": "America/Sao_Paulo", + "CUIABA": "America/Cuiaba", + "MATO GROSSO": "America/Cuiaba", + "CAMPO GRANDE": "America/Campo_Grande", + "MATO GROSSO DO SUL": "America/Campo_Grande", + "PALMAS": "America/Araguaina", + "TOCANTINS": "America/Araguaina", + "TERESINA": "America/Fortaleza", + "PIAUI": "America/Fortaleza", + "SAO LUIS": "America/Fortaleza", + "MARANHAO": "America/Fortaleza", + "NATAL": "America/Fortaleza", + "RIO GRANDE DO NORTE": "America/Fortaleza", + "JOAO PESSOA": "America/Fortaleza", + "PARAIBA": "America/Fortaleza", + "MACEIO": "America/Maceio", + "ALAGOAS": "America/Maceio", + "ARACAJU": "America/Maceio", + "SERGIPE": "America/Maceio", + "VITORIA": "America/Sao_Paulo", + "ESPIRITO SANTO": "America/Sao_Paulo", + "FLORIANOPOLIS": "America/Sao_Paulo", + "SANTA CATARINA": "America/Sao_Paulo", + "PORTO VELHO": "America/Porto_Velho", + "RONDONIA": "America/Porto_Velho", + "RIO BRANCO": "America/Rio_Branco", + "ACRE": "America/Rio_Branco", + "BOA VISTA": "America/Boa_Vista", + "RORAIMA": "America/Boa_Vista", + "MACAPA": "America/Belem", + "AMAPA": "America/Belem", + "FERNANDO DE NORONHA": "America/Noronha", + "NORONHA": "America/Noronha" + } +} \ No newline at end of file diff --git a/examples/timezone-agent/timezone_tools.py b/examples/timezone-agent/timezone_tools.py new file mode 100644 index 0000000..734df32 --- /dev/null +++ b/examples/timezone-agent/timezone_tools.py @@ -0,0 +1,142 @@ +""" +Timezone tools for LangChain agent with OpenInference tracing. + +This file combines the timezone conversion functions with LangChain tool wrappers +for use in the tutorial agent. +""" + +import json +import os +import re +from datetime import datetime +from zoneinfo import ZoneInfo +from langchain.tools import tool + +def load_timezone_shortcuts(): + """Load timezone shortcuts from JSON file.""" + try: + # Get the directory of this script to find the JSON file + script_dir = os.path.dirname(os.path.abspath(__file__)) + json_path = os.path.join(script_dir, 'timezone_shortcuts.json') + + with open(json_path, 'r') as f: + data = json.load(f) + + # Flatten the nested structure into a single dictionary + shortcuts = {} + for category in data.values(): + shortcuts.update(category) + + return shortcuts + except FileNotFoundError: + print("Warning: timezone_shortcuts.json not found, using fallback shortcuts") + return { + 'EST': 'America/New_York', 'EDT': 'America/New_York', + 'CST': 'America/Chicago', 'CDT': 'America/Chicago', + 'MST': 'America/Denver', 'MDT': 'America/Denver', + 'PST': 'America/Los_Angeles', 'PDT': 'America/Los_Angeles', + 'GMT': 'GMT', 'UTC': 'UTC', + 'JST': 'Asia/Tokyo', 'IST': 'Asia/Kolkata', + 'CET': 'Europe/Paris', 'BST': 'Europe/London', + 'ZURICH': 'Europe/Zurich', 'LONDON': 'Europe/London', + 'TOKYO': 'Asia/Tokyo', 'CALIFORNIA': 'America/Los_Angeles', + 'NEW YORK': 'America/New_York' + } + +def convert_timezone(time_str, from_tz, to_tz): + """ + Convert time between timezones. + + Args: + time_str: "2024-03-15 14:30" or "2024-03-15 14:30:00" or "10pm" or "10:30pm" + from_tz: "EST", "UTC", "America/New_York", "California", "Zurich", etc. + to_tz: "JST", "UTC", "Asia/Tokyo", "California", "Zurich", etc. + + Returns: + String with converted time or error message + """ + shortcuts = load_timezone_shortcuts() + + try: + # Handle time formats like "10pm", "10:30pm", "10 AM", etc. + time_str = time_str.strip().upper() + + # If it's just a time (like "10PM"), assume today's date + if re.match(r'^\d{1,2}(:\d{2})?\s*(AM|PM)$', time_str): + today = datetime.now().strftime('%Y-%m-%d') + time_str = f"{today} {time_str}" + + # Parse time + if len(time_str.split(':')) == 2 and 'T' not in time_str: + time_str += ":00" # Add seconds if missing + + # Handle different date formats + if '/' in time_str: + time_str = time_str.replace('/', '-') + + # Parse the datetime + dt = datetime.fromisoformat(time_str) + + # Get timezone names + from_zone = shortcuts.get(from_tz.upper(), from_tz) + to_zone = shortcuts.get(to_tz.upper(), to_tz) + + # Convert + dt_from = dt.replace(tzinfo=ZoneInfo(from_zone)) + dt_to = dt_from.astimezone(ZoneInfo(to_zone)) + + return f"{dt_to.strftime('%Y-%m-%d %H:%M:%S')} {to_tz.upper()}" + + except Exception as e: + return f"Error: {str(e)}" + +def current_time(timezone): + """Get current time in timezone.""" + shortcuts = load_timezone_shortcuts() + + try: + zone_name = shortcuts.get(timezone.upper(), timezone) + now = datetime.now(ZoneInfo(zone_name)) + return f"{now.strftime('%Y-%m-%d %H:%M:%S')} {timezone.upper()}" + except Exception as e: + return f"Error: {str(e)}" + +# LangChain Tool Wrappers + +@tool +def get_current_time(timezone: str) -> str: + """ + Get the current time in a specific timezone or city. + + Args: + timezone: The timezone or city name (e.g., "Zurich", "California", "EST", "UTC") + + Returns: + Current time in the specified timezone + + Examples: + - "What is the time in Zurich right now?" -> get_current_time("Zurich") + - "What time is it in California?" -> get_current_time("California") + - "Current time in EST" -> get_current_time("EST") + """ + return current_time(timezone) + +@tool +def convert_time_between_zones(time_str: str, from_timezone: str, to_timezone: str) -> str: + """ + Convert a time from one timezone to another. + + Args: + time_str: The time to convert (e.g., "10pm", "2024-03-15 14:30", "10:30pm") + from_timezone: The source timezone or city (e.g., "EDT", "California", "Zurich") + to_timezone: The target timezone or city (e.g., "PST", "Tokyo", "London") + + Returns: + The converted time in the target timezone + + Examples: + - "What is 10pm EDT in California?" -> convert_time_between_zones("10pm", "EDT", "California") + - "Convert 2:30pm EST to Tokyo time" -> convert_time_between_zones("2:30pm", "EST", "Tokyo") + - "What time is 9am in London when it's 3pm in New York?" -> convert_time_between_zones("3pm", "New York", "London") + """ + return convert_timezone(time_str, from_timezone, to_timezone) \ No newline at end of file diff --git a/examples/timezone-agent/validate_env.py b/examples/timezone-agent/validate_env.py new file mode 100644 index 0000000..eb5d828 --- /dev/null +++ b/examples/timezone-agent/validate_env.py @@ -0,0 +1,28 @@ +""" +Environment validation helper for the OpenInference tracing tutorial. +""" + +import os + +def validate_environment(): + """Validate that all required environment variables are set.""" + required_vars = { + "ARTHUR_BASE_URL": "Arthur instance URL (e.g., https://app.arthur.ai)", + "ARTHUR_API_KEY": "Arthur API key from your dashboard", + "ARTHUR_TASK_ID": "Arthur task ID (ensure task is marked as agentic: is_agentic=True)", + "OPENAI_API_KEY": "OpenAI API key" + } + + missing_vars = [] + for var, description in required_vars.items(): + if not os.getenv(var): + missing_vars.append(f"• {var}: {description}") + + if missing_vars: + print("❌ Missing required environment variables:") + for var in missing_vars: + print(f" {var}") + print("\nPlease update your .env file with the missing values.") + return False + + return True \ No newline at end of file