brian-yu-nexusflow commited on
Commit
6b08132
β€’
1 Parent(s): d57b950

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +484 -235
app.py CHANGED
@@ -1,282 +1,531 @@
1
- """
2
- These are all the tools used in the NexusRaven V2 demo! You can provide any tools you want to Raven.
3
 
4
- Nothing in this file is specific to Raven, code/information related to Raven can be found in the `raven_demo.py` file.
5
 
6
- For more information about the Google Maps Places API Python client, see https://github.com/googlemaps/google-maps-services-python
7
- """
8
- from typing import Dict, List
9
 
10
- from math import radians, cos, sin, asin, sqrt
11
 
12
- import random
13
 
14
- import requests
15
 
16
- from googlemaps import Client
17
 
18
- from config import DemoConfig
19
 
 
20
 
21
- class Tools:
22
- def __init__(self, config: DemoConfig) -> None:
23
- self.config = config
24
 
25
- self.gmaps = Client(config.gmaps_client_key)
26
- self.client_ip: str | None = None
27
 
28
- def haversine(self, lon1, lat1, lon2, lat2) -> float:
29
- """
30
- Calculate the great circle distance in kilometers between two points on the earth (specified in decimal degrees).
31
- """
32
- # convert decimal degrees to radians
33
- lon1, lat1, lon2, lat2 = map(radians, [lon1, lat1, lon2, lat2])
34
-
35
- # haversine formula
36
- dlon = lon2 - lon1
37
- dlat = lat2 - lat1
38
- a = sin(dlat / 2) ** 2 + cos(lat1) * cos(lat2) * sin(dlon / 2) ** 2
39
- c = 2 * asin(sqrt(a))
40
- r = 6371 # Radius of Earth in kilometers. Use 3956 for miles
41
- return round(c * r, 2)
42
-
43
- def get_current_location(self) -> str:
44
- """
45
- Returns the current location. ONLY use this if the user has not provided an explicit location in the query.
46
- """
47
- try:
48
- response = requests.get(f"http://ip-api.com/json/{self.client_ip}")
49
- location_data = response.json()
50
- city = location_data["city"]
51
- region = location_data["regionName"]
52
- country = location_data["countryCode"]
53
- location = f"{city}, {region}, {country}"
54
- print(f"User successfully located in {location}")
55
- except:
56
- location = "San Francisco, California, US"
57
- print(f"Not able to find user. Defaulting to {location}")
58
- return location
59
-
60
- def sort_results(
61
- self, places: list, sort: str, descending: bool = True, first_n: int = None
62
- ) -> List:
63
- """
64
- Sorts the results by either 'distance', 'rating' or 'price'.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
 
66
- - places (list): The output list from the recommendations.
67
- - sort (str): If set, sorts by either 'distance' or 'rating' or 'price'. ONLY supports 'distance' or 'rating' or 'price'.
68
- - descending (bool): If descending is set, setting this boolean to true will sort the results such that the highest values are first.
69
- - first_n (int): If provided, only retains the first n items in the final sorted list.
70
 
71
- When people ask for 'closest' or 'nearest', sort by 'distance'.
72
- When people ask for 'cheapest' or 'most expensive', sort by 'price'.
73
- When people ask for 'best' or 'highest rated', sort by rating.
74
- """
75
 
76
- if not sort:
77
- return places
 
 
 
78
 
79
- if sort == "price":
80
- sort = "price_level"
 
 
81
 
82
- items = sorted(
83
- places,
84
- key=lambda x: x.get(sort, float("inf")),
85
- reverse=descending,
86
  )
87
 
88
- if first_n:
89
- items = items[:first_n]
90
- return items
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
 
92
- def get_latitude_longitude(self, location: str) -> List:
93
- """
94
- Given a city name, this function provides the latitude and longitude of the specific location.
 
 
95
 
96
- - location: This can be a city like 'Austin', or a place like 'Austin Airport', etc.
97
- """
98
- if (
99
- isinstance(location, list)
100
- and len(location) != 0
101
- and isinstance(location[0], dict)
102
- ):
103
- return location
104
 
