|
1 | 1 | """Shared tools for interacting with Hayhooks API endpoints.""" |
2 | 2 | import requests |
3 | | -from typing import Dict, Any, List |
| 3 | +from typing import Dict, Any |
4 | 4 | from langchain_core.tools import tool |
5 | | -from langchain_core.messages import HumanMessage, AIMessage, SystemMessage |
6 | | -from .config import get_llm |
7 | 5 |
|
8 | 6 | BASE_URL = "http://localhost:1416" |
9 | 7 |
|
10 | | -MODEL = "gpt-oss:20b" #"gpt-4o-mini" #change to your preferred model |
11 | | - |
12 | | -# OpenAI Chat Completion Tool |
13 | | -@tool |
14 | | -def chat_completion(messages: List[Dict[str, str]], model: str = MODEL, stream: bool = False, base_url: str = BASE_URL) -> Dict[str, Any]: |
15 | | - """Send a general chat completion request using OpenAI's chat completion API. |
16 | | - |
17 | | - Args: |
18 | | - messages: List of message dicts, e.g. [{"role": "user", "content": "Hello!"}] |
19 | | - model: Model name to use (please review shared/config.py for supported models) |
20 | | - stream: Whether to stream the response (default: False) |
21 | | - base_url: Base URL for the API endpoint (deprecated, kept for compatibility) |
22 | | - |
23 | | - Returns: |
24 | | - Dictionary containing the chat completion response in OpenAI format: |
25 | | - { |
26 | | - "success": True/False, |
27 | | - "response": { |
28 | | - "id": "string", |
29 | | - "object": "chat.completion", |
30 | | - "created": int, |
31 | | - "model": "string", |
32 | | - "choices": [ |
33 | | - { |
34 | | - "index": 0, |
35 | | - "message": { |
36 | | - "role": "assistant", |
37 | | - "content": "string" |
38 | | - }, |
39 | | - "finish_reason": "stop" |
40 | | - } |
41 | | - ] |
42 | | - }, |
43 | | - "error": "error message" (only if success is False) |
44 | | - } |
45 | | - """ |
46 | | - max_attempts = 3 |
47 | | - last_error = None |
48 | | - |
49 | | - for attempt in range(max_attempts): |
50 | | - try: |
51 | | - # Get the LLM instance with error handling |
52 | | - try: |
53 | | - llm = get_llm(model=model) |
54 | | - except Exception as llm_error: |
55 | | - if attempt == max_attempts - 1: |
56 | | - return { |
57 | | - "success": False, |
58 | | - "error": f"Failed to initialize model '{model}': {str(llm_error)}. Please check your API keys and model configuration." |
59 | | - } |
60 | | - # Wait before retry (exponential backoff) |
61 | | - import time |
62 | | - time.sleep(2 ** attempt) |
63 | | - continue |
64 | | - |
65 | | - # Add system instruction to always end with a summary |
66 | | - system_instruction = SystemMessage(content=( |
67 | | - "You are a helpful assistant for a Yelp business search and analysis system. " |
68 | | - "After answering the user's question, ALWAYS end your response with a brief summary that includes:\n" |
69 | | - "1. What this agentic system does (searches for businesses, analyzes reviews, and provides recommendations)\n" |
70 | | - "2. What information it needs from users (location, business type/category, and specific preferences or requirements)" |
71 | | - )) |
72 | | - |
73 | | - # Convert message dicts to LangChain message objects |
74 | | - langchain_messages = [system_instruction] |
75 | | - for msg in messages: |
76 | | - role = msg.get("role", "user") |
77 | | - content = msg.get("content", "") |
78 | | - |
79 | | - if role == "system": |
80 | | - langchain_messages.append(SystemMessage(content=content)) |
81 | | - elif role == "assistant": |
82 | | - langchain_messages.append(AIMessage(content=content)) |
83 | | - else: # user or any other role |
84 | | - langchain_messages.append(HumanMessage(content=content)) |
85 | | - |
86 | | - # Invoke the LLM with timeout protection |
87 | | - try: |
88 | | - response = llm.invoke(langchain_messages) |
89 | | - except Exception as invoke_error: |
90 | | - last_error = invoke_error |
91 | | - if "rate limit" in str(invoke_error).lower() or "quota" in str(invoke_error).lower(): |
92 | | - # Rate limit error - wait longer |
93 | | - if attempt < max_attempts - 1: |
94 | | - import time |
95 | | - wait_time = min(30, 5 * (2 ** attempt)) |
96 | | - print(f"Rate limit hit, waiting {wait_time}s before retry...") |
97 | | - time.sleep(wait_time) |
98 | | - continue |
99 | | - elif "timeout" in str(invoke_error).lower(): |
100 | | - # Timeout error - retry with shorter timeout |
101 | | - if attempt < max_attempts - 1: |
102 | | - import time |
103 | | - time.sleep(2) |
104 | | - continue |
105 | | - raise # Re-raise if not rate limit or last attempt |
106 | | - |
107 | | - # Format response in OpenAI-compatible format |
108 | | - return { |
109 | | - "success": True, |
110 | | - "response": { |
111 | | - "id": getattr(response, "id", "chatcmpl-" + str(hash(response.content))), |
112 | | - "object": "chat.completion", |
113 | | - "created": int(getattr(response, "response_metadata", {}).get("created", 0)), |
114 | | - "model": model, |
115 | | - "choices": [ |
116 | | - { |
117 | | - "index": 0, |
118 | | - "message": { |
119 | | - "role": "assistant", |
120 | | - "content": response.content |
121 | | - }, |
122 | | - "finish_reason": "stop" |
123 | | - } |
124 | | - ] |
125 | | - }, |
126 | | - "metadata": { |
127 | | - "attempts": attempt + 1, |
128 | | - "model_used": model |
129 | | - } |
130 | | - } |
131 | | - |
132 | | - except Exception as e: |
133 | | - last_error = e |
134 | | - if attempt == max_attempts - 1: |
135 | | - error_type = type(e).__name__ |
136 | | - error_details = str(e) |
137 | | - |
138 | | - # Provide helpful error messages based on error type |
139 | | - if "api_key" in error_details.lower() or "authentication" in error_details.lower(): |
140 | | - error_msg = f"Authentication failed for model '{model}': Check your API keys in .env file" |
141 | | - elif "not found" in error_details.lower() or "does not exist" in error_details.lower(): |
142 | | - error_msg = f"Model '{model}' not found. Available models depend on your provider." |
143 | | - elif "connection" in error_details.lower() or "network" in error_details.lower(): |
144 | | - error_msg = f"Network error connecting to {model}: Check your internet connection" |
145 | | - else: |
146 | | - error_msg = f"Error in chat completion ({error_type}): {error_details}" |
147 | | - |
148 | | - return { |
149 | | - "success": False, |
150 | | - "error": error_msg, |
151 | | - "error_type": error_type, |
152 | | - "attempts": max_attempts |
153 | | - } |
154 | | - |
155 | | - # Should not reach here, but just in case |
156 | | - return { |
157 | | - "success": False, |
158 | | - "error": f"Failed after {max_attempts} attempts: {str(last_error)}", |
159 | | - "attempts": max_attempts |
160 | | - } |
161 | 8 |
|
162 | 9 | @tool |
163 | 10 | def search_businesses(query: str) -> Dict[str, Any]: |
|
0 commit comments