James MacQuillan commited on
Commit
0f14eb2
·
1 Parent(s): 98c8f98
Files changed (2) hide show
  1. app.py +384 -47
  2. requirements.txt +16 -1
app.py CHANGED
@@ -1,64 +1,401 @@
 
 
 
 
 
 
 
 
1
  import gradio as gr
2
  from huggingface_hub import InferenceClient
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
  """
5
- For more information on `huggingface_hub` Inference API support, please check the docs: https://huggingface.co/docs/huggingface_hub/v0.22.2/en/guides/inference
6
- """
7
- client = InferenceClient("HuggingFaceH4/zephyr-7b-beta")
8
 
 
 
 
9
 
10
- def respond(
11
- message,
12
- history: list[tuple[str, str]],
13
- system_message,
14
- max_tokens,
15
- temperature,
16
- top_p,
17
- ):
18
- messages = [{"role": "system", "content": system_message}]
19
 
20
- for val in history:
21
- if val[0]:
22
- messages.append({"role": "user", "content": val[0]})
23
- if val[1]:
24
- messages.append({"role": "assistant", "content": val[1]})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
- messages.append({"role": "user", "content": message})
27
 
28
- response = ""
 
 
 
 
 
 
 
29
 
30
- for message in client.chat_completion(
31
- messages,
32
- max_tokens=max_tokens,
33
- stream=True,
34
- temperature=temperature,
35
- top_p=top_p,
36
- ):
37
- token = message.choices[0].delta.content
38
 
39
- response += token
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  yield response
 
41
 
42
 
43
- """
44
- For information on how to customize the ChatInterface, peruse the gradio docs: https://www.gradio.app/docs/chatinterface
45
- """
46
- demo = gr.ChatInterface(
47
- respond,
48
- additional_inputs=[
49
- gr.Textbox(value="You are a friendly Chatbot.", label="System message"),
50
- gr.Slider(minimum=1, maximum=2048, value=512, step=1, label="Max new tokens"),
51
- gr.Slider(minimum=0.1, maximum=4.0, value=0.7, step=0.1, label="Temperature"),
52
- gr.Slider(
53
- minimum=0.1,
54
- maximum=1.0,
55
- value=0.95,
56
- step=0.05,
57
- label="Top-p (nucleus sampling)",
58
- ),
59
- ],
60
  )
61
 
62
 
63
- if __name__ == "__main__":
64
- demo.launch()
 
 
 
 
 
 
 
1
+ from googlesearch import search
2
+ import requests
3
+ import trafilatura
4
+ import openai
5
+ from openai import OpenAI
6
+ from concurrent.futures import ThreadPoolExecutor
7
+ import json
8
+ import ast
9
  import gradio as gr
10
  from huggingface_hub import InferenceClient
