Skip to content

Commit d34d515

Browse files
committed
Initial commit
0 parents  commit d34d515

22 files changed

+589
-0
lines changed

.env_example

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
GROQ_API_KEY="your api key here"
2+
JINA_API_KEY="your api key here"
3+
SERPER_API_KEY="your api key here"
4+
COHERE_API_KEY = "your api key here"

Dockerfile

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
FROM python:3.12-slim
2+
3+
WORKDIR /workspace
4+
ENV HOME=/workspace
5+
6+
ADD . /workspace
7+
8+
RUN chown -R 42420:42420 /workspace
9+
10+
# Install dependencies using apk and then Python packages
11+
RUN pip install -r requirements.txt
12+
13+
EXPOSE 8080
14+
15+
ENTRYPOINT ["uvicorn"]
16+
17+
CMD ["main:app", "--host", "0.0.0.0", "--port", "8080"]
18+
19+
20+
# this docker image work for OVH CLOUD AI DEPLOY
2.96 KB
Binary file not shown.
Binary file not shown.

__pycache__/groq_api.cpython-312.pyc

2.76 KB
Binary file not shown.
2.41 KB
Binary file not shown.

__pycache__/main.cpython-312.pyc

3.82 KB
Binary file not shown.

__pycache__/prompts.cpython-312.pyc

2.11 KB
Binary file not shown.
1.41 KB
Binary file not shown.
910 Bytes
Binary file not shown.
3.02 KB
Binary file not shown.

build_context.py

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import logging
2+
from jina_rerank import get_reranking_jina
3+
from semantic_chunking import get_chunking
4+
5+
# Configure logging
6+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
7+
logger = logging.getLogger(__name__)
8+
9+
10+
def build_context(sources_result, query, pro_mode, date_context):
11+
"""
12+
Build context from search results.
13+
14+
:param sources_result: Dictionary containing search results
15+
:param query: Search query string
16+
:param pro_mode: Boolean indicating whether to use pro mode (reranking)
17+
:param date_context: Date context string
18+
:return: Built context as a string
19+
"""
20+
try:
21+
combined_list = []
22+
23+
organic_results = sources_result.get('organic', [])
24+
graph = sources_result.get('graph')
25+
answer_box = sources_result.get('answerBox')
26+
27+
snippets = [
28+
f"{item['snippet']} {item.get('date', '')}"
29+
for item in organic_results if 'snippet' in item # Ensure there's always a snippet
30+
]
31+
32+
combined_list.extend(snippets)
33+
34+
html_text = " ".join(item['html'] for item in organic_results if 'html' in item)
35+
if html_text is not None and len(html_text) > 200:
36+
combined_list.extend(get_chunking(html_text))
37+
38+
# Extract top stories titles
39+
if sources_result.get('topStories') is not None:
40+
top_stories_titles = [item['title'] for item in sources_result.get('topStories') if 'title' in item]
41+
combined_list.extend(top_stories_titles)
42+
43+
# Add descriptions and answers from 'graph' and 'answerBox'
44+
if graph is not None:
45+
graph_desc = graph.get('description')
46+
if graph_desc:
47+
combined_list.append(graph_desc)
48+
49+
if answer_box is not None:
50+
for key in ['answer', 'snippet']:
51+
if key in answer_box: # Use this if you want to append regardless of the value (including None)
52+
combined_list.append(answer_box[key])
53+
54+
if pro_mode:
55+
# you can choose to use jina or cohere for reranking
56+
final_list = get_reranking_jina(combined_list, query + date_context, 15)
57+
else:
58+
final_list = combined_list
59+
60+
search_contexts = "\n\n".join(final_list)
61+
print(search_contexts)
62+
return search_contexts
63+
except Exception as e:
64+
logger.exception(f"An error occurred while building context: {e}")
65+
return ""