105
- # For response content, see https://developers.google.com/maps/documentation/places/web-service/search-find-place#find-place-responses
106
- results = self.gmaps.find_place(
107
- location, input_type="textquery", location_bias="ipbias"
 
 
108
  )
109
- if results["status"] != "OK":
110
- return []
111
- print(results)
112
 
113
- # We always use the first candidate
114
- place_id = results["candidates"][0]["place_id"]
 
115
 
116
- # For response format, see https://developers.google.com/maps/documentation/places/web-service/details#PlaceDetailsResponses
117
- place_details = self.gmaps.place(place_id=place_id)["result"]
118
- return [place_details]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
 
120
- def get_distance(self, place_1: str, place_2: str):
121
- """
122
- Provides distance between two locations. Do NOT provide latitude longitude, but rather, provide the string descriptions.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
 
124
- Allows you to provide output from the get_recommendations API.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
 
126
- - place_1: The first location.
127
- - place_2: The second location.
128
- """
129
- if isinstance(place_1, list) and len(place_1) > 0:
130
- place_1 = place_1[0]
131
- if isinstance(place_2, list) and len(place_2) > 0:
132
- place_2 = place_2[0]
133
-
134
- if isinstance(place_1, dict):
135
- place_1: str = place_1["name"]
136
- if isinstance(place_2, dict):
137
- place_2: str = place_2["name"]
138
-
139
- latlong_1 = self.get_latitude_longitude(place_1)
140
- if len(latlong_1) == 0:
141
- return f"No place found for `{place_1}`. Please be more explicit."
142
-
143
- latlong_2 = self.get_latitude_longitude(place_2)
144
- if len(latlong_2) == 0:
145
- return f"No place found for `{place_2}`. Please be more explicit."
146
-
147
- latlong_1 = latlong_1[0]
148
- latlong_2 = latlong_2[0]
149
-
150
- latlong_values_1 = latlong_1["geometry"]["location"]
151
- latlong_values_2 = latlong_2["geometry"]["location"]
152
-
153
- dist = self.haversine(
154
- latlong_values_1["lng"],
155
- latlong_values_1["lat"],
156
- latlong_values_2["lng"],
157
- latlong_values_2["lat"],
 
 
 
 
 
 
 
158
  )
159
- dist = dist * 0.621371
 
 
 
 
 
 
 
 
160
 
161
- return [
162
- latlong_1,
163
- latlong_2,
164
- f"The distance between {place_1} and {place_2} is {dist:.3f} miles",
165
- ]
166
 
167
- def get_recommendations(self, topics: list, lat_long: tuple):
168
- """
169
- Returns the recommendations for a specific topic that is of interest. Remember, a topic IS NOT an establishment. For establishments, please use another function.
 
 
170
 
171
- - topics (list): A list of topics of interest to pull recommendations for. Can be multiple words.
172
- - lat_long (tuple): The lat_long of interest.
173
- """
174
- if len(lat_long) == 0:
175
- return []
176
-
177
- topic = " ".join(topics)
178
- latlong = lat_long[0]["geometry"]["location"]
179
- # For response format, see https://developers.google.com/maps/documentation/places/web-service/search-find-place#find-place-responses
180
- results = self.gmaps.places(
181
- query=topic,
182
- location=latlong,
183
- )
184
- return results["results"]
185
 
