Skip to content

Commit 072b657

Browse files
committed
init
0 parents  commit 072b657

File tree

3 files changed

+363
-0
lines changed

3 files changed

+363
-0
lines changed

.gitignore

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
# Byte-compiled / optimized / DLL files
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
6+
# C extensions
7+
*.so
8+
9+
# Distribution / packaging
10+
.Python
11+
build/
12+
develop-eggs/
13+
dist/
14+
downloads/
15+
eggs/
16+
.eggs/
17+
lib/
18+
lib64/
19+
parts/
20+
sdist/
21+
var/
22+
wheels/
23+
share/python-wheels/
24+
*.egg-info/
25+
.installed.cfg
26+
*.egg
27+
MANIFEST
28+
29+
# PyInstaller
30+
# Usually these files are written by a python script from a template
31+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
32+
*.manifest
33+
*.spec
34+
35+
# Installer logs
36+
pip-log.txt
37+
pip-delete-this-directory.txt
38+
39+
# Unit test / coverage reports
40+
htmlcov/
41+
.tox/
42+
.nox/
43+
.coverage
44+
.coverage.*
45+
.cache
46+
nosetests.xml
47+
coverage.xml
48+
*.cover
49+
*.py,cover
50+
.hypothesis/
51+
.pytest_cache/
52+
cover/
53+
54+
# Translations
55+
*.mo
56+
*.pot
57+
58+
# Django stuff:
59+
*.log
60+
local_settings.py
61+
db.sqlite3
62+
db.sqlite3-journal
63+
64+
# Flask stuff:
65+
instance/
66+
.webassets-cache
67+
68+
# Scrapy stuff:
69+
.scrapy
70+
71+
# Sphinx documentation
72+
docs/_build/
73+
74+
# PyBuilder
75+
.pybuilder/
76+
target/
77+
78+
# Jupyter Notebook
79+
.ipynb_checkpoints
80+
81+
# IPython
82+
profile_default/
83+
ipython_config.py
84+
85+
# pyenv
86+
# For a library or package, you might want to ignore these files since the code is
87+
# intended to run in multiple environments; otherwise, check them in:
88+
# .python-version
89+
90+
# pipenv
91+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
93+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
94+
# install all needed dependencies.
95+
#Pipfile.lock
96+
97+
# poetry
98+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99+
# This is especially recommended for binary packages to ensure reproducibility, and is more
100+
# commonly ignored for libraries.
101+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102+
#poetry.lock
103+
104+
# pdm
105+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106+
#pdm.lock
107+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108+
# in version control.
109+
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
110+
.pdm.toml
111+
.pdm-python
112+
.pdm-build/
113+
114+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
115+
__pypackages__/
116+
117+
# Celery stuff
118+
celerybeat-schedule
119+
celerybeat.pid
120+
121+
# SageMath parsed files
122+
*.sage.py
123+
124+
# Environments
125+
.env
126+
.venv
127+
env/
128+
venv/
129+
ENV/
130+
env.bak/
131+
venv.bak/
132+
133+
# Spyder project settings
134+
.spyderproject
135+
.spyproject
136+
137+
# Rope project settings
138+
.ropeproject
139+
140+
# mkdocs documentation
141+
/site
142+
143+
# mypy
144+
.mypy_cache/
145+
.dmypy.json
146+
dmypy.json
147+
148+
# Pyre type checker
149+
.pyre/
150+
151+
# pytype static type analyzer
152+
.pytype/
153+
154+
# Cython debug symbols
155+
cython_debug/
156+
157+
# PyCharm
158+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
159+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
160+
# and can be added to the global gitignore or merged into this file. For a more nuclear
161+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
162+
#.idea/

README.md

Whitespace-only changes.

