Skip to content

Commit a53c053

Browse files
authored
Merge pull request #56 from boettiger-lab/add_llms
update llm options and adding logging option
2 parents 557af6f + 663a540 commit a53c053

5 files changed

Lines changed: 105 additions & 19 deletions

File tree

app/app.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,10 @@
129129
#############
130130

131131

132+
132133
chatbot_container = st.container()
133134
with chatbot_container:
134-
llm_left_col, llm_right_col = st.columns([5,1], vertical_alignment = "bottom")
135+
llm_left_col, llm_right_col = st.columns([2.5,1], vertical_alignment = "bottom")
135136
with llm_left_col:
136137

137138
with st.popover("💬 Example Queries"):
@@ -157,7 +158,6 @@
157158

158159
st.info('If the map appears blank, queried data may be too small to see at the default zoom level. Check the table below the map, as query results will also be displayed there.', icon="ℹ️")
159160

160-
161161
with llm_right_col:
162162
llm_choice = st.selectbox("Select LLM:", llm_options, key = "llm", help = "Select which model to use.")
163163
llm = llm_options[llm_choice]
@@ -206,7 +206,7 @@ def run_sql(query,color_choice):
206206
explanation =output.explanation
207207
if not sql_query: # if the chatbot can't generate a SQL query.
208208
st.success(explanation)
209-
return pd.DataFrame({'id' : []})
209+
return pd.DataFrame({'id' : []}), [], []
210210

211211
result = ca.sql(sql_query).execute()
212212
if result.empty :
@@ -215,9 +215,9 @@ def run_sql(query,color_choice):
215215
st.caption("SQL Query:")
216216
st.code(sql_query,language = "sql")
217217
if 'geom' in result.columns:
218-
return result.drop('geom',axis = 1)
218+
return result.drop('geom',axis = 1), sql_query, explanation
219219
else:
220-
return result
220+
return result, sql_query, explanation
221221

222222
elif ("id" and "geom" in result.columns):
223223
style = get_pmtiles_style_llm(style_options[color_choice], result["id"].tolist())
@@ -235,7 +235,7 @@ def run_sql(query,color_choice):
235235
st.caption("SQL Query:")
236236
st.code(sql_query,language = "sql")
237237

238-
return result
238+
return result, sql_query, explanation
239239

240240

241241

@@ -266,15 +266,22 @@ def run_sql(query,color_choice):
266266
example_query = "👋 Input query here"
267267
prompt = st.chat_input(example_query, key="chain", max_chars=300)
268268

269+
with chatbot_container:
270+
_,log_query_col, _ = st.columns([.001, 5,1], vertical_alignment = "top")
271+
with log_query_col:
272+
log_queries = st.checkbox("Save query", value = True, help = "Saving your queries helps improve this tool and guide conservation efforts. Your data is stored in a private location. For more details, see 'Why save your queries?' at the bottom of this page.")
269273

274+
270275
with st.container():
271276
if prompt:
272277
st.chat_message("user").write(prompt)
273278
try:
274279
with st.chat_message("assistant"):
275280
with st.spinner("Invoking query..."):
276281

277-
out = run_sql(prompt,color_choice)
282+
out, sql_query, llm_explanation = run_sql(prompt,color_choice)
283+
minio_logger(log_queries, prompt, sql_query, llm_explanation, llm_choice, 'query_log_prototype.csv', "shared-ca30x30-app")
284+
278285
if ("id" in out.columns) and (not out.empty):
279286
ids = out['id'].tolist()
280287
cols = out.columns.tolist()

app/footer.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,17 @@ Data: https://huggingface.co/datasets/boettiger-lab/ca-30x30
1616
- CDC 2022 Social Vulnerability Index by US Census Tract. Archived description: https://web.archive.org/web/20250126095916/https://www.atsdr.cdc.gov/place-health/php/svi/index.html. Data: https://source.coop/repositories/cboettig/social-vulnerability/description. License: Public Domain
1717

1818
- Fire and Prescribed Fire by CAL FIRE (2023), reprocessed to PMTiles on https://beta.source.coop/cboettig/fire/. License: Public Domain
19+
20+
21+
#### LLMs
22+
This app can use a selection of open-weights language models hosted on the National Research Platform (https://nrp.ai/documentation/userdocs/ai/llm-managed/), and Open Router (https://openrouter.ai/models).
23+
24+
---
25+
26+
### Why save your queries?
27+
Conservation researchers and practitioners are interested in **learning what matters most to our community**.
28+
29+
By saving your anonymous queries, we can identify which topics and areas are drawing the most attention, helping us improve future tools and data products to **better support conservation efforts**. We also save the LLM’s response to each query to monitor its accuracy and ensure the system is working as intended.
30+
31+
You can opt out at any time by disabling “Save query”.
32+

app/utils.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
from typing import Optional
1212
from functools import reduce
1313
from itertools import chain
14+
import minio
15+
import datetime
1416

1517
from variables import *
1618

@@ -407,3 +409,28 @@ def create_bar_chart(df, x, y, title, color=None, stacked=False, colors=None):
407409
)
408410