186
- def find_places_near_location(
187
- self, type_of_place: list, location: str, radius_miles: int = 50
188
- ) -> List[Dict]:
189
- """
190
- Find places close to a very defined location.
191
 
192
- - type_of_place (list): The type of place. This can be something like 'restaurant' or 'airport'. Make sure that it is a physical location. You can provide multiple words.
193
- - location (str): The location for the search. This can be a city's name, region, or anything that specifies the location.
194
- - radius_miles (int): Optional. The max distance from the described location to limit the search. Distance is specified in miles.
195
- """
196
- place_details = self.get_latitude_longitude(location)
197
- if len(place_details) == 0:
198
- return []
199
- place_details = place_details[0]
200
- location = place_details["name"]
201
- latlong = place_details["geometry"]["location"]
202
-
203
- type_of_place = " ".join(type_of_place)
204
- # Perform the search using Google Places API
205
- # For response format, see https://developers.google.com/maps/documentation/places/web-service/search-nearby#nearby-search-responses
206
- places_nearby = self.gmaps.places_nearby(
207
- location=(latlong["lat"], latlong["lng"]),
208
- keyword=type_of_place,
209
- radius=radius_miles * 1609.34,
210
  )
211
- if places_nearby["status"] != "OK":
212
- return []
213
-
214
- places_nearby = places_nearby["results"]
215
- places = []
216
- for place_nearby in places_nearby:
217
- place_location = place_nearby["geometry"]["location"]
218
- distance = self.haversine(
219
- latlong["lng"],
220
- latlong["lat"],
221
- place_location["lng"],
222
- place_location["lat"],
223
- )
224
- if distance == 0.0:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
225
  continue
226
 
227
- distance = distance * 0.621371
228
- place_nearby["distance"] = f"{distance} miles from {location}"
229
- places.append(place_nearby)
230
 
231
- if len(places) == 0:
232
- return []
 
 
233
 
234
- return self.sort_results(places, sort="distance", descending=False)
 
235
 
236
- def get_some_reviews(self, place_names: list, location: str = None):
 
 
 
 
 
 
 
 
 
 
 
 
 
237
  """
238
- Given an establishment (or place) name, return reviews about the establishment.
 
 
 
239
 
240
- - place_names (list): The name of the establishment. This should be a physical location name. You can provide multiple inputs.
241
- - location (str) : The location where the restaurant is located. Optional argument.
242
  """
243
- all_reviews = []
244
- for place_name in place_names:
245
- if isinstance(place_name, str):
246
- if location and isinstance(location, list) and len(location) > 0:
247
- # Sometimes location will be a list of relevant places from the API.
248
- # We just use the first one.
249
- location = location[0]
250
- elif location and isinstance(location, list):
251
- # No matching spaces found in the API, len of 0
252
- location = None
253
- if location and isinstance(location, dict):
254
- # Weird response from the API, likely a timeout error, disable geoloc
255
- location = None
256
- if location and isinstance(location, str):
257
- place_name += " , " + location
258
- elif (
259
- isinstance(place_name, dict)
260
- and "results" in place_name
261
- and "name" in place_name["results"]
262
- ):
263
- place_name = place_name["results"]["name"]
264
- elif isinstance(place_name, dict) and "name" in place_name:
265
- place_name = place_name["name"]
266
-
267
- place_details = self.get_latitude_longitude(place_name)
268
- if len(place_details) == 0:
269
- continue
270
- place_details = place_details[0]
 
 
271
 
272
- reviews = place_details.get("reviews", [])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
 
274
- for review in reviews:
275
- review["for_location"] = place_name
276
- review["formatted_address"] = place_details["formatted_address"]
277
 
278
- all_reviews.extend(reviews)
279
 
280
- random.shuffle(all_reviews)
281
 
282
- return all_reviews
 
 
 
 
 
 
1
+ from typing import Any, Callable, List, Tuple
 
2
 
3
+ import huggingface_hub
4
 
5
+ from dataclasses import dataclass
 
 
6
 
7
+ from datetime import datetime
8
 
9
+ from time import sleep
10
 
11
+ import inspect
12
 
13
+ from random import randint
14
 
15
+ from urllib.parse import quote
16
 
17
+ from black import Mode, format_str
18
 
19
+ import gradio as gr
 
 
20
 
21
+ from huggingface_hub import InferenceClient
 
22
 