perplexity.py

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
import logging
2+
import argparse
3+
import os
4+
import sys
5+
from dataclasses import dataclass
6+
import requests
7+
import json
8+
9+
AVAILABLE_MODELS = [
10+
"llama-3.1-sonar-small-128k-online",
11+
"llama-3.1-sonar-large-128k-online",
12+
"llama-3.1-sonar-huge-128k-online",
13+
]
14+
15+
logger = logging.getLogger(__name__)
16+
17+
18+
class ApiKeyNotFoundException(Exception):
19+
pass
20+
21+
22+
class InvalidSelectedModelException(Exception):
23+
pass
24+
25+
26+
def display(
27+
message: str,
28+
color: str = "white",
29+
bold: bool = False,
30+
bg_color: str = "black",
31+
):
32+
colors = {
33+
"red": "91m",
34+
"green": "92m",
35+
"yellow": "93m",
36+
"blue": "94m",
37+
"white": "97m",
38+
}
39+
bg_colors = {
40+
"black": "40",
41+
"red": "41",
42+
"green": "42",
43+
"yellow": "43",
44+
"blue": "44",
45+
"white": "47",
46+
}
47+
if bold:
48+
print(f"\033[1;{bg_colors[bg_color]};{colors[color]} {message}\033[0m")
49+
else:
50+
print(f"\033[{bg_colors[bg_color]};{colors[color]} {message}\033[0m")
51+
52+
53+
@dataclass(frozen=True)
54+
class ApiConfig:
55+
api_url: str = "https://api.perplexity.ai/chat/completions"
56+
api_key: str | None = None
57+
usage: bool = False
58+
citations: bool = False
59+
model: str | None = None
60+
61+
62+
class ModelValidator:
63+
@staticmethod
64+
def validate(model: str) -> bool:
65+
return model in AVAILABLE_MODELS
66+
67+
@staticmethod
68+
def get_AVAILABLE_MODELS() -> list[str]:
69+
return AVAILABLE_MODELS
70+
71+
72+
class ApiKeyValidator:
73+
@staticmethod
74+
def get_api_key_from_system() -> str | None:
75+
return os.environ.get("PERPLEXITY_API_KEY")
76+
77+
78+
class Perplexity:
79+
def __init__(self, args) -> None:
80+
self.setup = ApiConfig
81+
if not ModelValidator.validate(args.model):
82+
raise InvalidSelectedModelException(
83+
f"Invalid model: {args.model}\n"
84+
f"Available models: {ModelValidator.get_AVAILABLE_MODELS()}"
85+
)
86+
self.setup.model = args.model
87+
self.setup.usage = args.usage
88+
self.setup.citations = args.citations
89+
self.use_glow = args.glow
90+
if not args.api_key:
91+
api_key = ApiKeyValidator.get_api_key_from_system()
92+
if api_key is None:
93+
logger.debug("Api key not found on system")
94+
raise ApiKeyNotFoundException
95+
else:
96+
logger.debug(f"Api key found on system: {api_key}")
97+
self.setup.api_key = api_key
98+
else:
99+
self.setup.api_key = args.api_key
100+
101+
def get_response(self, message) -> None:
102+
headers = {
103+
"accept": "application/json",
104+
"content-type": "application/json",
105+
"Authorization": f"Bearer {self.setup.api_key}",
106+
}
107+
logger.debug(f"Headers: {headers}")
108+
query_data = {
109+
"model": self.setup.model,
110+
"messages": [
111+
{"role": "system", "content": "Be precise and concise."},
112+
{"role": "user", "content": message},
113+
],
114+
}
115+
logger.debug(f"Query data: {query_data}")
116+
117+
response = requests.post(
118+
self.setup.api_url, headers=headers, data=json.dumps(query_data)
119+
)
120+
121+
if response.status_code == 200:
122+
result = response.json()
123+
if self.setup.citations:
124+
self._show_citations(result["citations"], self.use_glow)
125+
if self.setup.usage:
126+
self._show_usage(result["usage"], self.use_glow)
127+
self._show_content(result["choices"][0]["message"]["content"])
128+
elif response.status_code == 401:
129+
display("Invalid api key! ", "red")
130+
else:
131+
logger.error(f"Error: {response.status_code}")
132+
133+
@staticmethod
134+
def _show_usage(result: dict, use_glow: bool) -> None:
135+
if use_glow:
136+
print("# Tokens")
137+
else:
138+
display("Tokens \n", "yellow", True, "blue")
139+
for token in result:
140+
print(f"- {token}: {result[token]}")
141+
print("\n")
142+
143+
@staticmethod
144+
def _show_citations(result: list, use_glow: bool) -> None:
145+
if use_glow:
146+
print("# Citations")
147+
else:
148+
display("Citations \n", "yellow", True, "blue")
149+
for element in result:
150+
print(f"- {element}")
151+
print("\n")
152+
153+
def _show_content(self, result: str) -> None:
154+
if self.use_glow:
155+
print("# Content")
156+
else:
157+
display("Content \n", "yellow", True, "blue")
158+
print(result)
159+
160+
161+
def main() -> None:
162+
parser = argparse.ArgumentParser()
163+
parser.add_argument("query", type=str, help="The query to process")
164+
parser.add_argument("-v", "--verbose", action="store_true", help="Debug mode")
165+
parser.add_argument("-u", "--usage", action="store_true", help="Show usage")
166+
parser.add_argument("-c", "--citations", action="store_true", help="Show citations")
167+
parser.add_argument("-g", "--glow", action="store_true", help="Show citations")
168+
parser.add_argument(
169+
"-a",
170+
"--api-key",
171+
type=str,
172+
help="Description for api_key argument",
173+
required=False,
174+
)
175+
parser.add_argument(
176+
"-m",
177+
"--model",
178+
type=str,
179+
help="Description for model argument (default: llama-3.1-sonar-small-128k-online) "
180+
f"Available models: {AVAILABLE_MODELS}",
181+
required=False,
182+
default="llama-3.1-sonar-small-128k-online",
183+
)
184+
args = parser.parse_args()
185+
log_level = logging.DEBUG if args.verbose else logging.WARNING
186+
logging.basicConfig(
187+
level=log_level,
188+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
189+
datefmt="%Y-%m-%d %H:%M:%S",
190+
)
191+
logger.debug(f"args: {args}")
192+
try:
193+
perplexity = Perplexity(args)
194+
perplexity.get_response(args.query)
195+
except Exception as e:
196+
logger.error(f"An error occurred: {str(e)}")
197+
sys.exit(1)
198+
199+
200+
if __name__ == "__main__":
201+
main()

0 commit comments

Comments
 (0)