Skip to content

Commit 573b3c8

Browse files
authored
Merge pull request #12 from NicholasGoh/feat/langfuse-observability
Feat/langfuse observability
2 parents 257e506 + 9b4793d commit 573b3c8

File tree

8 files changed

+96
-9
lines changed

8 files changed

+96
-9
lines changed

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ Visit the Github: [![FastAPI MCP LangGraph Template](https://img.shields.io/gith
4747
- [![FastAPI](https://img.shields.io/github/stars/fastapi/fastapi?logo=fastapi&label=fastapi)](https://github.com/fastapi/fastapi) for Python backend API
4848
- [![SQLModel](https://img.shields.io/github/stars/fastapi/sqlmodel?logo=sqlmodel&label=SQLModel)](https://github.com/fastapi/sqlmodel) for Python SQL database interactions (ORM + Validation).
4949
- Wrapper of [![SQLAlchemy](https://img.shields.io/github/stars/sqlalchemy/sqlalchemy?logo=sqlalchemy&label=SQLAlchemy)](https://github.com/sqlalchemy/sqlalchemy)
50+
- [![LangFuse](https://img.shields.io/github/stars/langfuse/langfuse?logo=langfuse&label=LangFuse)](https://github.com/langfuse/langfuse) for LLM Observability and LLM Metrics
5051
- [![Pydantic](https://img.shields.io/github/stars/pydantic/pydantic?logo=pydantic&label=Pydantic)](https://github.com/pydantic/pydantic) for Data Validation and Settings Management.
5152
- [![Supabase](https://img.shields.io/github/stars/supabase/supabase?logo=supabase&label=Supabase)](https://github.com/supabase/supabase) for DB RBAC
5253
- [![PostgreSQL](https://img.shields.io/github/stars/postgres/postgres?logo=postgresql&label=Postgres)](https://github.com/postgres/postgres) Relational DB
@@ -56,7 +57,6 @@ Visit the Github: [![FastAPI MCP LangGraph Template](https://img.shields.io/gith
5657

5758
### Planned Features
5859

59-
- [![LangFuse](https://img.shields.io/github/stars/langfuse/langfuse?logo=langfuse&label=LangFuse)](https://github.com/langfuse/langfuse) for LLM Observability and LLM Metrics
6060
- [![Prometheus](https://img.shields.io/github/stars/prometheus/prometheus?logo=prometheus&label=Prometheus)](https://github.com/prometheus/prometheus) for scraping Metrics
6161
- [![Grafana](https://img.shields.io/github/stars/prometheus/prometheus?logo=grafana&label=Grafana)](https://github.com/grafana/grafana) for visualizing Metrics
6262
- [![Auth0](https://img.shields.io/badge/Auth0-white?logo=auth0)](https://auth0.com/docs) SaaS for Authentication and Authorization with OIDC & JWT via OAuth 2.0
@@ -199,6 +199,13 @@ Add your following API keys and value to the respective file: `./envs/backend.en
199199
```bash
200200
OPENAI_API_KEY=sk-proj-...
201201
POSTGRES_DSN=postgresql://postgres...
202+
203+
LANGFUSE_PUBLIC_KEY=pk-lf-...
204+
LANGFUSE_SECRET_KEY=sk-lf-...
205+
LANGFUSE_HOST=https://cloud.langfuse.com
206+
207+
ENVIRONMENT=production
208+
202209
YOUTUBE_API_KEY=...
203210
```
204211

backend/api/core/agent/orchestration.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from langgraph.prebuilt import ToolNode, tools_condition
1111

1212
from api.core.agent.prompts import SYSTEM_PROMPT
13+
from api.core.dependencies import LangfuseHandlerDep
1314

1415

1516
class State(MessagesState):
@@ -70,7 +71,8 @@ def get_graph(
7071
return graph_factory(worker_node, tools, checkpointer, name)
7172

7273

73-
def get_config():
74+
def get_config(langfuse_handler: LangfuseHandlerDep):
7475
return dict(
7576
configurable=dict(thread_id="1"),
77+
callbacks=[langfuse_handler],
7678
)

backend/api/core/config.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,11 @@ def checkpoint_conn_str(self) -> str:
3333
# with specifying psycopg driver explicitly
3434
return self.postgres_dsn.encoded_string()
3535

36+
langfuse_public_key: str = ""
37+
langfuse_secret_key: str = ""
38+
langfuse_host: str = "https://cloud.langfuse.com"
39+
40+
environment: str = "development"
41+
3642

3743
settings = Settings()

backend/api/core/dependencies.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from fastapi import Depends
55
from langchain_mcp_adapters.tools import load_mcp_tools
66
from langchain_openai import ChatOpenAI
7+
from langfuse.callback import CallbackHandler
78
from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine
89

910
from api.core.agent.persistence import checkpointer_context
@@ -47,3 +48,17 @@ async def setup_graph() -> AsyncGenerator[Resource]:
4748
tools=tools,
4849
session=session,
4950
)
51+
52+
53+
def get_langfuse_handler() -> CallbackHandler:
54+
55+
return CallbackHandler(
56+
public_key=settings.langfuse_public_key,
57+
secret_key=settings.langfuse_secret_key,
58+
host=settings.langfuse_host,
59+
session_id=settings.environment,
60+
environment=settings.environment,
61+
)
62+
63+
64+
LangfuseHandlerDep = Annotated[CallbackHandler, Depends(get_langfuse_handler)]

backend/api/routers/llms.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from starlette.responses import Response
99

1010
from api.core.agent.orchestration import get_config, get_graph
11-
from api.core.dependencies import LLMDep, setup_graph
11+
from api.core.dependencies import LangfuseHandlerDep, LLMDep, setup_graph
1212
from api.core.logs import print, uvicorn
1313

1414
router = APIRouter(tags=["chat"])
@@ -26,13 +26,17 @@ async def completions(query: str, llm: LLMDep) -> Response:
2626

2727

2828
@router.get("/chat/agent")
29-
async def agent(query: str, llm: LLMDep) -> Response:
29+
async def agent(
30+
query: str,
31+
llm: LLMDep,
32+
langfuse_handler: LangfuseHandlerDep,
33+
) -> Response:
3034
"""Stream LangGraph completions as Server-Sent Events (SSE).
3135
3236
This endpoint streams LangGraph-generated events in real-time, allowing the client
3337
to receive responses as they are processed, useful for agent-based workflows.
3438
"""
35-
return EventSourceResponse(stream_graph(query, llm))
39+
return EventSourceResponse(stream_graph(query, llm, langfuse_handler))
3640

3741

3842
async def stream_completions(
@@ -57,17 +61,23 @@ async def checkpointer_setup(pool):
5761
async def stream_graph(
5862
query: str,
5963
llm: LLMDep,
64+
langfuse_handler: LangfuseHandlerDep,
6065
) -> AsyncGenerator[dict[str, str], None]:
6166
async with setup_graph() as resource:
6267
graph = get_graph(
6368
llm,
6469
tools=resource.tools,
6570
checkpointer=resource.checkpointer,
71+
name="math_agent",
6672
)
67-
config = get_config()
73+
config = get_config(langfuse_handler)
6874
events = dict(messages=[HumanMessage(content=query)])
6975

70-
async for event in graph.astream_events(events, config, version="v2"):
71-
if event.get("event").endswith("end"):
72-
print(event)
76+
async for event in graph.astream_events(
77+
events,
78+
config,
79+
version="v2",
80+
stream_mode="updates",
81+
):
7382
yield dict(data=event)
83+
print(event)

docs/langfuse.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,41 @@
55
Learn more [here](https://langfuse.com/)
66

77
[![LangFuse](https://img.shields.io/github/stars/langfuse/langfuse?logo=langfuse&label=LangFuse)](https://github.com/langfuse/langfuse)
8+
9+
Below is an outline of the steps involved in a simple Math Agent. Key elements illustrated include:
10+
11+
- A visual breakdown of each step—e.g., when the agent invokes a tool and when control returns to the agent
12+
- Inputs and outputs at each stage of the process:
13+
- **User to Agent**: The user asks a natural language question — e.g., `What is 1 + 1?`
14+
- **Agent to Tool**: The agent decides to call a calculator tool with structured arguments — e.g., `args: { a: 1, b: 1 }`.
15+
- **Tool to Agent**: The tool executes the operation and returns the result — e.g., `2`.
16+
- **Agent to User**: The agent responds with the final answer in natural language — e.g., `1 + 1 = 2`.
17+
- The full chat history throughout the interaction
18+
- Latency and cost associated with each node
19+
20+
### Step 1: Math Agent (User to Agent → Agent to Tool)
21+
22+
This section shows:
23+
24+
- **User to Agent**: The user asks a natural language question — e.g., `What is 1 + 1?`
25+
- **Agent to Tool**: The agent decides to call a calculator tool with structured arguments — e.g., `args: { a: 1, b: 1 }`.
26+
- **Full Chat History Throughout the Interaction**: You can inspect earlier user-agent messages. For instance:
27+
28+
```text
29+
User: reply only no
30+
Agent: No.
31+
```
32+
33+
In this example, the agent responded directly without calling any tools.
34+
35+
### Step 2: Tool Call (Tool to Agent)
36+
37+
This section shows:
38+
39+
- **Tool to Agent**: The tool executes the operation and returns the result — e.g., `2`.
40+
41+
### Step 3: Math Agent (Agent to User)
42+
43+
This section shows:
44+
45+
- **Agent to User**: The agent responds with the final answer in natural language — e.g., `1 + 1 = 2`.

envs/backend.env

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
11
OPENAI_API_KEY=
22
# do not specify driver (do not specify `+psycopg`)
33
POSTGRES_DSN=
4+
5+
LANGFUSE_PUBLIC_KEY=pk-lf-
6+
LANGFUSE_SECRET_KEY=sk-lf-
7+
LANGFUSE_HOST=https://cloud.langfuse.com
8+
9+
ENVIRONMENT=production

nginx/nginx.conf

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ server {
2323
proxy_set_header Upgrade $http_upgrade;
2424
proxy_set_header Host $host;
2525

26+
proxy_read_timeout 600;
27+
proxy_send_timeout 600;
28+
2629
proxy_buffering off;
2730
proxy_redirect off;
2831
proxy_pass http://api/;

0 commit comments

Comments
 (0)