23
+ from constants import *
24
+ from config import DemoConfig
25
+ from tools import Tools
26
+
27
+
28
+ @dataclass
29
+ class Function:
30
+ name: str
31
+ short_description: str
32
+ description_function: Callable[[Any], str]
33
+ explanation_function: Callable[[Any], str]
34
+
35
+
36
+ FUNCTIONS = [
37
+ Function(
38
+ name="get_current_location",
39
+ short_description="Finding your city",
40
+ description_function=lambda *_, **__: "Finding your city",
41
+ explanation_function=lambda result: f"Found you in {result}!",
42
+ ),
43
+ Function(
44
+ name="sort_results",
45
+ short_description="Sorting results",
46
+ description_function=lambda places, sort, descending=True, first_n=None: f"Sorting results by {sort} from "
47
+ + ("lowest to highest" if not descending else "highest to lowest"),
48
+ explanation_function=lambda result: "Done!",
49
+ ),
50
+ Function(
51
+ name="get_latitude_longitude",
52
+ short_description="Convert to coordinates",
53
+ description_function=lambda location: f"Converting {location} into latitude and longitude coordinates",
54
+ explanation_function=lambda result: "Converted!",
55
+ ),
56
+ Function(
57
+ name="get_distance",
58
+ short_description="Calcuate distance",
59
+ description_function=lambda place_1, place_2: "Calculating distances",
60
+ explanation_function=lambda result: result[2],
61
+ ),
62
+ Function(
63
+ name="get_recommendations",
64
+ short_description="Read recommendations",
65
+ description_function=lambda topics, **__: f"Reading recommendations for the following "
66
+ + (
67
+ f"topics: {', '.join(topics)}" if len(topics) > 1 else f"topic: {topics[0]}"
68
+ ),
69
+ explanation_function=lambda result: f"Read {len(result)} recommendations",
70
+ ),
71
+ Function(
72
+ name="find_places_near_location",
73
+ short_description="Look for places",
74
+ description_function=lambda type_of_place, location, radius_miles=50: f"Looking for places near {location} within {radius_miles} with the following "
75
+ + (
76
+ f"types: {', '.join(type_of_place)}"
77
+ if isinstance(type_of_place, list)
78
+ else f"type: {type_of_place}"
79
+ ),
80
+ explanation_function=lambda result: f"Found "
81
+ + (f"{len(result)} places!" if len(result) > 1 else f"1 place!"),
82
+ ),
83
+ Function(
84
+ name="get_some_reviews",
85
+ short_description="Fetching reviews",
86
+ description_function=lambda place_names, **_: f"Fetching reviews for the requested items",
87
+ explanation_function=lambda result: f"Fetched {len(result)} reviews!",
88
+ ),
89
+ ]
90
+
91
+
92
+ class FunctionsHelper:
93
+ FUNCTION_DEFINITION_TEMPLATE = '''Function:
94
+ def {name}{signature}:
95
+ """
96
+ {docstring}
97
+ """
98
 
99
+ '''
100
+ PROMPT_TEMPLATE = """{function_definitions}User Query: {query}<human_end>Call:"""
 
 
101
 
102
+ def __init__(self, tools: Tools) -> None:
103
+ self.tools = tools
 
 
104
 
105
+ function_definitions = ""
106
+ for function in FUNCTIONS:
107
+ f = getattr(tools, function.name)
108
+ signature = inspect.signature(f)
109
+ docstring = inspect.getdoc(f)
110
 
111
+ function_str = self.FUNCTION_DEFINITION_TEMPLATE.format(
112
+ name=function.name, signature=signature, docstring=docstring
113
+ )
114
+ function_definitions += function_str
115
 
116
+ self.prompt_without_query = self.PROMPT_TEMPLATE.format(
117
+ function_definitions=function_definitions, query="{query}"
 
 
118
  )
119
 
