Skip to content

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

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions firebase.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.10 -m pip install -r llm/requirements.txt"],
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC, this file is mainly used when you type firebase deploy ... so this pre-deploy will not happen inside a Docker environment

"source": "llm",
"codebase": "maple-llm",
"runtime": "python310"
}
],
"firestore": {
"rules": "firestore.rules",
"indexes": "firestore.indexes.json"
Expand Down
19 changes: 14 additions & 5 deletions infra/firebase.compose.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": ["source /app/llm/venv/bin/activate && python3.10 -m pip install -r /app/llm/requirements.txt"],
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC, this one should be used by docker compose and should run inside the docker enviroment (note this doesn't exactly work for reasons I don't understand)

"source": "llm",
"codebase": "maple-llm",
"runtime": "python310"
}
],
"firestore": {
"rules": "firestore.rules",
"indexes": "firestore.indexes.json"
Expand Down
3 changes: 3 additions & 0 deletions llm/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
venv/
__pycache__/
databases/
51 changes: 51 additions & 0 deletions llm/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
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

initialize_app()
app = Flask(__name__)


def is_intersection(keys, required_keys):
return (keys & required_keys) == required_keys


@app.route("/summary", methods=["POST"])
def summary():
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")
Comment on lines +15 to +20
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: currently this API takes a json payload like

{
  "bill_id": "...",
  "bill_title": "...",
  "bill_text": "..." 
}

In the future, since this has access to firebase, you could also offer an API which takes the bill_id and returns the summary after looking up the title/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 +26 to +27
Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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 get_summary_api_function but we don't really have types.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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 int


return jsonify(summary["summary"])


@app.route("/tags", methods=["POST"])
def tags():
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 +42 to +43
Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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"])


@https_fn.on_request(secrets=["OPENAI_DEV"])
def httpsflaskexample(req: https_fn.Request) -> https_fn.Response:
with app.request_context(req.environ):
return app.full_dispatch_request()
Comment on lines +48 to +51
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the magic sauce to expose these routes to firebase

18 changes: 18 additions & 0 deletions llm/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,21 @@ 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

```
python3 -m venv venv
source venv/bin/activate # .fish if using fish
pip3 install -r requirements.txt
python3 -m flask --app main run
```
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to add more details here and remove everything below it


TODO:

- [x] Get an OpenAPI key `firebase functions:secrets:access OPENAI_DEV`
- [ ] Need to get docker running locally `yarn compose up --build`
- [ ] Try to deploy the function `firebase deploy --only functions:maple-llm`
- [ ] Alphabetize the requirements.txt file
177 changes: 5 additions & 172 deletions llm/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,183 +1,16 @@
absl-py==2.1.0
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
27 changes: 27 additions & 0 deletions llm/test_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import pytest
from main import app


@pytest.fixture
def client():
return app.test_client()


def test_summary_returns_404_with_empty_body(client):
response = client.post("/summary", json={})
assert response.status_code == 404


def test_summary_returns_404_with_partial_body(client):
response = client.post("/summary", json={"bill_id": "bill"})
assert response.status_code == 404


def test_tags_returns_404_with_empty_body(client):
response = client.post("/tags", json={})
assert response.status_code == 404


def test_tags_returns_404_with_partial_body(client):
response = client.post("/tags", json={"bill_id": "bill"})
assert response.status_code == 404
Loading