tecuts commited on
Commit
9178353
·
verified ·
1 Parent(s): 69269fa

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +205 -0
app.py ADDED
@@ -0,0 +1,205 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import requests
4
+ from fastapi import FastAPI, Request
5
+ from fastapi.responses import HTMLResponse
6
+ from fastapi.middleware.cors import CORSMiddleware
7
+ from openai import OpenAI
8
+
9
+ # --- Load API Keys and IDs from Environment Variables (Hugging Face Secrets) ---
10
+ # For Hugging Face Spaces, set these in "Repository secrets" in your space's settings.
11
+ # 1. GOOGLE_API_KEY: Your API key from Google Cloud Console.
12
+ # 2. GOOGLE_CX: Your Custom Search Engine ID.
13
+ # 3. LLM_API_KEY: Your API key for the OpenAI-compatible service.
14
+ # 4. LLM_BASE_URL: The base URL for the OpenAI-compatible service.
15
+ GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
16
+ GOOGLE_CX = os.getenv("GOOGLE_CX")
17
+ LLM_API_KEY = os.getenv("LLM_API_KEY")
18
+ LLM_BASE_URL = os.getenv("LLM_BASE_URL", "https://api-15i2e8ze256bvfn6.aistudio-app.com/v1") # Default added for convenience
19
+
20
+ # --- IMPORTANT: Real Web Search Tool Implementation ---
21
+ def Google Search_tool(queries: list) -> list:
22
+ """
23
+ Performs a real web search using the Google Custom Search JSON API.
24
+ """
25
+ if not GOOGLE_API_KEY or not GOOGLE_CX:
26
+ print("ERROR: GOOGLE_API_KEY or GOOGLE_CX environment variables not set.")
27
+ # Return a structure indicating the error to the LLM
28
+ return [{"query": queries[0], "results": [{"dict": lambda: {"snippet": "Search is not configured."}}]}]
29
+
30
+ query = queries[0] # The LLM is expected to send one query at a time.
31
+ print(f"Executing Google Custom Search for: '{query}'")
32
+
33
+ search_url = "https://www.googleapis.com/customsearch/v1"
34
+ params = {
35
+ "key": GOOGLE_API_KEY,
36
+ "cx": GOOGLE_CX,
37
+ "q": query,
38
+ "num": 3 # Request top 5 results
39
+ }
40
+
41
+ try:
42
+ response = requests.get(search_url, params=params, timeout=10)
43
+ response.raise_for_status() # Raise an exception for HTTP errors (e.g., 4xx, 5xx)
44
+ search_results = response.json()
45
+
46
+ # --- Define classes to structure the output for the LLM ---
47
+ class SearchResult:
48
+ def __init__(self, title, url, snippet):
49
+ self.source_title = title
50
+ self.url = url
51
+ self.snippet = snippet
52
+ def dict(self):
53
+ return self.__dict__
54
+
55
+ class SearchResultsContainer:
56
+ def __init__(self, query, results):
57
+ self.query = query
58
+ self.results = results
59
+
60
+ # --- Parse the API response ---
61
+ parsed_snippets = []
62
+ if "items" in search_results:
63
+ for item in search_results["items"]:
64
+ parsed_snippets.append(
65
+ SearchResult(
66
+ title=item.get("title"),
67
+ url=item.get("link"),
68
+ snippet=item.get("snippet")
69
+ )
70
+ )
71
+
72
+ return [SearchResultsContainer(query=query, results=parsed_snippets)]
73
+
74
+ except requests.exceptions.RequestException as e:
75
+ print(f"Error during Google search request: {e}")
76
+ return [] # Return an empty list to indicate an error
77
+
78
+ # --- FastAPI Application Setup ---
79
+ app = FastAPI()
80
+
81
+ app.add_middleware(
82
+ CORSMiddleware,
83
+ allow_origins=["*"], # Allows all origins, suitable for Hugging Face Spaces
84
+ allow_credentials=True,
85
+ allow_methods=["*"],
86
+ allow_headers=["*"],
87
+ )
88
+
89
+ # --- OpenAI Client Initialization ---
90
+ # Check if the keys were loaded correctly
91
+ if not LLM_API_KEY or not LLM_BASE_URL:
92
+ print("WARNING: LLM_API_KEY or LLM_BASE_URL is not set. The /chat endpoint will fail.")
93
+ client = None
94
+ else:
95
+ client = OpenAI(
96
+ api_key=LLM_API_KEY,
97
+ base_url=LLM_BASE_URL
98
+ )
99
+
100
+ # --- Define Tools for the LLM ---
101
+ available_tools = [
102
+ {
103
+ "type": "function",
104
+ "function": {
105
+ "name": "Google Search",
106
+ "description": "Performs a Google search to find information on the internet. Use this when the user asks a question that requires up-to-date, external, or real-time knowledge (e.g., current events, weather, specific facts not in training data, definitions, 'what is', 'latest', 'news about').",
107
+ "parameters": {
108
+ "type": "object",
109
+ "properties": {
110
+ "query": {
111
+ "type": "string",
112
+ "description": "The search query based on the user's question, optimized for web search. Be concise and precise."
113
+ }
114
+ },
115
+ "required": ["query"]
116
+ }
117
+ }
118
+ }
119
+ ]
120
+
121
+ # --- Chatbot Endpoint ---
122
+ @app.post("/chat")
123
+ async def chat_endpoint(request: Request):
124
+ if not client:
125
+ return {"response": "Error: The LLM client is not configured on the server. API keys may be missing."}
126
+
127
+ try:
128
+ data = await request.json()
129
+ user_message = data.get("message")
130
+ chat_history = data.get("history", [])
131
+
132
+ if not user_message:
133
+ return {"response": "Error: No message provided."}
134
+
135
+ messages = chat_history + [{"role": "user", "content": user_message}]
136
+
137
+ # --- Step 1: Call LLM with potential tools ---
138
+ llm_response_1 = client.chat.completions.create(
139
+ model="unsloth/Qwen3-30B-A3B-GGUF", # Make sure this model is correct for your service
140
+ temperature=0.6,
141
+ messages=messages,
142
+ stream=False,
143
+ tools=available_tools,
144
+ tool_choice="auto"
145
+ )
146
+
147
+ tool_calls = llm_response_1.choices[0].message.tool_calls
148
+ if tool_calls:
149
+ # --- Step 2: Execute the tool(s) ---
150
+ tool_outputs = []
151
+ for tool_call in tool_calls:
152
+ function_name = tool_call.function.name
153
+ function_args = json.loads(tool_call.function.arguments)
154
+
155
+ if function_name == "Google Search":
156
+ search_query = function_args.get("query")
157
+ if search_query:
158
+ search_results_obj = Google Search_tool(queries=[search_query])
159
+
160
+ formatted_results = []
161
+ if search_results_obj and search_results_obj[0].results:
162
+ for res in search_results_obj[0].results:
163
+ formatted_results.append(f"Source: {res.source_title}\nURL: {res.url}\nSnippet: {res.snippet}")
164
+
165
+ tool_output_content = "No relevant search results found."
166
+ if formatted_results:
167
+ tool_output_content = "Search Results:\n" + "\n---\n".join(formatted_results[:3]) # Top 3 results
168
+
169
+ tool_outputs.append({
170
+ "tool_call_id": tool_call.id,
171
+ "output": tool_output_content
172
+ })
173
+ else:
174
+ tool_outputs.append({
175
+ "tool_call_id": tool_call.id,
176
+ "output": f"Error: Tool '{function_name}' is not supported."
177
+ })
178
+
179
+ # --- Step 3: Send tool output back to LLM ---
180
+ messages.append(llm_response_1.choices[0].message)
181
+ for output_item in tool_outputs:
182
+ messages.append(
183
+ {"role": "tool", "tool_call_id": output_item["tool_call_id"], "content": output_item["output"]}
184
+ )
185
+
186
+ llm_response_2 = client.chat.completions.create(
187
+ model="unsloth/Qwen3-30B-A3B-GGUF",
188
+ temperature=0.6,
189
+ messages=messages,
190
+ stream=False
191
+ )
192
+ final_chatbot_response = llm_response_2.choices[0].message.content
193
+ else:
194
+ final_chatbot_response = llm_response_1.choices[0].message.content
195
+
196
+ return {"response": final_chatbot_response}
197
+
198
+ except Exception as e:
199
+ print(f"ERROR in /chat: {e}")
200
+ return {"response": f"An internal error occurred: {str(e)}"}
201
+
202
+ # --- Health Check / Root Endpoint ---
203
+ @app.get("/")
204
+ async def root():
205
+ return {"message": "Chatbot FastAPI is running. Send POST requests to /chat."}