120
+ def get_prompt(self, query: str):
121
+ return self.prompt_without_query.format(query=query)
122
+
123
+ def get_function_call_plan(self, function_call_str: str) -> List[str]:
124
+ function_call_list = []
125
+ locals_to_pass = {"function_call_list": function_call_list}
126
+ for f in FUNCTIONS:
127
+ name = f.name
128
+ exec(
129
+ f"def {name}(**_):\n\tfunction_call_list.append('{f.short_description}')",
130
+ locals_to_pass,
131
+ )
132
+ calls = [c.strip() for c in function_call_str.split(";") if c.strip()]
133
+ [eval(call, locals_to_pass) for call in calls]
134
+ return function_call_list
135
+
136
+ def run_function_call(self, function_call_str: str):
137
+ function_call_list = []
138
+ locals_to_pass = {"function_call_list": function_call_list, "tools": self.tools}
139
+ for f in FUNCTIONS:
140
+ name = f.name
141
+
142
+ locals_to_pass[f"{name}_description_function"] = f.description_function
143
+ locals_to_pass[f"{name}_explanation_function"] = f.explanation_function
144
+
145
+ function_definition = f"""
146
+ def {name}(**kwargs):
147
+ result = tools.{f.name}(**kwargs)
148
+ function_call_list.append(({name}_description_function(**kwargs), {name}_explanation_function(result)))
149
+ return result
150
+ """
151
+ exec(function_definition, locals_to_pass)
152
 
153
+ calls = [c.strip() for c in function_call_str.split(";") if c.strip()]
154
+ for call in calls:
155
+ locals_to_pass["function_call_list"] = function_call_list = []
156
+ result = eval(call, locals_to_pass)
157
+ yield result, function_call_list
158
 
 
 
 
 
 
 
 
 
159
 
160
+ class RavenDemo(gr.Blocks):
161
+ def __init__(self, config: DemoConfig) -> None:
162
+ theme = gr.themes.Soft(
163
+ primary_hue=gr.themes.colors.blue,
164
+ secondary_hue=gr.themes.colors.blue,
165
  )
166
+ super().__init__(theme=theme, css=CSS, title="NexusRaven V2 Demo")
 
 
167
 
168
+ self.config = config
169
+ self.tools = Tools(config)
170
+ self.functions_helper = FunctionsHelper(self.tools)
171
 
172
+ self.raven_client = InferenceClient(
173
+ model=config.raven_endpoint, token=config.hf_token
174
+ )
175
+ self.summary_model_client = InferenceClient(config.summary_model_endpoint)
176
+
177
+ self.max_num_steps = 20
178
+
179
+ with self:
180
+ gr.HTML(HEADER_HTML)
181
+ with gr.Row():
182
+ gr.Image(
183
+ "NexusRaven.png",
184
+ show_label=False,
185
+ show_share_button=True,
186
+ min_width=200,
187
+ scale=1,
188
+ )
189
+ with gr.Column(scale=4, min_width=800):
190
+ gr.Markdown(INTRO_TEXT, elem_classes="inner-large-font")
191
+ with gr.Row():
192
+ examples = [
193
+ gr.Button(query_name) for query_name in EXAMPLE_QUERIES
194
+ ]
195
+
196
+ user_input = gr.Textbox(
197
+ placeholder="Ask me anything!",
198
+ show_label=False,
199
+ autofocus=True,
200
+ )
201
 
202
+ raven_function_call = gr.Code(
203
+ label="πŸ¦β€β¬› NexusRaven V2 13B generated function call",
204
+ language="python",
205
+ interactive=False,
206
+ lines=10,
207
+ )
208
+ with gr.Accordion(
209
+ "Executing plan generated by πŸ¦β€β¬› NexusRaven V2 13B", open=True
210
+ ) as steps_accordion:
211
+ steps = [
212
+ gr.Textbox(visible=False, show_label=False)
213
+ for _ in range(self.max_num_steps)
214
+ ]
215
+
216
+ with gr.Column():
217
+ initial_relevant_places = self.get_relevant_places([])
218
+ relevant_places = gr.State(initial_relevant_places)
219
+ place_dropdown_choices = self.get_place_dropdown_choices(
220
+ initial_relevant_places
221
+ )
222
+ places_dropdown = gr.Dropdown(
223
+ choices=place_dropdown_choices,
224
+ value=place_dropdown_choices[0],
225
+ label="Relevant places",
226
+ )
227
+ gmaps_html = gr.HTML(self.get_gmaps_html(initial_relevant_places[0]))
228
+
229
+ summary_model_summary = gr.Textbox(
230
+ label="Chat summary",
231
+ interactive=False,
232
+ show_copy_button=True,
233
+ lines=10,
234
+ max_lines=1000,
235
+ autoscroll=False,
236
+ elem_classes="inner-large-font",
237
+ )
238
 