11
+ import tiktoken
12
+ import time
13
+ import os
14
+ from PIL import Image
15
+ client = InferenceClient(api_key=os.getenv('HF_TOKEN'))
16
+
17
+
18
+ import tiktoken
19
+ import requests
20
+ import os
21
+ import json
22
+ import random
23
+ from huggingface_hub import InferenceClient
24
+
25
+
26
+
27
+ def upload_to_catbox(file_path):
28
+ """
29
+ Upload a file to Catbox and return the URL.
30
+
31
+ Args:
32
+ file_path (str): Path to the file to upload.
33
+
34
+ Returns:
35
+ str: URL of the uploaded file.
36
+ """
37
+ with open(file_path, "rb") as file:
38
+ response = requests.post(
39
+ "https://catbox.moe/user/api.php",
40
+ data={"reqtype": "fileupload"},
41
+ files={"fileToUpload": file}
42
+ )
43
+ if response.status_code == 200:
44
+ return response.text.strip()
45
+ else:
46
+ return "Failed to upload file."
47
+
48
+
49
+ # Set your Hugging Face API key here
50
+
51
+
52
+ # Sample data stored in a variable
53
+
54
+
55
+ def generate_quickchart_config(user_input, data):
56
+ """
57
+ Use Hugging Face InferenceClient to determine if a chart is needed and generate its configuration.
58
+
59
+ Args:
60
+ user_input (str): The user's question or description of the desired analysis.
61
+ data (dict): Sample data containing stock prices and dates.
62
+
63
+ Returns:
64
+ dict or None: QuickChart configuration parameters generated by the model, or None if no chart is requested.
65
+ """
66
+ prompt = f"""
67
+ You are given the following stock price data:
68
+
69
+ {data}
70
+
71
+ Based on the user question: "{user_input}", determine if a chart is required to answer the question.
72
+ If a chart is required, generate a valid QuickChart configuration in the following JSON format:
73
+ {{
74
+ "type": <chart type>,
75
+ "data": {{
76
+ "labels": <x-axis labels>,
77
+ "datasets": [
78
+ {{
79
+ "label": <dataset label>,
80
+ "data": <y-axis data>,
81
+ "borderColor": <color>,
82
+ "fill": <boolean>
83
+ }}
84
+ ]
85
+ }},
86
+ "options": {{
87
+ "title": {{
88
+ "display": true,
89
+ "text": <chart title>
90
+ }},
91
+ "scales": {{
92
+ "xAxes": [{{"scaleLabel": {{"display": true, "labelString": "Date"}}}}],
93
+ "yAxes": [{{"scaleLabel": {{"display": true, "labelString": "Price ($)"}}}}]
94
+ }}
95
+ }}
96
+ }}
97
+ If a chart is not needed, respond with ONLY the word "NO". for example if they ask: make a line chart with cisco stock price over the last days, make the chart
98
+ """
99
+ messages = [
100
+ {"role": "user", "content": prompt}
101
+ ]
102
+
103
+ # Make the inference request
104
+ completion = client.chat.completions.create(
105
+ model="Qwen/Qwen2.5-Coder-32B-Instruct", # Replace with your specific model
106
+ messages=messages,
107
+ max_tokens=2000
108
+ )
109
+ response_content = completion.choices[0].message["content"].strip()
110
+ print(f"Model response: {response_content}")
111
+
112
+ if response_content.lower() == "no":
113
+ print("No chart is required for this question.")
114
+ return None
115
+ else:
116
+ try:
117
+ return json.loads(response_content) # Convert JSON string to Python dictionary
118
+ except Exception as e:
119
+ print("Error parsing JSON from model response:", e)
120
+ return None
121
+
122
+ def generate_chart(config, filename=f"chart.png", output_dir="charts"):
123
+ """
124
+ Generate a chart using QuickChart.io and save it locally.
125
+
126
+ Args:
127
+ config (dict): QuickChart configuration.
128
+ filename (str): Name of the output file (e.g., 'chart.png').
129
+ output_dir (str): Directory to save the chart image.
130
+
131
+ Returns:
132
+ str: Path to the saved chart file.
133
+ """
134
+ # QuickChart API URL
135
+ url = "https://quickchart.io/chart"
136
+
137
+ # Make the output directory if it doesn't exist
138
+ if not os.path.exists(output_dir):
139
+ os.makedirs(output_dir)
140
+
141
+ # Send the request to QuickChart API
142
+ response = requests.post(url, json={"c": config})
143
+
144
+ if response.status_code == 200:
145
+ # Save the chart image
146
+ file_path = os.path.join(output_dir, filename)
147
+ with open(file_path, "wb") as file:
148
+ file.write(response.content)
149
+ print(f"Chart saved at {file_path}")
150
+ return file_path
151
+ else:
152
+ print(f"Failed to generate chart: {response.text}")
153
+ return None
154
+
155
+ # Main execution flow
156
+
157
+
158
+
159
+
160
+ dots_animation = [
161
+ "Working on your response.",
162
+ "Working on your response..",
163
+ "Working on your response...",
164
+ ]
165
+
166
+ arrow_animation = [
167
+ "----> Preparing your answer",
168
+ "---> Preparing your answer",
169
+ "--> Preparing your answer",
170
+ "-> Preparing your answer",
171
+ "> Preparing your answer",
172
+ ]
173
+
174
+ loader_animation = [
175
+ "[ ] Fetching data...",
176
+ "[= ] Fetching data...",
177
+ "[== ] Fetching data...",
178
+ "[=== ] Fetching data...",
179
+ "[====] Fetching data...",
180
+ ]
181
+
182
+ typing_animation = [
183
+ "Bot is typing.",
184
+ "Bot is typing..",
185
+ "Bot is typing...",
186
+ ]
187
+ rotating_text_animation = [
188
+ "Working |",
189
+ "Working /",
190
+ "Working -",
191
+ "Working \\",
192
+ ]
193
+
194
+
195
+
196
+
197
+ def tokenize_with_qwen(text):
198
+ """
199
+ Tokenizes the input text using a compatible tokenizer for Qwen models and returns a string of tokens.
200
+
201
+ Parameters:
202
+ text (list or str): The text (or list of strings) to be tokenized.
203
+
204
+ Returns:
205
+ str: A single string of tokens, truncated to 32,500 tokens if necessary.
206
+ """
207
+ # Ensure input is a string (concatenate if it's a list)
208
+ if isinstance(text, list):
209
+ text = ''.join(text)
210
+ elif not isinstance(text, str):
211
+ raise ValueError("Input must be a string or a list of strings.")
212
+
213
+ # Use a base encoding like cl100k_base for GPT-style tokenization
214
+ encoding = tiktoken.get_encoding("cl100k_base")
215
+
216
+ # Tokenize the text into token IDs
217
+ token_ids = encoding.encode(text)
218
+
219
+ # Decode each token ID into its string representation
220
+ token_strings = [encoding.decode_single_token_bytes(token_id).decode('utf-8', errors='replace') for token_id in token_ids]
221
+
222
+ # Truncate if the number of tokens exceeds 32,500
223
+ if len(token_strings) > 23000:
224
+ token_strings = token_strings[:17000]
225
+
226
+ # Join tokens back into a single string
227
+ stringed_tokens = ''.join(token_strings)
228
+ return stringed_tokens
229
+
230
+
231
+
232
+
233
+
234
+ def fetch_and_process_url(link):
235
+ try:
236
+ # Fetch URL content
237
+ req = requests.get(link, headers={"User-Agent": "Mozilla/5.0"}, timeout=10)
238
+ html_content = req.text # Use raw HTML directly
239
+ # Extract main content using trafilatura
240
+ return trafilatura.extract(html_content)
241
+ except Exception as e:
242
+ return f"Error fetching or processing {link}: {e}"
243
+
244
+ def perform_search(query, num_results=5):
245
+ try:
246
+ # Perform Google search
247
+ urls = [url for url in search(query, num_results=num_results)]
248
+ print("URLs Found:")
249
+ print(urls)
250
+ except Exception as e:
251
+ print(f"An error occurred during search: {e}")
252
+ return
253
+
254
+ # Fetch and process URLs in parallel
255
+ with ThreadPoolExecutor(max_workers=30) as executor:
256
+ results = list(executor.map(fetch_and_process_url, urls))
257
+
258
+ # Combine results into a single formatted output
259
+ formatted_text = '\n\n'.join(filter(None, results)) # Skip None or empty results
260
+ return formatted_text
261
+
262
+ def chat(user_input,history):
263
+
264
+ format_template = examples = """
265
+
266
+ {"user_input": "cisco systems stock price for the last 4 days", "searches": ["cisco stock price last 4 days", "cisco systems stock historical data", "current price of Cisco Systems", "cisco stock price chart"]},
267
+ {"user_input": "Apple stock price yesterday", "searches": ["Apple stock price yesterday", "historical price of Apple stock"]},
268
+ {"user_input": "Tesla quarterly revenue", "searches": ["Tesla latest quarterly revenue", "Tesla revenue report Q3 2024"]},
269
+ {"user_input": "CAPM model for Tesla", "searches": ["Tesla stock beta value", "current risk-free rate", "expected market return for CAPM model"]},
270
+ {"user_input": "Hi", "searches": []},
271
+ {"user_input": "Who are you?", "searches": []},
272
+ {"user_input": "Google earnings per share last quarter", "searches": ["Google EPS last quarter", "Google quarterly earnings report"]},
273
+ {"user_input": "Calculate WACC for Microsoft", "searches": ["Microsoft cost of equity", "Microsoft cost of debt", "Microsoft capital structure", "current risk-free rate", "Microsoft beta"]},
274
+ {"user_input": "Show Amazon stock chart for last 5 years", "searches": ["Amazon stock chart last 5 years", "Amazon historical price data"]},
275
+ {"user_input": "GDP of China in 2023", "searches": ["China GDP 2023", "latest GDP figures for China"]},
276
+ {"user_input": "Portfolio optimization model", "searches": ["efficient frontier portfolio theory", "input data for portfolio optimization model", "expected returns and covariances"]},
277
+ {"user_input": "Find current inflation rate in the US", "searches": ["current US inflation rate", "US CPI data"]},
278
+ {"user_input": "What is NPV and how do you calculate it?", "searches": ["definition of NPV", "how to calculate NPV"]},
279
+ {"user_input": "Dividend yield for Coca-Cola", "searches": ["Coca-Cola dividend yield", "latest Coca-Cola dividend data"]},
280
+ {"user_input": "Sharpe ratio formula example", "searches": ["Sharpe ratio formula", "example calculation of Sharpe ratio"]},
281
+ {"user_input": "What is the current Fed interest rate?", "searches": ["current Federal Reserve interest rate", "latest Fed interest rate decision"]},
282
+ {"user_input": "Generate DCF model for Tesla", "searches": ["Tesla free cash flow data", "Tesla growth rate projections", "current discount rate for Tesla", "steps to build a DCF model"]},
283
+ {"user_input": "Tell me a joke", "searches": []},
284
+ {"user_input": "Explain the concept of opportunity cost", "searches": ["definition of opportunity cost", "examples of opportunity cost in economics"]}
285
 
286
  """
 
 
 
