Demo / teaching code only. Built for Module 10: AgenticOps Lifecycle of the Applied Agentic AI for Modern Data Engineering certificate programme, hosted by IDEAS, ISI Kolkata.
This code is for demo / teaching purposes only and is not to be shipped or packaged as a production version. It demonstrates patterns and integrations for educational use; production systems require additional concerns (rate limiting, secret management, retry policies, alerting, audit trails, PII handling) that are intentionally out of scope here.
A LangGraph ReAct agent that researches Indian stock markets (NSE / BSE) using three tools — real-time stock data, financial news search, and a financial calculator. Bundled with a Streamlit UI that visualises the agent's Think → Act → Observe → Synthesise reasoning cycle in real time, plus optional LangFuse instrumentation for trace observability.
- LangGraph's
create_react_agentwith three custom@tool-decorated functions - A model-agnostic agent — default OpenAI
gpt-4o-mini; one-line swap to Groq Llama for free-play mode - Live ReAct visualisation in Streamlit (Thinking / Action / Observation / Final Answer cards, streamed)
- Two-line LangFuse integration that captures every reasoning cycle, tool call, and token without instrumenting any of the tool code itself
User Query
|
v
+-----------------------+ +--------------------+
| Streamlit UI (app.py) | -----> | Agent (agent.py) |
+-----------------------+ +--------------------+
|
v
LangGraph create_react_agent
|
+-------------------------+-----------------------+
| |
v v
OpenAI gpt-4o-mini 3 Tools:
(or Groq Llama) - stock_data_fetcher (yfinance)
- market_news_search (Tavily)
- financial_calculator (Python)
|
v
LangFuse CallbackHandler (optional)
|
v
cloud.langfuse.com
The agent runs perfectly well headlessly via python agent.py — that is the production-shape interface, no UI required. So why ship a Streamlit wrapper alongside it?
Because ReAct is invisible until you see it happen.
The core teaching point of Module 10 is how agents reason. If a student runs an agent and only sees a final answer, they miss the most important thing: the cycles the agent went through to get there. Did it call one tool or three? Did it reason between calls? When did it decide it had enough information to stop? These are exactly the questions LangFuse answers after the fact by producing a trace tree. The Streamlit UI answers them as they happen, in front of the learner.
The Streamlit app is therefore a teaching wrapper, not the agent itself. The agent (agent.py) is self-contained and could be called from a CLI, a notebook, a backend service, or any other harness. The Streamlit UI (app.py) exists exclusively to make the agent's reasoning legible while it runs.
When a query is submitted, the UI streams events from the agent in real time and renders four card types in sequence. Each card maps directly to an event emitted by LangChain / LangGraph internally — and to a node you would later see in the LangFuse trace.
| Card | What's happening | Underlying event | Maps to LangFuse trace as |
|---|---|---|---|
| THINKING | The LLM is reasoning about what to do next — what data it needs, which tool fits, why | AIMessage with text content but no tool_calls yet |
GENERATION observation with prose output |
| ACTION | The LLM has decided to call a tool — tool name and arguments shown | AIMessage with structured tool_calls |
GENERATION observation followed by a TOOL invocation |
| OBSERVATION | The tool ran and returned data — the agent now has new information to reason over | ToolMessage with the tool's output |
TOOL observation with input + output captured |
| Final answer | The agent has enough context to synthesise a response and stops looping | AIMessage with content but no tool_calls |
GENERATION observation with the answer |
A simple query like "What is Reliance's current price?" produces three cards (Thinking → Action → Observation) followed by the final answer. A more complex query like "Analyse Reliance Industries — is it overvalued?" produces a longer sequence with multiple cycles visible — students literally count the Think-Act-Observe iterations as they appear on screen.
The cards are not cosmetic. They make three abstract ideas concrete:
- ReAct is a loop, not a one-shot call. Students see the agent oscillating between Thinking and Acting multiple times before the final answer. The number of
THINKINGcards equals the number of reasoning cycles. The loop is visible. - Tool selection happens at runtime, not at compile time. Different queries trigger different tool sequences. Run the same query twice and the sequence may even differ slightly. This is the autonomy that distinguishes an agent from a fixed pipeline — and it is invisible without a UI like this one.
- The final answer is a synthesis, not a tool output. The card with no Action attached is the LLM combining everything it observed into a single coherent response. Students often assume the last tool's output is the answer; this UI shows clearly that it is not.
The Streamlit cards and the LangFuse trace are two views of the exact same execution. Each query runs once; both views see it from a different angle:
- The Streamlit UI shows it as it happens. Cards animate in real time. Ephemeral — once the chat is cleared, the visual record is gone. Designed for intuition: "I can see the agent thinking."
- LangFuse captures it permanently. The same events become a structured trace tree at
cloud.langfuse.com, queryable across hundreds of past runs. Designed for analysis: "What did the agent do, when, with what tokens, at what cost?"
In a teaching session this works as a deliberate two-step that students should adopt as a habit during their own assignment work:
- Run a query in the Streamlit UI. Watch the THINKING → ACTION → OBSERVATION cards stream in. Count the cycles. See which tools fired. Notice that more complex queries trigger more cycles than simple ones.
- Switch tabs to the LangFuse dashboard. The same query is now a persistent trace tree. Click the trace and you see the cycles again — but now with structural detail the live UI deliberately hides for clarity: token counts on every LLM call, latencies on every tool, the full system prompt + user prompt + completion captured per generation, parallel tools rendered side-by-side as branching tree nodes, sequential cycles stacked vertically.
Concretely, what each view answers:
| Question | Streamlit shows? | LangFuse shows? |
|---|---|---|
| What did the agent reason about? | Yes (text in card) | Yes (full prompt + completion) |
| How many tool calls happened? | Yes (count cards) | Yes (count TOOL observations) |
| Were tools called in parallel within a cycle? | Implied (cards in same group) | Explicit (parallel branch in the tree) |
| How many tokens did each step cost? | No | Yes (per-node breakdown) |
| Cumulative cost / latency for this run? | No | Yes (workflow root totals) |
| What did the LLM see in cycle 3 vs cycle 1? | No | Yes (full input per generation) |
| How does this run compare to 50 prior runs? | No | Yes (dashboard — sortable, filterable, exportable) |
This is the bridge between demo (the UI gives stakeholders a fast intuition for how an agent reasons) and production observability (LangFuse gives engineers the data to debug, optimise, and audit). The Module 10 teaching arc deliberately runs each query in the UI first, then opens LangFuse to inspect what just happened — making both the "what" and the "why" visible in the same session.
See How LangFuse tracing works (the handshake) below for the three-line code wiring that makes this possible — and why the tool functions themselves require zero instrumentation.
The Streamlit app is a thin presentation layer around one function in agent.py:
app.pyimportsstream_agentfromagent.py.- When the user submits a query,
stream_agent()invokes the LangGraph agent instream_mode="values"and yields structured events —{"type": "thinking" | "tool_call" | "tool_result" | "final_answer", "content": ..., "metadata": ...}. app.pylistens to those events and routes each one to the matching card renderer (the_react_step_html()function maps event type → coloured HTML card).- Cards appear progressively in the chat panel as the agent reasons — there is no buffering, no "wait for completion." Students watch the ReAct loop unfold token by token.
If you remove app.py entirely and call run_agent() (the non-streaming counterpart) from your own code, you get exactly the same execution — same tools, same reasoning, same final answer. You just don't see the cycles. The Streamlit wrapper exists exclusively to make reasoning legible; it adds zero behaviour to the agent itself.
This separation is itself a teaching point: in production, the agent code (agent.py) is what you ship; the visualisation layer (app.py) is what you build for demos, internal debugging, and stakeholder reviews. They are deliberately decoupled.
- Python 3.10 or later
- API keys for:
# 1. Clone this repository
git clone https://github.com/devdutta/langgraph-react-agent-with-langfuse.git
cd langgraph-react-agent-with-langfuse
# 2. Create a virtual environment (recommended)
python -m venv .venv
.venv\Scripts\activate # Windows
# source .venv/bin/activate # macOS / Linux
# 3. Install dependencies
pip install -r requirements.txt
# 4. Copy the environment template and fill in your keys
copy .env.example .env # Windows
# cp .env.example .env # macOS / Linux
# Then open .env in any text editor and replace the REPLACE_ME values.The Setup steps above use venv (Python's built-in virtual-environment tool). If you prefer Anaconda or work primarily inside an IDE, the same requirements.txt and .env apply — only the environment-creation step changes.
Anaconda / conda environment:
conda create -n financial-agent python=3.11
conda activate financial-agent
pip install -r requirements.txt
# Then continue with the .env setup as aboveAfter this, run python agent.py or streamlit run app.py from the same activated terminal.
PyCharm / VS Code / Cursor / Jupyter / any IDE: Open the cloned folder as a project, then point the project interpreter at:
- Your
.venv\Scripts\python.exeif you usedvenv, or - Your conda environment's Python if you used Anaconda (e.g.
C:\Users\<you>\anaconda3\envs\financial-agent\python.exe)
The IDE handles activation automatically — running agent.py or launching a terminal from inside the IDE both use the selected interpreter. No extra steps needed.
One thing to know about
run_demo.bat: the script auto-detects.venv\Scripts\python.exeonly. If you're using a conda environment instead, activate it in your terminal first (conda activate financial-agent), then runrun_demo.bat. The script will fall back to whicheverpythonis on your active PATH — which, post-activation, is the conda env's Python.Alternatively: launch Streamlit from inside your IDE's terminal with
streamlit run app.py— the IDE's interpreter selection does the work for you.
Common pitfall: install dependencies and run the agent from the same environment. If you
pip install -r requirements.txtin your base Python and then runstreamlit run app.pyfrom a conda env, you'll seeModuleNotFoundError. Always checkpython --versionandwhere python(Windows) /which python(macOS/Linux) before installing to confirm where you are.
Test the agent headless (any platform — verify keys work before launching the UI):
python agent.pyThis runs a single hardcoded query (Reliance Industries) and prints the result.
Windows — quick launch (run_demo.bat):
Double-click run_demo.bat from File Explorer, or run it from a terminal:
run_demo.batWhat it does:
- Changes to the script's directory automatically (no manual
cdneeded) - Checks that
.envexists; if missing, prints a clear error and exits before starting Streamlit - Auto-detects
.venv\Scripts\python.exeif present, otherwise falls back to systempython - Starts the Streamlit server on port 8501 in the foreground so you can see the logs
- Opens your default browser to
http://localhost:8501after a 5-second startup wait
To stop: press Ctrl+C in the terminal window, then close it.
Launch the Streamlit UI manually (any platform):
streamlit run app.pyOpen the URL shown in your terminal (usually http://localhost:8501).
LangFuse is an observability platform that captures the agent's full reasoning path as a "trace" — every LLM call, every tool invocation, every cycle, with token counts and latencies. Without it, the agent runs perfectly fine; you just don't get the visibility.
1. The handler is created (in agent.py, inside create_financial_agent):
from langfuse.langchain import CallbackHandler
# Reads LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY, LANGFUSE_HOST from environment
langfuse_handler = CallbackHandler()2. The handler is wired into the agent invocation (in run_agent and stream_agent):
config = {"callbacks": [langfuse_handler]}
agent.invoke({"messages": [("user", query)]}, config=config)3. That's it. No instrumentation in the tool functions. No log calls. No spans.
- LangChain and LangGraph internally emit callback events for everything that happens during agent execution: chain start, LLM start, LLM end, tool start, tool end, chain end, and so on.
- The
CallbackHandlersubscribes to these events. - For each event, the handler ships the payload (with token counts, durations, inputs, outputs) to your LangFuse project at the URL specified by
LANGFUSE_HOST. - LangFuse builds these events into a trace tree visible at
https://cloud.langfuse.com(or your EU / self-hosted instance).
In your .env:
LANGFUSE_ENABLED=true
LANGFUSE_PUBLIC_KEY=pk-lf-xxxxxxxxxxxx
LANGFUSE_SECRET_KEY=sk-lf-xxxxxxxxxxxx
LANGFUSE_HOST=https://us.cloud.langfuse.com
To get these:
- Sign up at https://cloud.langfuse.com/
- Create a new organisation → new project
- Settings → API Keys → copy the public key (starts with
pk-lf-) and secret key (starts withsk-lf-) - Use
https://us.cloud.langfuse.comfor the US region orhttps://cloud.langfuse.comfor the EU region
Set LANGFUSE_ENABLED=false in your .env, or simply leave the public / secret keys blank. The agent will run normally and skip tracing.
OpenAI's gpt-4o-mini is cheap (~$0.001/query) but not free. For unlimited experimentation, swap to Groq's free tier:
-
Sign up at https://console.groq.com/ and copy your API key.
-
Add to
.env:GROQ_API_KEY=gsk_xxxxxxxxxxxx -
In
agent.py, change the LLM initialisation insidecreate_financial_agent:From:
from langchain_openai import ChatOpenAI ... llm = ChatOpenAI( model=model_name, temperature=0.1, max_tokens=2048, api_key=openai_api_key )
To:
from langchain_groq import ChatGroq ... llm = ChatGroq( model="llama-3.3-70b-versatile", temperature=0.1, max_tokens=2048, api_key=os.getenv("GROQ_API_KEY"), )
-
Update the env validation at the top of
create_financial_agentto checkGROQ_API_KEYinstead ofOPENAI_API_KEY.
That's it. Free queries.
Note: Groq's free tier has rate limits (30 RPM, ~6,000 TPM at the time of writing). For heavy experimentation, upgrade to Groq's paid tier or stay on OpenAI.
| Symptom | Likely cause | Fix |
|---|---|---|
ImportError: No module named 'langchain_tavily' |
Missing dependency | Re-run pip install -r requirements.txt |
OPENAI_API_KEY not found on startup |
Missing or unfilled .env |
Confirm .env exists and has OPENAI_API_KEY=sk-... (not REPLACE_ME) |
| Agent runs but no traces appear in LangFuse | Wrong region or missing keys | Verify LANGFUSE_HOST matches the region where you signed up |
429 rate limit from OpenAI |
API rate limit hit | Wait 60 s and retry. Set a usage cap in your OpenAI dashboard |
RateLimitError from Tavily |
Free tier exhausted | Wait until next month or upgrade |
yfinance returns no data |
yfinance occasionally rate-limits or rejects tickers | Try a different ticker or wait a few minutes |
| Streamlit shows a blank screen | Browser cache | Force-refresh (Ctrl+Shift+R) |
langgraph-react-agent-with-langfuse/
├── README.md ← this file
├── requirements.txt ← Python dependencies (pinned)
├── .env.example ← environment variable template
├── .gitignore ← excludes secrets and run artifacts
├── agent.py ← the LangGraph ReAct agent + 3 tools + LangFuse handler
├── app.py ← Streamlit chat UI with ReAct visualisation
└── run_demo.bat ← Windows quick-launch script (auto-detects .venv, opens browser)
Developed as a teaching demo for Module 10: AgenticOps Lifecycle of the Applied Agentic AI for Modern Data Engineering certificate programme, hosted by IDEAS, ISI Kolkata.
Author: Devjothi Dutta — Faculty, IDEAS, ISI Kolkata.
This code is for demo / teaching purposes only and is not to be shipped or packaged as a production version.