239
+ with gr.Accordion("Raven inputs", open=False):
240
+ gr.Textbox(
241
+ label="Available functions",
242
+ value="`" + "`, `".join(f.name for f in FUNCTIONS) + "`",
243
+ interactive=False,
244
+ show_copy_button=True,
245
+ )
246
+ gr.Textbox(
247
+ label="Raven prompt",
248
+ value=self.functions_helper.get_prompt("{query}"),
249
+ interactive=False,
250
+ show_copy_button=True,
251
+ lines=20,
252
+ )
253
+
254
+ user_input.submit(
255
+ fn=self.on_submit,
256
+ inputs=[user_input],
257
+ outputs=[
258
+ user_input,
259
+ raven_function_call,
260
+ summary_model_summary,
261
+ relevant_places,
262
+ places_dropdown,
263
+ gmaps_html,
264
+ steps_accordion,
265
+ *steps,
266
+ ],
267
+ concurrency_limit=20, # not a hyperparameter
268
+ api_name=False,
269
+ )
270
 
271
+ for i, button in enumerate(examples):
272
+ button.click(
273
+ fn=EXAMPLE_QUERIES.get,
274
+ inputs=button,
275
+ outputs=user_input,
276
+ api_name=f"button_click_{i}",
277
+ )
278
+
279
+ places_dropdown.input(
280
+ fn=self.get_gmaps_html_from_dropdown,
281
+ inputs=[places_dropdown, relevant_places],
282
+ outputs=gmaps_html,
283
+ )
284
+
285
+ def on_submit(self, query: str, request: gr.Request):
286
+ def get_returns():
287
+ return (
288
+ user_input,
289
+ raven_function_call,
290
+ summary_model_summary,
291
+ relevant_places,
292
+ places_dropdown,
293
+ gmaps_html,
294
+ steps_accordion,
295
+ *steps,
296
+ )
297
+
298
+ user_input = gr.Textbox(interactive=False)
299
+ raven_function_call = ""
300
+ summary_model_summary = ""
301
+ relevant_places = []
302
+ places_dropdown = ""
303
+ gmaps_html = ""
304
+ steps_accordion = gr.Accordion(open=True)
305
+ steps = [gr.Textbox(value="", visible=False) for _ in range(self.max_num_steps)]
306
+ yield get_returns()
307
+
308
+ raven_prompt = self.functions_helper.get_prompt(
309
+ query.replace("'", r"\'").replace('"', r"\"")
310
  )
311
+ print(f"{'-' * 80}\nPrompt sent to Raven\n\n{raven_prompt}\n\n{'-' * 80}\n")
312
+ stream = self.raven_client.text_generation(
313
+ raven_prompt, **RAVEN_GENERATION_KWARGS
314
+ )
315
+ for s in stream:
316
+ for c in s:
317
+ raven_function_call += c
318
+ raven_function_call = raven_function_call.removesuffix("<bot_end>")
319
+ yield get_returns()
320
 
321
+ print(f"Raw Raven response before formatting: {raven_function_call}")
 
 
 
 
322
 
323
+ r_calls = [c.strip() for c in raven_function_call.split(";") if c.strip()]
324
+ f_r_calls = []
325
+ for r_c in r_calls:
326
+ f_r_call = format_str(r_c.strip(), mode=Mode())
327
+ f_r_calls.append(f_r_call)
328
 
