Skip to content

Commit 64edf36

Browse files
committed
LangGraph console based instrumentation sample
This sample has region tags to go in the public docs. It is a CLI based LangGraph agent that is manually instrumented for Cloud Obs.
1 parent 5f53472 commit 64edf36

File tree

8 files changed

+3749
-0
lines changed

8 files changed

+3749
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.13
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import sqlite3
16+
import tempfile
17+
18+
from langchain_community.agent_toolkits.sql.toolkit import SQLDatabaseToolkit
19+
from langchain_community.utilities.sql_database import SQLDatabase
20+
from langchain_core.messages import HumanMessage, SystemMessage
21+
from langchain_core.runnables.config import (
22+
RunnableConfig,
23+
)
24+
from langgraph.checkpoint.memory import InMemorySaver
25+
from langgraph.prebuilt import create_react_agent
26+
from opentelemetry import trace
27+
from patched_vertexai import PatchedChatVertexAI
28+
from sqlalchemy import create_engine
29+
from utils import ask_prompt, print_markdown, render_messages
30+
31+
SYSTEM_PROMPT = SystemMessage(
32+
content=f"""\
33+
You are a helpful AI assistant with a mastery of database design and querying. You have access
34+
to an ephemeral sqlite3 database that you can query and modify through some tools. Help answer
35+
questions and perform actions. Follow these rules:
36+
37+
- Make sure you always use sql_db_query_checker to validate SQL statements **before** running
38+
them. In pseudocode: `checked_query = sql_db_query_checker(query);
39+
sql_db_query(checked_query)`.
40+
- Be creative and don't ask for permission! The database is ephemeral so it's OK to make some mistakes.
41+
- The sqlite version is {sqlite3.sqlite_version} which supports multiple row inserts.
42+
- Always prefer to insert multiple rows in a single call to the sql_db_query tool, if possible.
43+
- You may request to execute multiple sql_db_query tool calls which will be run in parallel.
44+
45+
If you make a mistake, try to recover."""
46+
)
47+
48+
INTRO_TEXT = """\
49+
Starting agent using ephemeral SQLite DB {dbpath}. This demo allows you to chat with an Agent
50+
that has full access to an ephemeral sqlite database. The database is initially empty. It is
51+
built with the the LangGraph prebuilt **ReAct Agent** and the **SQLDatabaseToolkit**. Here are some samples you can try:
52+
53+
**Weather**
54+
- Create a new table to hold weather data.
55+
- Populate the weather database with 20 example rows.
56+
- Add a new column for weather observer notes
57+
58+
**Pets**
59+
- Create a database table for pets including an `owner_id` column.
60+
- Add 20 example rows please.
61+
- Create an owner table.
62+
- Link the two tables together, adding new columns, values, and rows as needed.
63+
- Write a query to join these tables and give the result of owners and their pets.
64+
- Show me the query, then the output as a table
65+
66+
---
67+
"""
68+
69+
tracer = trace.get_tracer(__name__)
70+
71+
72+
def run_agent(*, model_name: str, recursion_limit: int = 50) -> None:
73+
model = PatchedChatVertexAI(model=model_name)
74+
checkpointer = InMemorySaver()
75+
thread_id = "default"
76+
77+
# Ephemeral sqlite database per conversation thread
78+
_, dbpath = tempfile.mkstemp(suffix=".db")
79+
engine = create_engine(
80+
f"sqlite:///{dbpath}",
81+
isolation_level="AUTOCOMMIT",
82+
)
83+
84+
# The agent has access to the SQL database through these tools
85+
db = SQLDatabase(engine)
86+
toolkit = SQLDatabaseToolkit(db=db, llm=model)
87+
# Filter out sql_db_list_tables since it only lists the initial tables
88+
tools = [tool for tool in toolkit.get_tools() if tool.name != "sql_db_list_tables"]
89+
90+
# Use the prebuilt ReAct agent graph
91+
# https://langchain-ai.github.io/langgraph/agents/agents/
92+
agent = create_react_agent(
93+
model, tools, checkpointer=checkpointer, prompt=SYSTEM_PROMPT
94+
)
95+
config: RunnableConfig = {
96+
"configurable": {"thread_id": thread_id},
97+
"recursion_limit": recursion_limit,
98+
}
99+
100+
# print intro text
101+
print_markdown(INTRO_TEXT.format(dbpath=dbpath))
102+
103+
while True:
104+
# Accept input from the user
105+
try:
106+
prompt_txt = ask_prompt()
107+
except (EOFError, KeyboardInterrupt):
108+
print_markdown("Exiting...")
109+
break
110+
111+
if not prompt_txt:
112+
continue
113+
prompt = HumanMessage(prompt_txt)
114+
115+
# [START opentelemetry_langgraph_trace_agent]
116+
# Invoke the agent within a span
117+
with tracer.start_as_current_span("invoke agent"):
118+
result = agent.invoke({"messages": [prompt]}, config=config)
119+
# [END opentelemetry_langgraph_trace_agent]
120+
121+
# Print history
122+
render_messages(result["messages"])
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import google.auth
16+
import google.auth.transport.requests
17+
import grpc
18+
from agent import run_agent
19+
from google.auth.transport.grpc import AuthMetadataPlugin
20+
from opentelemetry import _events as events
21+
from opentelemetry import _logs as logs
22+
from opentelemetry import metrics, trace
23+
from opentelemetry.exporter.cloud_logging import CloudLoggingExporter
24+
from opentelemetry.exporter.cloud_monitoring import CloudMonitoringMetricsExporter
25+
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
26+
OTLPSpanExporter,
27+
)
28+
from opentelemetry.instrumentation.sqlite3 import SQLite3Instrumentor
29+
from opentelemetry.instrumentation.vertexai import VertexAIInstrumentor
30+
from opentelemetry.sdk._events import EventLoggerProvider
31+
from opentelemetry.sdk._logs import LoggerProvider
32+
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
33+
from opentelemetry.sdk.metrics import MeterProvider
34+
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
35+
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
36+
from opentelemetry.sdk.trace import TracerProvider
37+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
38+
39+
40+
# [START opentelemetry_langgraph_otel_setup]
41+
def setup_opentelemetry() -> None:
42+
credentials, project_id = google.auth.default()
43+
resource = Resource.create(
44+
attributes={
45+
SERVICE_NAME: "langgraph-sql-agent",
46+
# The project to send spans to
47+
"gcp.project_id": project_id,
48+
}
49+
)
50+
51+
# Set up OTLP auth
52+
request = google.auth.transport.requests.Request()
53+
auth_metadata_plugin = AuthMetadataPlugin(credentials=credentials, request=request)
54+
channel_creds = grpc.composite_channel_credentials(
55+
grpc.ssl_channel_credentials(),
56+
grpc.metadata_call_credentials(auth_metadata_plugin),
57+
)
58+
59+
# Set up OpenTelemetry Python SDK
60+
tracer_provider = TracerProvider(resource=resource)
61+
tracer_provider.add_span_processor(
62+
BatchSpanProcessor(
63+
OTLPSpanExporter(
64+
credentials=channel_creds,
65+
endpoint="https://telemetry.googleapis.com:443/v1/traces",
66+
)
67+
)
68+
)
69+
trace.set_tracer_provider(tracer_provider)
70+
71+
logger_provider = LoggerProvider(resource=resource)
72+
logger_provider.add_log_record_processor(
73+
BatchLogRecordProcessor(CloudLoggingExporter())
74+
)
75+
logs.set_logger_provider(logger_provider)
76+
77+
event_logger_provider = EventLoggerProvider(logger_provider)
78+
events.set_event_logger_provider(event_logger_provider)
79+
80+
reader = PeriodicExportingMetricReader(CloudMonitoringMetricsExporter())
81+
meter_provider = MeterProvider(metric_readers=[reader], resource=resource)
82+
metrics.set_meter_provider(meter_provider)
83+
84+
# Load instrumentors
85+
SQLite3Instrumentor().instrument()
86+
VertexAIInstrumentor().instrument()
87+
88+
89+
# [END opentelemetry_langgraph_otel_setup]
90+
91+
# Make sure to set:
92+
# OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true
93+
# OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true
94+
# in order to full prompts and responses and logs messages.
95+
setup_opentelemetry()
96+
run_agent(
97+
model_name="gemini-2.0-flash",
98+
# You can increase this, but it may incur more token usage
99+
# https://langchain-ai.github.io/langgraph/troubleshooting/errors/GRAPH_RECURSION_LIMIT/#troubleshooting
100+
recursion_limit=50,
101+
)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Copyright The OpenTelemetry Authors
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
from __future__ import annotations
5+
6+
from typing import Any
7+
8+
from google.cloud.aiplatform_v1.types import (
9+
GenerateContentRequest as v1GenerateContentRequest,
10+
)
11+
from google.cloud.aiplatform_v1beta1.types import (
12+
GenerateContentRequest,
13+
)
14+
from langchain_core.messages import (
15+
BaseMessage,
16+
)
17+
from langchain_google_vertexai import ChatVertexAI
18+
19+
20+
class PatchedChatVertexAI(ChatVertexAI):
21+
def _prepare_request_gemini(
22+
self, messages: list[BaseMessage], *args: Any, **kwargs: Any
23+
) -> v1GenerateContentRequest | GenerateContentRequest:
24+
# See https://github.com/langchain-ai/langchain-google/issues/886
25+
#
26+
# Filter out any blocked messages with no content which can appear if you have a blocked
27+
# message from finish_reason SAFETY:
28+
#
29+
# AIMessage(
30+
# content="",
31+
# additional_kwargs={},
32+
# response_metadata={
33+
# "is_blocked": True,
34+
# "safety_ratings": [ ... ],
35+
# "finish_reason": "SAFETY",
36+
# },
37+
# ...
38+
# )
39+
#
40+
# These cause `google.api_core.exceptions.InvalidArgument: 400 Unable to submit request
41+
# because it must include at least one parts field`
42+
43+
messages = [
44+
message
45+
for message in messages
46+
if not message.response_metadata.get("is_blocked", False)
47+
]
48+
return super()._prepare_request_gemini(messages, *args, **kwargs)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
[project]
16+
name = "langgraph-sql-agent"
17+
version = "0.1.0"
18+
description = "A LangGraph ReAct agent that can run queries on an ephemeral SQLite database"
19+
readme = "README.md"
20+
requires-python = ">=3.12"
21+
dependencies = [
22+
"langchain-community>=0.3.16",
23+
"langchain-google-vertexai>=2.0.7",
24+
"langgraph>=0.4.3",
25+
"opentelemetry-exporter-gcp-logging>=1.9.0a0",
26+
"opentelemetry-exporter-gcp-monitoring>=1.9.0a0",
27+
"opentelemetry-exporter-otlp-proto-grpc>=1.33.0",
28+
"opentelemetry-instrumentation-httpx>=0.54b0",
29+
"opentelemetry-instrumentation-requests>=0.54b0",
30+
"opentelemetry-instrumentation-sqlite3>=0.54b0",
31+
"opentelemetry-instrumentation-vertexai>=2.0b0",
32+
"rich>=14.0.0",
33+
]

0 commit comments

Comments
 (0)