-
-
Notifications
You must be signed in to change notification settings - Fork 135
Llm flask api #1789
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Llm flask api #1789
Changes from all commits
3c2a1e5
83d0cbc
b254730
e6a6e59
641ca2e
82bb272
d2a07be
099373d
87ede5a
ba076ea
86f40aa
22a05be
5211a90
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,11 +4,20 @@ | |
"cleanUrls": true, | ||
"ignore": ["firebase.json", "**/.*", "**/node_modules/**"] | ||
}, | ||
"functions": { | ||
"predeploy": ["yarn build:functions"], | ||
"source": "functions", | ||
"runtime": "nodejs18" | ||
}, | ||
"functions": [ | ||
{ | ||
"predeploy": ["yarn build:functions"], | ||
"source": "functions", | ||
"codebase": "maple", | ||
"runtime": "nodejs18" | ||
}, | ||
{ | ||
"predeploy": [". llm/venv/bin/activate && python3 -m pip install -r llm/requirements.txt"], | ||
"source": "llm", | ||
"codebase": "maple-llm", | ||
"runtime": "python311" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the version running inside |
||
} | ||
], | ||
"firestore": { | ||
"rules": "firestore.rules", | ||
"indexes": "firestore.indexes.json" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
FROM andreysenov/firebase-tools:latest-node-18 | ||
|
||
USER root | ||
RUN apt update && apt install -y curl | ||
RUN apt update && apt install -y curl python3 python3-pip python3-venv | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need python in this container to build the virtual environments, see |
||
|
||
WORKDIR /app | ||
RUN chown -R node:node . | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
venv/ | ||
__pycache__/ | ||
databases/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -278,6 +278,7 @@ def set_my_llm_cache(cache_file: Path=LLM_CACHE) -> SQLiteCache: | |
Set an LLM cache, which allows for previously executed completions to be | ||
loaded from disk instead of repeatedly queried. | ||
""" | ||
cache_file.parent.mkdir(exist_ok=True) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was required because the container doesn't have this path and we need it to build the LLM cache |
||
set_llm_cache(SQLiteCache(database_path = cache_file)) | ||
|
||
@dataclass() | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
from flask import Flask, jsonify, abort, request | ||
from llm_functions import get_summary_api_function, get_tags_api_function | ||
import json | ||
from firebase_admin import initialize_app | ||
from firebase_functions import https_fn, options | ||
import os | ||
|
||
initialize_app() | ||
app = Flask(__name__) | ||
|
||
|
||
def is_intersection(keys, required_keys): | ||
return (keys & required_keys) == required_keys | ||
|
||
|
||
def set_openai_api_key(): | ||
match os.environ.get("MAPLE_DEV"): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. AFAICT, we use this to deploy dev versus prod. I re-use it here to select an environment. IIUC, we have a lost less dollars in |
||
case "prod": | ||
if os.environ.get("OPENAI_PROD") != None: | ||
os.environ["OPENAI_API_KEY"] = os.environ["OPENAI_PROD"] | ||
case _: # if "dev" or unspecified, use OPENAI_DEV | ||
if os.environ.get("OPENAI_DEV") != None: | ||
os.environ["OPENAI_API_KEY"] = os.environ["OPENAI_DEV"] | ||
|
||
|
||
@app.route("/summary", methods=["POST"]) | ||
def summary(): | ||
set_openai_api_key() | ||
body = json.loads(request.data) | ||
# We require bill_id, bill_title, bill_text to exist as keys in the POST | ||
if not is_intersection(body.keys(), {"bill_id", "bill_title", "bill_text"}): | ||
abort(404, description="requires bill_id, bill_title, and bill_text") | ||
|
||
summary = get_summary_api_function( | ||
body["bill_id"], body["bill_title"], body["bill_text"] | ||
) | ||
|
||
if summary["status"] in [-1, -2]: | ||
abort(500, description="Unable to generate summary") | ||
Comment on lines
+38
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The status field returns either -1 or -2 if it fails... This is probably somewhat unfortunate here because if this API ever changes I will not be able to know about it. I'll add a note to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or, maybe I should just check that this value is negative? technically the return type is |
||
|
||
return jsonify(summary["summary"]) | ||
|
||
|
||
@app.route("/tags", methods=["POST"]) | ||
def tags(): | ||
set_openai_api_key() | ||
body = json.loads(request.data) | ||
# We require bill_id, bill_title, bill_text to exist as keys in the POST | ||
# Note: & is essentially set intersection | ||
if not is_intersection(body.keys(), {"bill_id", "bill_title", "bill_text"}): | ||
abort(404, description="requires bill_id, bill_title, and bill_text") | ||
|
||
tags = get_tags_api_function(body["bill_id"], body["bill_title"], body["bill_text"]) | ||
|
||
if tags["status"] in [-1, -2]: | ||
abort(500, description="Unable to generate tags") | ||
Comment on lines
+55
to
+56
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably check for negative return type? See above https://github.com/codeforboston/maple/pull/1789/files#r2055086855 |
||
|
||
return jsonify(tags["tags"]) | ||
|
||
|
||
@app.route("/ready", methods=["GET"]) | ||
def ready(): | ||
return "" | ||
Comment on lines
+61
to
+63
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This may not be necessary... |
||
|
||
|
||
@https_fn.on_request( | ||
secrets=["OPENAI_DEV", "OPENAI_PROD"], | ||
timeout_sec=300, | ||
memory=options.MemoryOption.GB_1, | ||
) | ||
def httpsflaskexample(req: https_fn.Request) -> https_fn.Response: | ||
with app.request_context(req.environ): | ||
return app.full_dispatch_request() |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -122,3 +122,55 @@ This project uses OpenAI's API for various language processing tasks. To use the | |
```python | ||
import os | ||
print(os.environ.get('OPENAI_API_KEY')) | ||
|
||
# Running the API | ||
|
||
Set up a virtual environment and run the Flask app | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Conceptually, there are a few ways you can run this application.
"2." is by far the hardest because we directly overlay |
||
|
||
``` | ||
python3 -m venv venv | ||
source venv/bin/activate # .fish if using fish | ||
pip3 install -r requirements.txt | ||
python3 -m flask --app main run | ||
``` | ||
chiroptical marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
## Infrastructure notes | ||
|
||
As of 2025-06-17, the version of `python3` inside the | ||
`infra/Dockerfile.firebase` is 3.11. Therefore, the `firebase.json` files use | ||
the `python311` runtime. | ||
|
||
## Deploying locally | ||
|
||
This is quite tricky due to how we overlay our current source directory to | ||
`/app` inside the container. You'll need to create and install dependencies from | ||
**inside** the container. If you are just working on python related code that | ||
doesn't need to be in Firebase, you **won't** be able to use this environment. | ||
|
||
```shell | ||
# Build the maple-firebase container | ||
yarn dev:update | ||
# Start up bash within the maple-firebase container | ||
docker run -v .:/app -it maple-firebase /bin/bash | ||
# Build the virtual env and install the dependencies matching the container | ||
python3 -m venv llm/venv | ||
source llm/venv/bin/activate | ||
pip3 install -r llm/requirements.txt | ||
``` | ||
|
||
## Deploying to Firebase | ||
|
||
```shell | ||
# not sure if the GOOGLE_APPLICATION_CREDENTIALS is strictly necessary, but I | ||
# had a number of problems with authorization | ||
GOOGLE_APPLICATION_CREDENTIALS=/path/to/application_default_credentials.json \ | ||
firebase deploy --only functions:maple-llm --debug | ||
|
||
# Hit the function in production | ||
curl \ | ||
-X POST \ | ||
-H "Content-Type: application/json" \ | ||
-H "Authorization: Bearer $(gcloud auth print-identity-token)" \ | ||
-d '{"bill_id": "1234","bill_title": "A title","bill_text": "Some bill text"}' \ | ||
https://httpsflaskexample-ke6znoupgq-uc.a.run.app/summary | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,183 +1,16 @@ | ||
absl-py==2.1.0 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minimized and simplified the requirements file. |
||
aiohttp==3.9.5 | ||
aiosignal==1.3.1 | ||
altair==5.3.0 | ||
annotated-types==0.7.0 | ||
anyio==4.4.0 | ||
asgiref==3.8.1 | ||
asttokens==2.4.1 | ||
attrs==23.2.0 | ||
backoff==2.2.1 | ||
bcrypt==4.1.3 | ||
blinker==1.8.2 | ||
blis==0.7.11 | ||
build==1.2.1 | ||
cachetools==5.3.3 | ||
catalogue==2.0.10 | ||
certifi==2024.6.2 | ||
charset-normalizer==3.3.2 | ||
chroma-hnswlib==0.7.3 | ||
Flask==3.1.0 | ||
chromadb==0.5.0 | ||
click==8.1.7 | ||
cloudpathlib==0.18.1 | ||
coloredlogs==15.0.1 | ||
confection==0.1.5 | ||
cymem==2.0.8 | ||
dataclasses-json==0.6.6 | ||
decorator==5.1.1 | ||
Deprecated==1.2.14 | ||
distro==1.9.0 | ||
dnspython==2.6.1 | ||
email_validator==2.1.1 | ||
executing==2.0.1 | ||
fastapi==0.111.0 | ||
fastapi-cli==0.0.4 | ||
filelock==3.14.0 | ||
flatbuffers==24.3.25 | ||
frozenlist==1.4.1 | ||
fsspec==2024.6.0 | ||
git-filter-repo==2.45.0 | ||
gitdb==4.0.11 | ||
GitPython==3.1.43 | ||
google-auth==2.29.0 | ||
googleapis-common-protos==1.63.1 | ||
grpcio==1.64.1 | ||
h11==0.14.0 | ||
httpcore==1.0.5 | ||
httptools==0.6.1 | ||
httpx==0.27.0 | ||
huggingface-hub==0.25.2 | ||
humanfriendly==10.0 | ||
idna==3.7 | ||
importlib_metadata==7.1.0 | ||
importlib_resources==6.4.0 | ||
ipdb==0.13.13 | ||
ipython==8.25.0 | ||
jedi==0.19.1 | ||
Jinja2==3.1.4 | ||
joblib==1.4.2 | ||
jsonpatch==1.33 | ||
jsonpointer==2.4 | ||
jsonschema==4.22.0 | ||
jsonschema-specifications==2023.12.1 | ||
kubernetes==29.0.0 | ||
langchain==0.2.1 | ||
firebase-admin==6.7.0 | ||
firebase-functions==0.4.2 | ||
langchain-community==0.2.1 | ||
langchain-core==0.2.3 | ||
langchain-openai==0.1.8 | ||
langchain-text-splitters==0.2.0 | ||
langcodes==3.4.0 | ||
langsmith==0.1.69 | ||
language_data==1.2.0 | ||
marisa-trie==1.2.0 | ||
markdown-it-py==3.0.0 | ||
MarkupSafe==2.1.5 | ||
marshmallow==3.21.2 | ||
matplotlib-inline==0.1.7 | ||
mdurl==0.1.2 | ||
mmh3==4.1.0 | ||
monotonic==1.6 | ||
mpmath==1.3.0 | ||
multidict==6.0.5 | ||
murmurhash==1.0.10 | ||
mypy-extensions==1.0.0 | ||
networkx==3.3 | ||
nltk==3.8.1 | ||
langchain==0.2.1 | ||
numpy==1.26.4 | ||
oauthlib==3.2.2 | ||
onnxruntime==1.18.0 | ||
openai==1.31.0 | ||
opentelemetry-api==1.25.0 | ||
opentelemetry-exporter-otlp-proto-common==1.25.0 | ||
opentelemetry-exporter-otlp-proto-grpc==1.25.0 | ||
opentelemetry-instrumentation==0.46b0 | ||
opentelemetry-instrumentation-asgi==0.46b0 | ||
opentelemetry-instrumentation-fastapi==0.46b0 | ||
opentelemetry-proto==1.25.0 | ||
opentelemetry-sdk==1.25.0 | ||
opentelemetry-semantic-conventions==0.46b0 | ||
opentelemetry-util-http==0.46b0 | ||
orjson==3.10.3 | ||
overrides==7.7.0 | ||
packaging==23.2 | ||
pandas==2.2.2 | ||
parso==0.8.4 | ||
pexpect==4.9.0 | ||
pillow==10.3.0 | ||
posthog==3.5.0 | ||
preshed==3.0.9 | ||
prompt_toolkit==3.0.45 | ||
protobuf==4.25.3 | ||
ptyprocess==0.7.0 | ||
pure-eval==0.2.2 | ||
pyarrow==16.1.0 | ||
pyasn1==0.6.0 | ||
pyasn1_modules==0.4.0 | ||
pydantic==2.7.3 | ||
pydantic_core==2.18.4 | ||
pydeck==0.9.1 | ||
Pygments==2.18.0 | ||
PyPika==0.48.9 | ||
pyproject_hooks==1.1.0 | ||
python-dateutil==2.9.0.post0 | ||
python-dotenv==1.0.1 | ||
python-multipart==0.0.9 | ||
pytz==2024.1 | ||
PyYAML==6.0.1 | ||
redis==5.0.7 | ||
referencing==0.35.1 | ||
regex==2024.5.15 | ||
pytest==8.3.5 | ||
requests==2.32.3 | ||
requests-oauthlib==2.0.0 | ||
rich==13.7.1 | ||
rouge_score==0.1.2 | ||
rpds-py==0.18.1 | ||
rsa==4.9 | ||
safetensors==0.4.3 | ||
scikit-learn==1.5.0 | ||
scipy==1.13.1 | ||
sentence-transformers==3.0.0 | ||
setuptools==70.0.0 | ||
shellingham==1.5.4 | ||
six==1.16.0 | ||
smart-open==7.0.4 | ||
smmap==5.0.1 | ||
sniffio==1.3.1 | ||
spacy==3.7.5 | ||
spacy-legacy==3.0.12 | ||
spacy-loggers==1.0.5 | ||
SQLAlchemy==2.0.30 | ||
srsly==2.4.8 | ||
stack-data==0.6.3 | ||
starlette==0.37.2 | ||
streamlit==1.35.0 | ||
sympy==1.12.1 | ||
tenacity==8.3.0 | ||
thinc==8.2.5 | ||
threadpoolctl==3.5.0 | ||
tiktoken==0.7.0 | ||
tokenizers==0.20.1 | ||
toml==0.10.2 | ||
toolz==0.12.1 | ||
torch==2.4.1 | ||
tornado==6.4 | ||
tqdm==4.66.4 | ||
traitlets==5.14.3 | ||
transformers==4.45.2 | ||
typer==0.12.3 | ||
typing-inspect==0.9.0 | ||
typing_extensions==4.12.1 | ||
tzdata==2024.1 | ||
ujson==5.10.0 | ||
urllib3==2.2.1 | ||
uvicorn==0.30.1 | ||
uvloop==0.19.0 | ||
wasabi==1.1.3 | ||
watchfiles==0.22.0 | ||
wcwidth==0.2.13 | ||
weasel==0.4.1 | ||
websocket-client==1.8.0 | ||
websockets==12.0 | ||
wrapt==1.16.0 | ||
yarl==1.9.4 | ||
zipp==3.19.1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: this PR requires an update to
firebase-tools
inpackage.json
!