329
+ raven_function_call = "; ".join(f_r_calls)
 
 
 
 
 
 
 
 
 
 
 
 
 
330
 
331
+ yield get_returns()
 
 
 
 
332
 
333
+ self._set_client_ip(request)
334
+ function_call_plan = self.functions_helper.get_function_call_plan(
335
+ raven_function_call
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
336
  )
337
+ for i, v in enumerate(function_call_plan):
338
+ steps[i] = gr.Textbox(value=f"{i+1}. {v}", visible=True)
339
+ yield get_returns()
340
+ sleep(0.1)
341
+
342
+ results_gen = self.functions_helper.run_function_call(raven_function_call)
343
+ results = []
344
+ previous_num_calls = 0
345
+ for result, function_call_list in results_gen:
346
+ results.extend(result)
347
+ for i, (description, explanation) in enumerate(function_call_list):
348
+ i = i + previous_num_calls
349
+
350
+ if len(description) > 100:
351
+ description = function_call_plan[i]
352
+ to_stream = f"{i+1}. {description} ..."
353
+ steps[i] = ""
354
+ for c in to_stream:
355
+ steps[i] += c
356
+ sleep(0.005)
357
+ yield get_returns()
358
+
359
+ to_stream = "." * randint(0, 5)
360
+ for c in to_stream:
361
+ steps[i] += c
362
+ sleep(0.2)
363
+ yield get_returns()
364
+
365
+ to_stream = f" {explanation}"
366
+ for c in to_stream:
367
+ steps[i] += c
368
+ sleep(0.005)
369
+ yield get_returns()
370
+
371
+ previous_num_calls += len(function_call_list)
372
+
373
+ relevant_places = self.get_relevant_places(results)
374
+ gmaps_html = self.get_gmaps_html(relevant_places[0])
375
+ places_dropdown_choices = self.get_place_dropdown_choices(relevant_places)
376
+ places_dropdown = gr.Dropdown(
377
+ choices=places_dropdown_choices, value=places_dropdown_choices[0]
378
+ )
379
+ steps_accordion = gr.Accordion(open=False)
380
+ yield get_returns()
381
+
382
+ while True:
383
+ try:
384
+ summary_model_prompt = self.get_summary_model_prompt(results, query)
385
+ print(
386
+ f"{'-' * 80}\nPrompt sent to summary model\n\n{summary_model_prompt}\n\n{'-' * 80}\n"
387
+ )
388
+ stream = self.summary_model_client.text_generation(
389
+ summary_model_prompt, **SUMMARY_MODEL_GENERATION_KWARGS
390
+ )
391
+ for s in stream:
392
+ for c in s:
393
+ summary_model_summary += c
394
+ summary_model_summary = (
395
+ summary_model_summary.lstrip().removesuffix(
396
+ "<|end_of_turn|>"
397
+ )
398
+ )
399
+ yield get_returns()
400
+ except huggingface_hub.inference._text_generation.ValidationError:
401
+ if len(results) > 1:
402
+ new_length = (3 * len(results)) // 4
403
+ results = results[:new_length]
404
+ continue
405
+ else:
406
+ break
407
+
408
+ break
409
+
410
+ user_input = gr.Textbox(interactive=True)
411
+ yield get_returns()
412
+
413
+ def get_summary_model_prompt(self, results: List, query: str) -> None:
414
+ # TODO check what outputs are returned and return them properly
415
+ ALLOWED_KEYS = [
416
+ "author_name",
417
+ "text",
418
+ "for_location",
419
+ "time",
420
+ "author_url",
421
+ "language",
422
+ "original_language",
423
+ "name",
424
+ "opening_hours",
425
+ "rating",
426
+ "user_ratings_total",
427
+ "vicinity",
428
+ "distance",
429
+ "formatted_address",
430
+ "price_level",
431
+ "types",
432
+ ]
433
+ ALLOWED_KEYS = set(ALLOWED_KEYS)
434
+
435
+ results_str = ""
436
+ for idx, res in enumerate(results):
437
+ if isinstance(res, str):
438
+ results_str += f"{res}\n"
439
  continue