287
 
288
+
289
+
290
+ search_messages = history or [{'role': 'system', 'content': 'you are IM.FIN'}]
291
 
292
+ print(f'here is the search messages: \n\n\n\n {search_messages} \n\n\n')
293
+ search_messages.append({'role':'user','content':f'based on {user_input} and {search_messages}, respond with a list of google searches that will give the correct data to respond, respond in this format: {format_template} with up to 3 searches but try and limit it to the minimum needed. RETURN 1 DICTIONARY IN THE SPECIFIED FORMAT BASED ON THE USER INPUT {user_input}. RETURN ABSOLUTELY NO OTHER TEXT OTHER THAN THE DICTIONARY WITH THE SEARCHES. here is the history use it {search_messages}'})
294
+ for value in dots_animation:
295
+ yield value
296
+ response_for_searches = client.chat.completions.create(
297
+ model='Qwen/Qwen2.5-72B-Instruct',
 
 
 
298
 
299
+ messages=search_messages
300
+ )
301
+ searches_resp = response_for_searches.choices[0].message.content
302
+ yield dots_animation[1]
303
+ print(f'search model response: {searches_resp}')
304
+ searches = ast.literal_eval(searches_resp)
305
+ search_messages.append(searches)
306
+
307
+ print(searches)
308
+ yield arrow_animation[0]
309
+ summary_messages = [
310
+ {'role':'system','content':'you are IM.FIN'}
311
+ ]
312
+
313
+ var = [perform_search(search) for search in searches['searches']]
314
+ yield arrow_animation[1]
315
+
316
+ var = tokenize_with_qwen(var)
317
+ yield arrow_animation[2]
318
+ print(f'the type of var is {type(var)}')
319
+ var = ''.join(var)
320
+ print(f'the data: {var}')
321
 