409411
return final_chart
412+
413+
minio_key = os.getenv("MINIO_KEY")
414+
if minio_key is None:
415+
minio_key = st.secrets["MINIO_KEY"]
416+
417+
minio_secret = os.getenv("MINIO_SECRET")
418+
if minio_secret is None:
419+
minio_secret = st.secrets["MINIO_SECRET"]
420+
421+
def minio_logger(consent, query, sql_query, llm_explanation, llm_choice, filename, bucket,
422+
key=minio_key, secret=minio_secret,
423+
endpoint="minio.carlboettiger.info"):
424+
mc = minio.Minio(endpoint, key, secret)
425+
mc.fget_object(bucket, filename, filename)
426+
log = pd.read_csv(filename)
427+
timestamp = datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
428+
if consent:
429+
df = pd.DataFrame({"timestamp": [timestamp], "user_query": [query], "llm_sql": [sql_query], "llm_explanation": [llm_explanation], "llm_choice":[llm_choice]})
430+
431+
# if user opted out, do not store query
432+
else:
433+
df = pd.DataFrame({"timestamp": [timestamp], "user_query": ['USER OPTED OUT'], "llm_sql": [''], "llm_explanation": [''], "llm_choice":['']})
434+
435+
pd.concat([log,df]).to_csv(filename, index=False, header=True)
436+
mc.fput_object(bucket, filename, filename, content_type="text/csv")

app/variables.py

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -293,13 +293,53 @@
293293

294294
from langchain_openai import ChatOpenAI
295295
import streamlit as st
296-
# from langchain_openai.chat_models.base import BaseChatOpenAI
296+
from langchain_openai.chat_models.base import BaseChatOpenAI
297297

298-
llm_options = {
299-
# "llama-3.3-quantized": ChatOpenAI(model = "cirrus", api_key=st.secrets['CIRRUS_LLM_API_KEY'], base_url = "https://llm.cirrus.carlboettiger.info/v1", temperature=0),
300-
"llama3.3": ChatOpenAI(model = "llama3-sdsc", api_key=st.secrets['NRP_API_KEY'], base_url = "https://llm.nrp-nautilus.io/", temperature=0),
301-
"gemma3": ChatOpenAI(model = "gemma3", api_key=st.secrets['NRP_API_KEY'], base_url = "https://llm.nrp-nautilus.io/", temperature=0),
302-
# "DeepSeek-R1-Distill-Qwen-32B": BaseChatOpenAI(model = "DeepSeek-R1-Distill-Qwen-32B", api_key=st.secrets['NRP_API_KEY'], base_url = "https://llm.nrp-nautilus.io/", temperature=0),
303-
"watt": ChatOpenAI(model = "watt", api_key=st.secrets['NRP_API_KEY'], base_url = "https://llm.nrp-nautilus.io/", temperature=0),
304-
# "phi3": ChatOpenAI(model = "phi3", api_key=st.secrets['NRP_API_KEY'], base_url = "https://llm.nrp-nautilus.io/", temperature=0),
298+
## dockerized streamlit app wants to read from os.getenv(), otherwise use st.secrets
299+
import os
300+
api_key = os.getenv("NRP_API_KEY")
301+
if api_key is None:
302+
api_key = st.secrets["NRP_API_KEY"]
303+
304+
openrouter_api = os.getenv("OPENROUTER_API_KEY")
305+
if openrouter_api is None:
306+
openrouter_api = st.secrets["OPENROUTER_API_KEY"]
307+
308+
openrouter_endpoint="https://openrouter.ai/api/v1"
309+
nrp_endpoint="https://ellm.nrp-nautilus.io/v1"
310+
311+
# don't use a provider that collects data
312+
data_policy = {
313+
"provider": {
314+
"data_collection": "deny"
315+
}
305316
}
317+
318+
llm_options = {
319+
"gemma-3-27b-it": ChatOpenAI(
320+
model="gemma3",
321+
api_key=api_key,
322+
base_url=nrp_endpoint,
323+
temperature=0
324+
),
325+
"gpt-oss-120b": ChatOpenAI(
326+
model="gpt-oss",
327+
api_key=api_key,
328+
base_url=nrp_endpoint,
329+
temperature=0
330+
),
331+
"trinity-mini": ChatOpenAI(
332+
model="arcee-ai/trinity-mini:free",
333+
api_key=openrouter_api,
334+
base_url=openrouter_endpoint,
335+
temperature=0,
336+
extra_body=data_policy
337+
),
338+
"nemotron-nano-9b-v2": ChatOpenAI(
339+
model="nvidia/nemotron-nano-9b-v2:free",
340+
api_key=openrouter_api,
341+
base_url=openrouter_endpoint,
342+
temperature=0,
343+
extra_body=data_policy
344+
),
345+
}

requirements.txt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,5 @@ geoarrow-pandas==0.1.1
1313
leafmap==0.38.12
1414
SQLAlchemy==2.0.35
1515
duckdb_engine==0.14.2
16-
langchain==0.2.17
17-
langchain-community==0.2.19
18-
langchain-core==0.2.43
19-
langchain-openai==0.1.25
16+
langchain-core==1.1.0
17+
langchain-openai==1.1.0

0 commit comments

Comments
 (0)