cohere_reranking.py

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import os
2+
3+
import cohere
4+
5+
6+
# use ENV variables
7+
COHERE_API_KEY = os.getenv("COHERE_API_KEY")
8+
MODEL = "rerank-multilingual-v3.0"
9+
10+
co = cohere.Client(api_key=COHERE_API_KEY)
11+
12+
13+
def get_reranking_cohere(docs, query, top_res):
14+
"""
15+
Re-ranks a list of documents based on a query using Cohere's reranking API.
16+
17+
Args:
18+
docs (list of str): List of documents to be re-ranked.
19+
query (str): Query string to rank the documents against.
20+
top_res (int): Number of top results to return.
21+
22+
Returns:
23+
list of str: Top re-ranked documents based on the query.
24+
"""
25+
try:
26+
# Call the Cohere rerank API
27+
response = co.rerank(
28+
model=MODEL,
29+
query=query,
30+
documents=docs,
31+
top_n=top_res,
32+
return_documents=True
33+
)
34+
35+
# Extract and return the texts of the top documents
36+
return [item.document.text for item in response.results]
37+
38+
except Exception as e:
39+
# Log the error and handle it as needed
40+
print(f"An error occurred: {e}")
41+
return []

extract_content_from_website.py

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from langchain_community.document_loaders import WebBaseLoader
2+
3+
4+
def extract_website_content(url):
5+
"""
6+
Extracts and cleans the main content from a given website URL.
7+
8+
Args:
9+
url (str): The URL of the website from which to extract content.
10+
11+
Returns:
12+
str: The first 4000 characters of the cleaned main content if it is sufficiently long, otherwise an empty string.
13+
"""
14+
try:
15+
clean_text = []
16+
loader = WebBaseLoader(url)
17+
data = loader.load()
18+
19+
# Aggregate content using a list to avoid inefficient string concatenation in the loop
20+
for doc in data:
21+
if doc.page_content: # Check if page_content is not None or empty
22+
clean_text.append(doc.page_content.replace("\n", ""))
23+
24+
# Join all parts into a single string after processing
25+
clean_text = "".join(clean_text)
26+
27+
# Return up to the first 4000 characters if the content is sufficiently long
28+
return clean_text[:4000] if len(clean_text) > 200 else ""
29+
30+
except Exception as error:
31+
print('Error extracting main content:', error)
32+
return ""

groq_api.py

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import json
2+
import os
3+
from groq import Groq
4+
from langchain_core.prompts import PromptTemplate
5+
from prompts import search_prompt_system, relevant_prompt_system
6+
7+
# use ENV variables
8+
MODEL = "llama3-70b-8192"
9+
api_key_groq = os.getenv("GROQ_API_KEY")
10+
11+
12+
client = Groq()
13+
14+
15+
def get_answer(query, contexts, date_context):
16+
system_prompt_search = PromptTemplate(input_variables=["date_today"], template=search_prompt_system)
17+
18+
messages = [
19+
{"role": "system", "content": system_prompt_search.format(date_today=date_context)},
20+
{"role": "user", "content": "User Question : " + query + "\n\n CONTEXTS :\n\n" + contexts}
21+
]
22+
23+
try:
24+
stream = client.chat.completions.create(
25+
model=MODEL,
26+
messages=messages,
27+
stream=True,
28+
stop=None,
29+
)
30+
31+
for chunk in stream:
32+
if chunk.choices[0].delta.content is not None:
33+
yield chunk.choices[0].delta.content
34+
35+
except Exception as e:
36+
print(f"Error during get_answer_groq call: {e}")
37+
yield "data:" + json.dumps(
38+
{'type': 'error', 'data': "We are currently experiencing some issues. Please try again later."}) + "\n\n"
39+
40+
41+
def get_relevant_questions(contexts, query):
42+
try:
43+
response = client.chat.completions.create(
44+
model=MODEL,
45+
messages=[
46+
{"role": "system",
47+
"content": relevant_prompt_system
48+
},
49+
{"role": "user",
50+
"content": "User Query: " + query + "\n\n" + "Contexts: " + "\n" + contexts + "\n"}
51+
],
52+
response_format={"type": "json_object"},
53+
)
54+
55+
return response.choices[0].message.content
56+
except Exception as e:
57+
print(f"Error during RELEVANT GROQ ***************: {e}")
58+
return {}

