PaperScout / app.py
vaishnaveswar's picture
Update app.py
d018685 verified
# app.py
import os
import uuid
import gradio as gr
from langchain_core.chat_history import (
InMemoryChatMessageHistory,
BaseChatMessageHistory,
)
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_core.runnables import RunnableLambda, RunnableParallel
import os
from core import answer_as_table
# Ensure GOOGLE_API_KEY is set in environment before running.
# Example:
# os.environ["GOOGLE_API_KEY"] = "your-google-api-key"
# Prompt scaffolding (used only to satisfy history insertion)
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"You are a helpful academic assistant that generates literature reviews.",
),
MessagesPlaceholder(variable_name="history"),
("human", "{question}"),
]
)
def identity(inputs: dict) -> dict:
return {
"question": (inputs.get("question") or "").strip(),
"use_web": bool(inputs.get("use_web", False)),
"region": (inputs.get("region") or "us-en"),
"safesearch": (inputs.get("safesearch") or "moderate"),
"timelimit": (inputs.get("timelimit") or None),
"backend": (inputs.get("backend") or None),
"max_results": int(inputs.get("max_results") or 20),
}
id_runnable = RunnableLambda(identity)
def _orchestrate(inputs: dict) -> str:
text = (inputs.get("question") or "").strip()
use_web = bool(inputs.get("use_web", False))
region = inputs.get("region") or "us-en"
safesearch = inputs.get("safesearch") or "moderate"
timelimit = inputs.get("timelimit") or None
backend = inputs.get("backend") or None
max_results = int(inputs.get("max_results") or 20)
if not text:
# Return a small info table only when user provided nothing
return (
"| Intent | Reply |\n"
"|--------|-------|\n"
"| Help | Please enter a research topic or a message. |\n"
)
# Route to web TABLE or plain chat text depending on use_web
return answer_as_table(
text,
region=region,
max_results=max_results,
safesearch=safesearch,
timelimit=timelimit,
backend=backend,
force_web=use_web,
)
core_runnable = RunnableLambda(_orchestrate)
# Run prompt and identity in parallel, then pick the identity output to feed core.
# Prompt runs solely to let RunnableWithMessageHistory insert 'history'.
combined = (
RunnableParallel(prompt=prompt, data=id_runnable).pick("data")
) | core_runnable
# Session-scoped history
_store: dict[str, BaseChatMessageHistory] = {}
def get_session_history(session_id: str) -> BaseChatMessageHistory:
if session_id not in _store:
_store[session_id] = InMemoryChatMessageHistory()
return _store[session_id]
with_history = RunnableWithMessageHistory(
combined,
get_session_history,
input_messages_key="question",
history_messages_key="history",
) # requires config={"configurable": {"session_id": "<id>"}} on invoke
def respond(
message,
history,
use_web,
session_state,
region,
safesearch,
timelimit,
backend,
max_results,
):
"""
- message: dict or str (ChatInterface type='messages' passes a dict with 'text')
- history: UI history (Gradio-managed; LangChain history is separate)
- use_web: checkbox
- session_state: gr.State carrying a stable session_id to isolate histories across users
- region, safesearch, timelimit, backend, max_results: web search controls
"""
text = (message.get("text") if isinstance(message, dict) else message) or ""
text = text.strip()
if not text:
return (
"| Intent | Reply |\n"
"|--------|-------|\n"
"| Help | Please enter a research topic or a message. |\n"
), session_state
# Ensure a per-user session id for RunnableWithMessageHistory
session_id = session_state.get("session_id")
if not session_id:
session_id = f"conv-{uuid.uuid4().hex}"
session_state["session_id"] = session_id
try:
output = with_history.invoke(
{
"question": text,
"use_web": bool(use_web),
"region": (region or "us-en"),
"safesearch": (safesearch or "moderate"),
"timelimit": (timelimit or None),
"backend": (backend or None),
"max_results": int(max_results or 20),
},
config={"configurable": {"session_id": session_id}},
)
# output is either a Markdown TABLE (web) or plain chat text (no web)
return output, session_state
except Exception as e:
return (
f"| Intent | Reply |\n|--------|-------|\n| Error | {str(e)} |\n"
), session_state
with gr.Blocks(title="Literature Review Chat") as demo:
gr.Markdown(
"Enter a research topic to generate a Markdown literature review table (enable web), or chat for quick help (plain text)."
)
session_state = gr.State(
{"session_id": None}
) # session-persistent state in the browser tab
with gr.Row():
use_web = gr.Checkbox(label="Use web search (academic sources)", value=True)
region = gr.Dropdown(
choices=["us-en", "wt-wt", "uk-en", "ca-en", "in-en", "de-de", "fr-fr"],
value="us-en",
label="Region",
)
safesearch = gr.Dropdown(
choices=["on", "moderate", "off"], value="moderate", label="SafeSearch"
)
timelimit = gr.Dropdown(
choices=[None, "d", "w", "m", "y"], value=None, label="Time limit"
)
backend = gr.Dropdown(
choices=[None, "api", "html", "lite"], value=None, label="DDG backend"
)
max_results = gr.Slider(
minimum=5, maximum=50, value=20, step=1, label="Max results"
)
chat = gr.ChatInterface(
fn=respond,
additional_inputs=[
use_web,
session_state,
region,
safesearch,
timelimit,
backend,
max_results,
],
additional_outputs=[session_state],
type="messages",
title="Literature Review Chat",
description="Toggle the checkbox to search the web and produce a literature review table; otherwise, get a concise plain-text chat reply.",
save_history=True,
)
if __name__ == "__main__":
demo.launch()