mayank1101
commited on
Upload 7 files
Browse files- gradio_app.py +180 -0
- main.py +82 -0
- model_registry.py +26 -0
- requirements.txt +18 -0
- utils.py +49 -0
- websearch.py +99 -0
- websites.yaml +135 -0
gradio_app.py
ADDED
@@ -0,0 +1,180 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# import gradio as gr
|
2 |
+
# import httpx
|
3 |
+
# import json
|
4 |
+
# from typing import Tuple, Any
|
5 |
+
|
6 |
+
# # Define the FastAPI endpoint URL
|
7 |
+
# FASTAPI_ENDPOINT = "http://localhost:8000/websearch"
|
8 |
+
|
9 |
+
|
10 |
+
# def query_api(query: str) -> Tuple[Any, Any]:
|
11 |
+
# try:
|
12 |
+
# # Send POST request to FastAPI endpoint with streaming enabled
|
13 |
+
# with httpx.Client() as client:
|
14 |
+
# with client.stream("POST", FASTAPI_ENDPOINT, json={"query": query}, timeout=60.0) as response:
|
15 |
+
# response.raise_for_status() # Raise an exception for 4xx or 5xx status codes
|
16 |
+
|
17 |
+
# # Process the streaming response
|
18 |
+
# response_data = ""
|
19 |
+
# for chunk in response.iter_text():
|
20 |
+
# response_data += chunk
|
21 |
+
|
22 |
+
# # Parse the accumulated response data as JSON
|
23 |
+
# response_json = json.loads(response_data)
|
24 |
+
|
25 |
+
# # Extract content and citations from the response JSON
|
26 |
+
# content = response_json.get("content", "")
|
27 |
+
# citations = response_json.get("citations", [])
|
28 |
+
|
29 |
+
# # Beautify content using Markdown formatting
|
30 |
+
# beautified_content = f"# Search Results\n\n{content}"
|
31 |
+
|
32 |
+
# # Beautify citations by adding Markdown links
|
33 |
+
# beautified_citations = "# Citations\n\n"
|
34 |
+
# for i, citation in enumerate(citations, start=1):
|
35 |
+
# beautified_citations += f"{i}. [{citation}]({citation})\n"
|
36 |
+
|
37 |
+
# # Yield the beautified content and citations
|
38 |
+
# yield beautified_content, beautified_citations
|
39 |
+
# except httpx.TimeoutException:
|
40 |
+
# yield "Request timed out. Please try again later.", ""
|
41 |
+
# except httpx.HTTPStatusError as e:
|
42 |
+
# yield f"HTTP error occurred: {e}", ""
|
43 |
+
# except Exception as e:
|
44 |
+
# yield f"An error occurred: {e}", ""
|
45 |
+
|
46 |
+
|
47 |
+
# # Create Gradio interface
|
48 |
+
# with gr.Blocks(css=".gradio-container { background-color: #f5f5f5; padding: 20px; border-radius: 10px; }") as demo:
|
49 |
+
# gr.Markdown("# Web Search Application")
|
50 |
+
|
51 |
+
# with gr.Row():
|
52 |
+
# with gr.Column():
|
53 |
+
# query = gr.Textbox(
|
54 |
+
# label="Enter your query",
|
55 |
+
# placeholder="Type your search query here...",
|
56 |
+
# lines=2,
|
57 |
+
# max_lines=4,
|
58 |
+
# value="",
|
59 |
+
# elem_id="query-input"
|
60 |
+
# )
|
61 |
+
# submit_button = gr.Button("Search")
|
62 |
+
|
63 |
+
# with gr.Column():
|
64 |
+
# output_content = gr.Textbox(
|
65 |
+
# label="Response Content",
|
66 |
+
# placeholder="Search results will appear here...",
|
67 |
+
# lines=10,
|
68 |
+
# max_lines=20,
|
69 |
+
# value="",
|
70 |
+
# elem_id="response-content"
|
71 |
+
# )
|
72 |
+
# output_citations = gr.Textbox(
|
73 |
+
# label="Citations",
|
74 |
+
# placeholder="Citations will appear here...",
|
75 |
+
# lines=5,
|
76 |
+
# max_lines=10,
|
77 |
+
# value="",
|
78 |
+
# elem_id="response-citations"
|
79 |
+
# )
|
80 |
+
|
81 |
+
# # Set up event listener
|
82 |
+
# submit_button.click(query_api, inputs=query, outputs=[output_content, output_citations])
|
83 |
+
|
84 |
+
# gr.Markdown("Powered by FastAPI and Gradio")
|
85 |
+
|
86 |
+
# # Launch the Gradio application
|
87 |
+
# demo.launch()
|
88 |
+
|
89 |
+
|
90 |
+
import gradio as gr
|
91 |
+
import httpx
|
92 |
+
import json
|
93 |
+
|
94 |
+
# Define the FastAPI endpoint URL
|
95 |
+
FASTAPI_ENDPOINT = "http://localhost:8000/websearch"
|
96 |
+
|
97 |
+
def query_api(query: str) -> tuple:
|
98 |
+
try:
|
99 |
+
# Send POST request to FastAPI endpoint with streaming enabled
|
100 |
+
with httpx.Client() as client:
|
101 |
+
with client.stream("POST", FASTAPI_ENDPOINT, json={"query": query}, timeout=60.0) as response:
|
102 |
+
response.raise_for_status() # Raise an exception for 4xx or 5xx status codes
|
103 |
+
|
104 |
+
# Process the streaming response
|
105 |
+
response_data = ""
|
106 |
+
for chunk in response.iter_text():
|
107 |
+
response_data += chunk
|
108 |
+
|
109 |
+
# Parse the accumulated response data as JSON
|
110 |
+
response_json = json.loads(response_data)
|
111 |
+
|
112 |
+
# Extract content and citations from the response JSON
|
113 |
+
content = response_json.get("content", "")
|
114 |
+
citations = response_json.get("citations", [])
|
115 |
+
|
116 |
+
# Beautify content using Markdown formatting
|
117 |
+
beautified_content = f"# Search Results\n\n{content}"
|
118 |
+
|
119 |
+
# Beautify citations by adding Markdown links
|
120 |
+
beautified_citations = "# Citations/Sources\n\n"
|
121 |
+
for i, citation in enumerate(citations, start=1):
|
122 |
+
beautified_citations += f"{i}. [{citation}]({citation})\n"
|
123 |
+
|
124 |
+
# Yield the beautified content and citations
|
125 |
+
yield beautified_content, beautified_citations
|
126 |
+
except httpx.TimeoutException:
|
127 |
+
yield "# Request Timeout\n\nRequest timed out. Please try again later.", ""
|
128 |
+
except httpx.HTTPStatusError as e:
|
129 |
+
yield f"# HTTP Error\n\nHTTP error occurred: {e}", ""
|
130 |
+
except Exception as e:
|
131 |
+
yield f"# Error\n\nAn error occurred: {e}", ""
|
132 |
+
|
133 |
+
# Create Gradio interface
|
134 |
+
with gr.Blocks(css=".gradio-container { background-color: #f5f5f5; padding: 20px; border-radius: 10px; }", theme=gr.themes.Citrus()) as demo:
|
135 |
+
gr.Markdown("# Web Search Application")
|
136 |
+
|
137 |
+
with gr.Row():
|
138 |
+
with gr.Column(
|
139 |
+
render=True,
|
140 |
+
show_progress=True
|
141 |
+
):
|
142 |
+
query = gr.Textbox(
|
143 |
+
label="Enter your query",
|
144 |
+
placeholder="Type your search query here...",
|
145 |
+
lines=2,
|
146 |
+
max_lines=4,
|
147 |
+
value="",
|
148 |
+
elem_id="query-input"
|
149 |
+
)
|
150 |
+
submit_button = gr.Button("Search")
|
151 |
+
|
152 |
+
with gr.Column(
|
153 |
+
render=True,
|
154 |
+
show_progress=True
|
155 |
+
):
|
156 |
+
output_content = gr.Markdown(
|
157 |
+
label="Response Content",
|
158 |
+
value="",
|
159 |
+
elem_id="response-content",
|
160 |
+
height="600px",
|
161 |
+
visible=True,
|
162 |
+
show_label=True
|
163 |
+
|
164 |
+
)
|
165 |
+
output_citations = gr.Markdown(
|
166 |
+
label="Citations",
|
167 |
+
value="",
|
168 |
+
elem_id="response-citations",
|
169 |
+
height="200px",
|
170 |
+
visible=True,
|
171 |
+
show_label=True
|
172 |
+
)
|
173 |
+
|
174 |
+
# Set up event listener
|
175 |
+
submit_button.click(query_api, inputs=query, outputs=[output_content, output_citations])
|
176 |
+
|
177 |
+
gr.Markdown("Powered by FastAPI and Gradio")
|
178 |
+
|
179 |
+
# Launch the Gradio application
|
180 |
+
demo.launch()
|
main.py
ADDED
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import json
|
3 |
+
import asyncio
|
4 |
+
from typing import AsyncGenerator
|
5 |
+
|
6 |
+
from fastapi.middleware.cors import CORSMiddleware
|
7 |
+
from fastapi.responses import StreamingResponse
|
8 |
+
from fastapi import FastAPI, HTTPException
|
9 |
+
|
10 |
+
from websearch import QueryRequest, PerplexityClient, parse_perplexity_response
|
11 |
+
|
12 |
+
# load .env file
|
13 |
+
from dotenv import load_dotenv
|
14 |
+
load_dotenv()
|
15 |
+
|
16 |
+
# Initialize FastAPI app
|
17 |
+
app = FastAPI()
|
18 |
+
|
19 |
+
# Add CORS middleware to allow frontend connections
|
20 |
+
app.add_middleware(
|
21 |
+
CORSMiddleware,
|
22 |
+
allow_origins=["*"], # Adjust this in production
|
23 |
+
allow_credentials=True,
|
24 |
+
allow_methods=["*"],
|
25 |
+
allow_headers=["*"],
|
26 |
+
)
|
27 |
+
|
28 |
+
# Initialize Perplexity client
|
29 |
+
perplexity_client = PerplexityClient(
|
30 |
+
api_key=os.environ["PERPLEXITY_AUTH_TOKEN"]
|
31 |
+
)
|
32 |
+
|
33 |
+
|
34 |
+
async def generate_stream(query: str) -> AsyncGenerator[str, None]:
|
35 |
+
"""
|
36 |
+
Async generator to stream JSON response
|
37 |
+
|
38 |
+
Args:
|
39 |
+
query (str): User query
|
40 |
+
|
41 |
+
Yields:
|
42 |
+
str: JSON-encoded chunks of response
|
43 |
+
"""
|
44 |
+
try:
|
45 |
+
# Fetch response from Perplexity
|
46 |
+
response = await perplexity_client.generate_response(query)
|
47 |
+
|
48 |
+
# Parse the response
|
49 |
+
parsed_response = parse_perplexity_response(response)
|
50 |
+
|
51 |
+
# Stream the parsed response as JSON chunks
|
52 |
+
yield json.dumps(parsed_response)
|
53 |
+
|
54 |
+
except Exception as e:
|
55 |
+
yield json.dumps({"error": str(e)})
|
56 |
+
|
57 |
+
|
58 |
+
@app.post("/websearch")
|
59 |
+
async def handle_query(request: QueryRequest):
|
60 |
+
"""
|
61 |
+
Endpoint to handle user queries and stream responses
|
62 |
+
|
63 |
+
Args:
|
64 |
+
request (QueryRequest): Query request model
|
65 |
+
|
66 |
+
Returns:
|
67 |
+
StreamingResponse: Streaming JSON response
|
68 |
+
"""
|
69 |
+
return StreamingResponse(
|
70 |
+
generate_stream(request.query),
|
71 |
+
media_type="application/json"
|
72 |
+
)
|
73 |
+
|
74 |
+
|
75 |
+
# Optional: Health check endpoint
|
76 |
+
@app.get("/health")
|
77 |
+
async def health_check():
|
78 |
+
return {"status": "healthy"}
|
79 |
+
|
80 |
+
# Run the app with:
|
81 |
+
# uvicorn main:app --reload
|
82 |
+
# Make sure to set PERPLEXITY_API_KEY environment variable
|
model_registry.py
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from utils import get_current_date, get_websites
|
2 |
+
|
3 |
+
current_date = get_current_date() # returns current date
|
4 |
+
websites = get_websites() # returns list of websites
|
5 |
+
|
6 |
+
perplexity_prompt = f"""
|
7 |
+
You are an advanced information retrieval assistant. Your primary task is to provide accurate and concise answers to user queries. You must refer to the following LIST OF WEBSITES to retrieve the context required to answer the user query.
|
8 |
+
|
9 |
+
LIST OF WEBSITES TO REFER - {', '.join(websites)}
|
10 |
+
|
11 |
+
|
12 |
+
IMPORTANT NOTE - You must always include citations source URLs in the response for user transparency. Provide information that is relevant and up-to-date as of this {current_date} date.
|
13 |
+
"""
|
14 |
+
|
15 |
+
|
16 |
+
model_card = {
|
17 |
+
"perplexity": {
|
18 |
+
"model": "llama-3.1-sonar-large-128k-online",
|
19 |
+
"url": "https://api.perplexity.ai/chat/completions",
|
20 |
+
"inference_config": {
|
21 |
+
"max_tokens": 4094,
|
22 |
+
"temperature": 0
|
23 |
+
},
|
24 |
+
"prompt": perplexity_prompt
|
25 |
+
}
|
26 |
+
}
|
requirements.txt
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
python-dotenv==1.0.1
|
2 |
+
langchain==0.3.7
|
3 |
+
langchain-community==0.3.7
|
4 |
+
langchain-aws==0.2.7
|
5 |
+
tavily-python==0.5.0
|
6 |
+
langchain-qdrant==0.2.0
|
7 |
+
langchain-openai==0.2.9
|
8 |
+
langchain-ollama==0.2.0
|
9 |
+
# langchain-mistralai==0.2.2
|
10 |
+
# langchain-huggingface==0.1.2
|
11 |
+
faiss-cpu==1.9.0.post1
|
12 |
+
rapidocr-onnxruntime==1.4.0
|
13 |
+
fastapi==0.115.5
|
14 |
+
httpx==0.28.0
|
15 |
+
uvicorn==0.32.1
|
16 |
+
pydantic==2.10.2
|
17 |
+
aiofiles==23.2.1 # for gradio 24.1.0 for unstructured
|
18 |
+
gradio==5.8.0
|
utils.py
ADDED
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import yaml
|
3 |
+
import datetime
|
4 |
+
from typing import Dict, List
|
5 |
+
|
6 |
+
|
7 |
+
def get_current_date() -> str:
|
8 |
+
"""returns present date.
|
9 |
+
|
10 |
+
Returns:
|
11 |
+
str: return present date as a string.
|
12 |
+
"""
|
13 |
+
current_date = datetime.date.today().strftime("%Y-%m-%d") # provide current date to LLM context
|
14 |
+
return current_date
|
15 |
+
|
16 |
+
|
17 |
+
def read_yaml(file_path: str) -> Dict:
|
18 |
+
"""_summary_
|
19 |
+
|
20 |
+
Args:
|
21 |
+
file_path (str): wesites.yaml file path
|
22 |
+
|
23 |
+
Raises:
|
24 |
+
ValueError: raise error is file_path is empty or if websites.yaml file is missing.
|
25 |
+
|
26 |
+
Returns:
|
27 |
+
Dict: return List websites to be used for websearch.
|
28 |
+
"""
|
29 |
+
websites_yaml = None
|
30 |
+
if not read_yaml:
|
31 |
+
raise ValueError("Website yaml config file missing")
|
32 |
+
return websites_yaml
|
33 |
+
else:
|
34 |
+
with open(file_path, 'r') as file:
|
35 |
+
websites_yaml = yaml.load(file, Loader=yaml.SafeLoader) # reads .yaml file
|
36 |
+
return websites_yaml
|
37 |
+
|
38 |
+
|
39 |
+
def get_websites() -> List[str]:
|
40 |
+
"""reads websites.yaml file and return list of webistes
|
41 |
+
|
42 |
+
Returns:
|
43 |
+
List[str]: List of websites
|
44 |
+
"""
|
45 |
+
file_path = os.path.join(os.getcwd(), 'websites.yaml') # get websites.yaml file path
|
46 |
+
websites = read_yaml(file_path) # read wesbites.yaml file
|
47 |
+
if not websites:
|
48 |
+
return []
|
49 |
+
return websites['public_websites'] # return list of public files
|
websearch.py
ADDED
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import asyncio
|
2 |
+
from typing import Dict, Any
|
3 |
+
|
4 |
+
import httpx
|
5 |
+
from pydantic import BaseModel
|
6 |
+
|
7 |
+
from model_registry import model_card
|
8 |
+
|
9 |
+
perpelxity_card = model_card["perplexity"]
|
10 |
+
|
11 |
+
|
12 |
+
class QueryRequest(BaseModel):
|
13 |
+
query: str
|
14 |
+
|
15 |
+
|
16 |
+
class PerplexityClient:
|
17 |
+
def __init__(self, api_key: str):
|
18 |
+
self.api_key = api_key
|
19 |
+
self.base_url = perpelxity_card["url"]
|
20 |
+
self.headers = {
|
21 |
+
"Authorization": f"Bearer {api_key}",
|
22 |
+
"Content-Type": "application/json"
|
23 |
+
}
|
24 |
+
|
25 |
+
async def generate_response(self, query: str) -> Dict[str, Any]:
|
26 |
+
payload = {
|
27 |
+
"model": perpelxity_card["model"],
|
28 |
+
"messages": [
|
29 |
+
{
|
30 |
+
"role": "system",
|
31 |
+
"content": perpelxity_card["prompt"]
|
32 |
+
},
|
33 |
+
{
|
34 |
+
"role": "user",
|
35 |
+
"content": query
|
36 |
+
}
|
37 |
+
],
|
38 |
+
"max_tokens": perpelxity_card["inference_config"]["max_tokens"],
|
39 |
+
"temperature": perpelxity_card["inference_config"]["temperature"],
|
40 |
+
"stream": False, # We'll handle streaming separately
|
41 |
+
}
|
42 |
+
|
43 |
+
async with httpx.AsyncClient() as client:
|
44 |
+
try:
|
45 |
+
async with httpx.AsyncClient(
|
46 |
+
timeout=httpx.Timeout(
|
47 |
+
connect=10.0, # Connection timeout
|
48 |
+
read=45.0, # Read timeout
|
49 |
+
write=10.0, # Write timeout
|
50 |
+
pool=10.0 # Connection pool timeout
|
51 |
+
)
|
52 |
+
) as client:
|
53 |
+
response = await client.post(
|
54 |
+
self.base_url,
|
55 |
+
headers=self.headers,
|
56 |
+
json=payload
|
57 |
+
)
|
58 |
+
response.raise_for_status()
|
59 |
+
return response.json()
|
60 |
+
except httpx.HTTPStatusError as e:
|
61 |
+
print(f"HTTP error occurred: {e}")
|
62 |
+
print(f"Response text: {e.response.text}")
|
63 |
+
raise
|
64 |
+
except httpx.RequestError as e:
|
65 |
+
print(f"Request error occurred: {e}")
|
66 |
+
raise
|
67 |
+
except Exception as e:
|
68 |
+
print(f"An unexpected error occurred: {e}")
|
69 |
+
raise
|
70 |
+
|
71 |
+
|
72 |
+
def parse_perplexity_response(response: Dict[str, Any]) -> Dict[str, Any]:
|
73 |
+
"""
|
74 |
+
Parse the Perplexity API response and extract key information.
|
75 |
+
|
76 |
+
Args:
|
77 |
+
response (Dict[str, Any]): Raw response from Perplexity API
|
78 |
+
|
79 |
+
Returns:
|
80 |
+
Dict[str, Any]: Parsed response with content and citations
|
81 |
+
"""
|
82 |
+
print("parse_perplexity_response called...")
|
83 |
+
# Basic citation extraction (this is a simple implementation)
|
84 |
+
# In a real-world scenario, you might want a more sophisticated citation extraction
|
85 |
+
citations = []
|
86 |
+
|
87 |
+
# default content to stream if no response from the Perplexity AI
|
88 |
+
default_content = (
|
89 |
+
"I'm sorry, I couldn't find any relevant information for your query from the available sources. "
|
90 |
+
"If you'd like, you can try rephrasing your question or provide more context to help refine the search. "
|
91 |
+
"Alternatively, let me know if you'd like assistance in a different area."
|
92 |
+
)
|
93 |
+
|
94 |
+
citations = response.get("citations", [])
|
95 |
+
content = response.get("choices", default_content)[0]["message"]["content"]
|
96 |
+
return {
|
97 |
+
"content": content,
|
98 |
+
"citations": citations
|
99 |
+
}
|
websites.yaml
ADDED
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
public_websites:
|
2 |
+
- https://www.alimarket.es/base_de_datos,
|
3 |
+
- https://www.bain.cn/,
|
4 |
+
- http://zhgry.aiijournal.com/CN/1671-4393/home.shtml,
|
5 |
+
- https://moloprom.ru/,
|
6 |
+
- https://2023.dairyunion.ru/,
|
7 |
+
- https://www2.deloitte.com/cn/zh.html,
|
8 |
+
- https://www.emeoutlookmag.com/food-beverage",
|
9 |
+
- https://fabnews.live/,
|
10 |
+
- https://fmcgmagazine.co.uk/,
|
11 |
+
- https://www.foodbusinessnews.net/,
|
12 |
+
- https://www.fooddive.com/,
|
13 |
+
- https://www.foodengineeringmag.com/articles/96940-new-advances-make-aseptic-packaging-more-popular,
|
14 |
+
- https://foodindustryexecutive.com/
|
15 |
+
- http://spgykj.com/
|
16 |
+
- https://www.foodmanufacturing.com/
|
17 |
+
- https://www.foodprocessing.com/
|
18 |
+
- https://www.foodprocessing.com/
|
19 |
+
- https://www.foodsafetyafrica.net/
|
20 |
+
- https://www.foodtechbiz.com/packaging/tetra-pak-introduces-tetra-stelo-aseptic-package-with-minute-maid-juice-range-of-coca-cola-in-india
|
21 |
+
- https://www.fruit-processing.com/
|
22 |
+
- https://www.globalbrandsmagazine.com/beverage-brands-in-the-middle-east/
|
23 |
+
- https://www.greenqueen.com.hk/
|
24 |
+
- https://www.healthcaremea.com/
|
25 |
+
- https://www.ingredientsnetwork.com/
|
26 |
+
- https://www.iresearch.cn/
|
27 |
+
- https://issuu.com/ruralnewsgroup/docs/dn_508_november_22
|
28 |
+
- https://www.just-food.com/
|
29 |
+
- https://www.kantarworldpanel.com/cn
|
30 |
+
- https://www.labelsandlabeling.com/
|
31 |
+
- https://lenta.ru
|
32 |
+
- https://www.livemint.com/
|
33 |
+
- https://www.magzter.com/ar/HK/Ringier-Trade-Media-Ltd/Food-Manufacturing-Journal---Middle-East-&-Africa/Food-&-Beverage/194798
|
34 |
+
- https://www.mckinsey.com.cn/
|
35 |
+
- https://milknews.ru/
|
36 |
+
- https://www.nutritioninsight.com/
|
37 |
+
- https://www.packagingnetwork.com/doc/fruit-juice-maker-chooses-drink-box-for-grown-0001
|
38 |
+
- https://www.packagingnews.co.uk/
|
39 |
+
- https://packagingsouthasia.com/
|
40 |
+
- https://www.packaginglaw.com/
|
41 |
+
- https://www.pwccn.com/zh/research-and-insights.html
|
42 |
+
- https://www.retail.ru/
|
43 |
+
- https://retailer.ru/
|
44 |
+
- https://www.rbc.ru/
|
45 |
+
- https://rg.ru/
|
46 |
+
- https://sustainabilitymea.com/
|
47 |
+
- https://www.foodsafetyafrica.net/fda-announces-elimination-of-pfas-in-food-packaging-to-protect-public-health/
|
48 |
+
- https://tass.ru
|
49 |
+
- https://thebeet.com/category/plant-based-news/
|
50 |
+
- https://www.thegrocer.co.uk/
|
51 |
+
- https://www.theveganindians.com/
|
52 |
+
- https://www.thevegankind.com/
|
53 |
+
- https://www.totallyveganbuzz.com/
|
54 |
+
- https://www.unipack.ru/
|
55 |
+
- https://vegnews.com/
|
56 |
+
- https://www.veganfirst.com/
|
57 |
+
- https://www.theveganindians.com/indian-start-up-launches-first-ever-vegan-milk-made-from-sprouted-millets/
|
58 |
+
- https://vegoutmag.com/food-and-drink
|
59 |
+
- https://www.ecfr.gov/
|
60 |
+
- https://freepub.edqm.eu/publications/
|
61 |
+
- https://www.bfr.bund.de/de/bfr_empfehlungen_fuer_materialien_im_lebensmittelkontakt-308425.html
|
62 |
+
- https://cfsa.net.cn/
|
63 |
+
- https://www.icourse163.org/
|
64 |
+
- https://www.doc88.com/
|
65 |
+
- https://www.foodbev.com/news/category/industries/beverage/
|
66 |
+
- https://www.packagingdigest.com/food-beverage/beverage-packaging
|
67 |
+
- https://www.packagingnews.co.uk/news/materials/cartonboard
|
68 |
+
- https://www.dairyreporter.com/
|
69 |
+
- https://www.packaginginsights.com
|
70 |
+
- https://packagingeurope.com/
|
71 |
+
- https://www.foodnavigator-asia.com/
|
72 |
+
- https://www.newsnow.co.uk/h/Industry+Sectors/Food+&+Drink/Food+Manufacturers
|
73 |
+
- https://www.beveragedaily.com/
|
74 |
+
- https://www.fb101.com/
|
75 |
+
- https://www.dairyfoods.com/
|
76 |
+
- https://www.foodengineeringmag.com/
|
77 |
+
- https://www.preparedfoods.com/
|
78 |
+
- https://www.bevindustry.com/
|
79 |
+
- https://www.foodnavigator-usa.com/
|
80 |
+
- https://www.foodnavigator-latam.com/
|
81 |
+
- https://www.foodnavigator.com/
|
82 |
+
- https://www.apfoodonline.com/
|
83 |
+
- https://www.food-safety.com/
|
84 |
+
- https://www.foodbusinessgulf.com/
|
85 |
+
- https://www.bevnet.com/
|
86 |
+
- https://imbibemagazine.com/
|
87 |
+
- https://beveragedynamics.com/
|
88 |
+
- https://www.beverage-digest.com/
|
89 |
+
- https://www.thebeveragejournal.com/
|
90 |
+
- https://foodchainmagazine.com/news/category/insights/
|
91 |
+
- https://www.just-drinks.com/
|
92 |
+
- https://www.newfoodmagazine.com/
|
93 |
+
- https://beverage-master.com/
|
94 |
+
- https://drinksint.com/
|
95 |
+
- https://dairynews.today/global/
|
96 |
+
- https://globaldairyplatform.com/news_category/dairy-media-news/
|
97 |
+
- https://www.usdairy.com/media
|
98 |
+
- https://dairybusiness.com/
|
99 |
+
- https://www.thedairysite.com/
|
100 |
+
- https://californiadairymagazine.com/
|
101 |
+
- https://www.dairynewsaustralia.com.au/
|
102 |
+
- https://www.dairynz.co.nz/news/
|
103 |
+
- https://international-dairy.com/media-information/
|
104 |
+
- https://www.packaging-gateway.com/news/
|
105 |
+
- https://www.packworld.com/
|
106 |
+
- https://foodbeverageasia.com/
|
107 |
+
- https://www.nspackaging.com/
|
108 |
+
- https://www.packagingstrategies.com/
|
109 |
+
- https://spnews.com/
|
110 |
+
- https://www.foodbusinessafrica.com/
|
111 |
+
- https://www.foodmanufacture.co.uk/#
|
112 |
+
- https://www.weibo.com/u/2096538335?lpage=profileRecom
|
113 |
+
- http://www.csztv.cn/
|
114 |
+
- https://www.cbndata.com/
|
115 |
+
- https://news.qq.com/
|
116 |
+
- http://www.foodmate.net/
|
117 |
+
- https://www.foodingredientsfirst.com/
|
118 |
+
- https://www.beveragemarketing.com/strategist.asp
|
119 |
+
- https://www.ift.org/news-and-publications/food-technology-magazine
|
120 |
+
- https://www.packagingstrategies.com/flexible-packaging
|
121 |
+
- https://www.compacknews.news/en/
|
122 |
+
- https://www.italiaimballaggio.it/
|
123 |
+
- https://www.italiagrafica.com/
|
124 |
+
- https://www.portalspozywczy.pl/
|
125 |
+
- https://www.clal.it/
|
126 |
+
- https://www.pack.com.br/
|
127 |
+
- https://www.plastico.com.br/category/embalagens/
|
128 |
+
- https://www.canaldoleite.com/
|
129 |
+
- https://www.foodtalks.cn/
|
130 |
+
- https://www.foodaily.com/
|
131 |
+
- https://www.iimedia.cn/
|
132 |
+
- https://www.cdia.org.cn/
|
133 |
+
- https://www.chinacoatingnet.com/
|
134 |
+
- http://www.cppia.org.cn/
|
135 |
+
- https://www.dac.org.cn/
|