322
+
323
 
324
+
325
+ yield arrow_animation[3]
326
+ summary_messages.append({'role':'user','content':f'use {user_input} to summarize {var}, return nothing other than the summarized response. MAKE SURE TO PICK OUT THE NUMERICAL DATA BASED ON THE USER RESPONSE'})
327
+ for value in arrow_animation:
328
+ time.sleep(1)
329
+ yield value
330
+ response_for_chat = client.chat.completions.create(
331
+ model='Qwen/Qwen2.5-72B-Instruct',
332
 
333
+ messages=summary_messages,
334
+ max_tokens=2000
335
+ )
 
 
 
 
 
336
 
337
+ summary = response_for_chat.choices[0].message.content
338
+ chart_url = 'nonethereyet'
339
+ ### possible chart generation
340
+
341
+ name_of_file = f"dynamic_chart{random.randint(0,1000)}.png"
342
+ config = generate_quickchart_config(user_input, summary)
343
+ if config:
344
+ generate_chart(config, filename=name_of_file)
345
+ else:
346
+ print("No chart was generated.")
347
+ image_path = f'{name_of_file}'
348
+ chart_url = upload_to_catbox(f'charts/{image_path}')
349
+
350
+ for value in arrow_animation:
351
+
352
+ yield value
353
+ final_messages = [
354
+ {'role':'system','content':'you are IM.FIN, you are a virtual stock analyst built to automate investing tasks and simulate the intelligence of stock analysts, you can form opinions based on data and form conclusions like stock analysts, you were created by quantineuron.com. KEEP RESPONSES CONCISE, ANSWERING THE USERS INPUT '}
355
+ ]
356
+
357
+ print(f'here is the summary: {summary}')
358
+ final_messages.append({'role':'user','content': f'based on this data {summary}, answer {user_input}, here is the history {final_messages}. ONLY USE THE DATA THAT IS NEEDED AND ACT AS THOUGH THAT DATA IS YOURS AND CORRECT. KEEP RESPONSES CONCISE. IF THE DATA PROVIDED IS NOT RELEVANT TO THE USERS REQUEST, IGNORE IT AND ANSWER NORMALLY. IF THE USER ASKS FOR ANY TYPE OF CHART, DO NOT ATTEMPT TO MAKE IT YOURSELF, SIMPLY MAKE A TABLE WITH THE DATA, THE CHART WILL BE FOUND AT THIS URL: {chart_url} SO SAY TO THE USER IT IS THERE AND LINK IT TO THEM, IF THEY HAVE NOT ASKED FOR A CHART, DO NOT INCLUDE THE URL IN YOUR RESPONSE '})
359
+ yield typing_animation[0]
360
+ final_response = client.chat.completions.create(
361
+ model='Qwen/Qwen2.5-72B-Instruct',
362
+
363
+ messages=final_messages,
364
+ max_tokens=2000,
365
+ stream=True
366
+ )
367
+ yield typing_animation[1]
368
+ response = ""
369
+ for chunk in final_response:
370
+ content = chunk.choices[0].delta.content or ''
371
+ response += content
372
+
373
  yield response