jina_rerank.py

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import os
2+
import requests
3+
from typing import List
4+
import logging
5+
from requests.exceptions import RequestException
6+
7+
# Configure logging
8+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
9+
logger = logging.getLogger(__name__)
10+
11+
# Constants
12+
API_URL = "https://api.jina.ai/v1/rerank"
13+
API_KEY = os.getenv("JINA_API_KEY")
14+
MODEL = "jina-reranker-v2-base-multilingual"
15+
HEADERS = {
16+
"Content-Type": "application/json",
17+
"Authorization": f"Bearer {API_KEY}"
18+
}
19+
20+
21+
def get_reranking_jina(docs: List[str], query: str, top_res: int) -> List[str]:
22+
"""
23+
Get reranked documents using Jina AI API.
24+
25+
:param docs: List of documents to rerank
26+
:param query: Query string
27+
:param top_res: Number of top results to return
28+
:return: List of reranked documents
29+
"""
30+
try:
31+
data = {
32+
"model": MODEL,
33+
"query": query,
34+
"documents": docs,
35+
"top_n": top_res
36+
}
37+
38+
response = requests.post(API_URL, headers=HEADERS, json=data, timeout=10)
39+
response.raise_for_status()
40+
response_data = response.json()
41+
42+
return [item['document']['text'] for item in response_data.get('results', [])]
43+
44+
except RequestException as e:
45+
logger.error(f"HTTP error occurred while reranking: {e}")
46+
except KeyError as e:
47+
logger.error(f"Unexpected response format: {e}")
48+
except Exception as e:
49+
logger.exception(f"An unexpected error occurred: {e}")
50+
51+
return []

main.py

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import orjson as json
2+
from dotenv import load_dotenv
3+
4+
load_dotenv()
5+
6+
from fastapi.responses import StreamingResponse
7+
from fastapi import FastAPI, HTTPException
8+
from fastapi.middleware.cors import CORSMiddleware
9+
from groq_api import get_answer, get_relevant_questions
10+
from sources_searcher import get_sources
11+
from build_context import build_context
12+
from sources_manipulation import populate_sources
13+
14+
15+
app = FastAPI()
16+
17+
# allow_origins=["https://openperplex.com"]
18+
app.add_middleware(
19+
CORSMiddleware,
20+
allow_origins=["*"],
21+
allow_credentials=True,
22+
allow_methods=["GET", "POST"], # Allow all methods or specify like ["POST", "GET"]
23+
allow_headers=["*"], # Allow all headers or specify
24+
)
25+
26+
load_dotenv()
27+
28+
29+
@app.get("/")
30+
def root():
31+
return {"message": "hello world openperplex v1"}
32+
33+
34+
@app.get("/up_test")
35+
def up_test():
36+
# test for kamal deploy
37+
return {"status": "ok"}
38+
39+
40+
# you can change to post if typical your query is too long
41+
@app.get("/search")
42+
def ask(query: str, date_context: str, stored_location: str, pro_mode: bool = False):
43+
if not query:
44+
raise HTTPException(status_code=400, detail="Query cannot be empty")
45+
46+
def generate():
47+
try:
48+
sources_result = get_sources(query, pro_mode, stored_location)
49+
yield "data:" + json.dumps({'type': 'sources', 'data': sources_result}).decode() + "\n\n"
50+
51+
if sources_result.get('organic') is not None and pro_mode is True:
52+
# set the number of websites to scrape : here = 2
53+
sources_result['organic'] = populate_sources(sources_result['organic'], 2)
54+
55+
search_contexts = build_context(sources_result, query, pro_mode, date_context)
56+
57+
for chunk in get_answer(query, search_contexts, date_context):
58+
yield "data:" + json.dumps({'type': 'llm', 'text': chunk}).decode() + "\n\n"
59+
60+
try:
61+
relevant_questions = get_relevant_questions(search_contexts, query)
62+
relevant_json = json.loads(relevant_questions)
63+
yield "data:" + json.dumps({'type': 'relevant', 'data': relevant_json}).decode() + "\n\n"
64+
except Exception as e:
65+
print(f"error in relevant questions main.py {e}")
66+
yield "data:" + json.dumps({'type': 'relevant', 'data': []}).decode() + "\n\n"
67+
68+
yield "data:" + json.dumps({'type': 'finished', 'data': ""}).decode() + "\n\n"
69+
yield "event: end-of-stream\ndata: null\n\n"
70+
71+
except Exception as e:
72+
print(e)
73+
yield "data:" + json.dumps(
74+
{'type': 'error',
75+
'data': "We are currently experiencing some issues. Please try again later."}).decode() + "\n\n"
76+
77+
return StreamingResponse(generate(), media_type="text/event-stream")

0 commit comments

Comments
 (0)