440
 
441
+ assert isinstance(res, dict)
 
 
442
 
443
+ item_str = ""
444
+ for key, value in res.items():
445
+ if key not in ALLOWED_KEYS:
446
+ continue
447
 
448
+ key = key.replace("_", " ").capitalize()
449
+ item_str += f"\t{key}: {value}\n"
450
 
451
+ results_str += f"Result {idx + 1}\n{item_str}\n"
452
+
453
+ current_time = datetime.now().strftime("%b %d, %Y %H:%M:%S")
454
+ current_location = self.tools.get_current_location()
455
+
456
+ prompt = SUMMARY_MODEL_PROMPT.format(
457
+ current_location=current_location,
458
+ current_time=current_time,
459
+ results=results_str,
460
+ query=query,
461
+ )
462
+ return prompt
463
+
464
+ def get_relevant_places(self, results: List) -> List[Tuple[str, str]]:
465
  """
466
+ Returns
467
+ -------
468
+ relevant_places: List[Tuple[str, str]]
469
+ A list of tuples, where each tuple is (address, name)
470
 
 
 
471
  """
472
+ # We use a dict to preserve ordering, while enforcing uniqueness
473
+ relevant_places = dict()
474
+ for result in results:
475
+ if "formatted_address" in result and "name" in result:
476
+ relevant_places[(result["formatted_address"], result["name"])] = None
477
+ elif "formatted_address" in result and "for_location" in result:
478
+ relevant_places[
479
+ (result["formatted_address"], result["for_location"])
480
+ ] = None
481
+ elif "vicinity" in result and "name" in result:
482
+ relevant_places[(result["vicinity"], result["name"])] = None
483
+
484
+ relevant_places = list(relevant_places.keys())
485
+
486
+ if not relevant_places:
487
+ current_location = self.tools.get_current_location()
488
+ relevant_places.append((current_location, current_location))
489
+
490
+ return relevant_places
491
+
492
+ def get_place_dropdown_choices(
493
+ self, relevant_places: List[Tuple[str, str]]
494
+ ) -> List[str]:
495
+ return [p[1] for p in relevant_places]
496
+
497
+ def get_gmaps_html(self, relevant_place: Tuple[str, str]) -> str:
498
+ address, name = relevant_place
499
+ return GMAPS_EMBED_HTML_TEMPLATE.format(
500
+ address=quote(address), location=quote(name)
501
+ )
502
 
503
+ def get_gmaps_html_from_dropdown(
504
+ self, place_name: str, relevant_places: List[Tuple[str, str]]
505
+ ) -> str:
506
+ relevant_place = [p for p in relevant_places if p[1] == place_name][0]
507
+ return self.get_gmaps_html(relevant_place)
508
+
509
+ def _set_client_ip(self, request: gr.Request) -> None:
510
+ client_ip = request.client.host
511
+ if (
512
+ "headers" in request.kwargs
513
+ and "x-forwarded-for" in request.kwargs["headers"]
514
+ ):
515
+ x_forwarded_for = request.kwargs["headers"]["x-forwarded-for"]
516
+ else:
517
+ x_forwarded_for = request.headers.get("x-forwarded-for", None)
518
+ if x_forwarded_for:
519
+ client_ip = x_forwarded_for.split(",")[0].strip()
520
 
521
+ self.tools.client_ip = client_ip
 
 
522
 
 
523
 
524
+ demo = RavenDemo(DemoConfig.load_from_env())
525
 
526
+ if __name__ == "__main__":
527
+ demo.launch(
528
+ share=True,
529
+ allowed_paths=["logo.png", "NexusRaven.png"],
530
+ favicon_path="logo.png",
531
+ )