374
+
375
 
376
 
377
+ final_messages.append(response)
378
+ search_messages.append(response)
379
+
380
+ print(f'\n\n here is the chat history for the final response \n\n\n {response}')
381
+
382
+
383
+
384
+
385
+ avatar = 'https://quantineuron.com/wp-content/uploads/2024/08/cropped-final-logo-with-background-removed.png'
386
+
387
+
388
+ theme = gr.themes.Soft(
389
+ primary_hue="sky",
390
+ neutral_hue="zinc",
 
 
 
391
  )
392
 
393
 
394
+
395
+ # Add the CSS to the ChatInterface
396
+ gr.ChatInterface(
397
+
398
+
399
+ fn=chat,
400
+ theme=theme
401
+ ).launch()
requirements.txt CHANGED
@@ -1 +1,16 @@
1
- huggingface_hub==0.25.2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ huggingface_hub==0.25.2
2
+ huggingface_hub==0.25.2
3
+ sentence-transformers
4
+ gradio
5
+ beautifulsoup4
6
+ requests
7
+ langchain
8
+ chromadb
9
+ numpy
10
+ scikit-learn
11
+ plotly
12
+ langchain-community
13
+ googlesearch-python
14
+ trafilatura
15
+ tiktoken
16
+ lxml_html_clean