Langgraph (Official Sample)
59by A2A Project
Official A2A python sample agent: Langgraph
Getting Started
README
LangGraph Currency Agent with A2A Protocol
This sample demonstrates a currency conversion agent built with LangGraph and exposed through the A2A protocol. It showcases conversational interactions with support for multi-turn dialogue and streaming responses.
How It Works
This agent uses LangGraph with LLM (for example Google Gemini..) to provide currency exchange information through a ReAct agent pattern. The A2A protocol enables standardized interaction with the agent, allowing clients to send requests and receive real-time updates.
sequenceDiagram
participant Client as A2A Client
participant Server as A2A Server
participant Agent as LangGraph Agent
participant API as Frankfurter API
Client->>Server: Send task with currency query
Server->>Agent: Forward query to currency agent
alt Complete Information
Agent->>API: Call get_exchange_rate tool
API->>Agent: Return exchange rate data
Agent->>Server: Process data & return result
Server->>Client: Respond with currency information
else Incomplete Information
Agent->>Server: Request additional input
Server->>Client: Set state to "input-required"
Client->>Server: Send additional information
Server->>Agent: Forward additional info
Agent->>API: Call get_exchange_rate tool
API->>Agent: Return exchange rate data
Agent->>Server: Process data & return result
Server->>Client: Respond with currency information
end
alt With Streaming
Note over Client,Server: Real-time status updates
Server->>Client: "Looking up exchange rates..."
Server->>Client: "Processing exchange rates..."
Server->>Client: Final result
end
Key Features
- Multi-turn Conversations: Agent can request additional information when needed
- Real-time Streaming: Provides status updates during processing
- Push Notifications: Support for webhook-based notifications
- Conversational Memory: Maintains context across interactions
- Currency Exchange Tool: Integrates with Frankfurter API for real-time rates
Prerequisites
- Python 3.12 or higher
- UV
- Access to an LLM and API Key
Setup & Running
-
Navigate to the samples directory:
cd samples/python/agents/langgraph -
Create an environment file with your API key:
If you're using a Google Gemini model (gemini-pro, etc.): echo "GOOGLE_API_KEY=your_api_key_here" > .env If you're using OpenAI or any compatible API (e.g., local LLM via Ollama, LM Studio, etc.): echo "API_KEY=your_api_key_here" > .env (not neccessary if have no api key) echo "TOOL_LLM_URL=your_llm_url" > .env echo "TOOL_LLM_NAME=your_llm_name" > .env -
Run the agent:
# Basic run on default port 10000 uv run app # On custom host/port uv run app --host 0.0.0.0 --port 8080 -
In a separate terminal, run the test client:
uv run app/test_client.py
Build Container Image
Agent can also be built using a container file.
- Navigate to the
samples/python/agents/langgraphdirectory:
cd samples/python/agents/langgraph
-
Build the container file
podman build . -t langgraph-a2a-server
[!Tip]
Podman is a drop-in replacement fordockerwhich can also be used in these commands.
-
Run your container
podman run -p 10000:10000 -e GOOGLE_API_KEY=your_api_key_here langgraph-a2a-server -
Run A2A client (follow step 5 from the section above)
[!Important]
- Access URL: You must access the A2A client through the URL
0.0.0.0:10000. Usinglocalhostwill not work.- Hostname Override: If you're deploying to an environment where the hostname is defined differently outside the container, use the
HOST_OVERRIDEenvironment variable to set the expected hostname on the Agent Card. This ensures proper communication with your client application.
Technical Implementation
- LangGraph ReAct Agent: Uses the ReAct pattern for reasoning and tool usage
- Streaming Support: Provides incremental updates during processing
- Checkpoint Memory: Maintains conversation state between turns
- Push Notification System: Webhook-based updates with JWK authentication
- A2A Protocol Integration: Full compliance with A2A specifications
Limitations
- Only supports text-based input/output (no multi-modal support)
- Uses Frankfurter API which has limited currency options
- Memory is session-based and not persisted between server restarts
Examples
Synchronous request
Request:
POST http://localhost:10000
Content-Type: application/json
{
"id": "12113c25-b752-473f-977e-c9ad33cf4f56",
"jsonrpc": "2.0",
"method": "message/send",
"params": {
"message": {
"kind": "message",
"messageId": "120ec73f93024993becf954d03a672bc",
"parts": [
{
"kind": "text",
"text": "how much is 10 USD in INR?"
}
],
"role": "user"
}
}
}
Response:
{
"id": "12113c25-b752-473f-977e-c9ad33cf4f56",
"jsonrpc": "2.0",
"result": {
"artifacts": [
{
"artifactId": "08373241-a745-4abe-a78b-9ca60882bcc6",
"name": "conversion_result",
"parts": [
{
"kind": "text",
"text": "10 USD is 856.2 INR."
}
]
}
],
"contextId": "e329f200-eaf4-4ae9-a8ef-a33cf9485367",
"history": [
{
"contextId": "e329f200-eaf4-4ae9-a8ef-a33cf9485367",
"kind": "message",
"messageId": "120ec73f93024993becf954d03a672bc",
"parts": [
{
"kind": "text",
"text": "how much is 10 USD in INR?"
}
],
"role": "user",
"taskId": "58124b63-dd3b-46b8-bf1d-1cc1aefd1c8f"
},
{
"contextId": "e329f200-eaf4-4ae9-a8ef-a33cf9485367",
"kind": "message",
"messageId": "d8b4d7de-709f-40f7-ae0c-fd6ee398a2bf",
"parts": [
{
"kind": "text",
"text": "Looking up the exchange rates..."
}
],
"role": "agent",
"taskId": "58124b63-dd3b-46b8-bf1d-1cc1aefd1c8f"
},
{
"contextId": "e329f200-eaf4-4ae9-a8ef-a33cf9485367",
"kind": "message",
"messageId": "ee0cb3b6-c3d6-4316-8d58-315c437a2a77",
"parts": [
{
"kind": "text",
"text": "Processing the exchange rates.."
}
],
"role": "agent",
"taskId": "58124b63-dd3b-46b8-bf1d-1cc1aefd1c8f"
}
],
"id": "58124b63-dd3b-46b8-bf1d-1cc1aefd1c8f",
"kind": "task",
"status": {
"state": "completed"
}
}
}
Multi-turn example
Request - Seq 1:
POST http://localhost:10000
Content-Type: application/json
{
"id": "27be771b-708f-43b8-8366-968966d07ec0",
"jsonrpc": "2.0",
"method": "message/send",
"params": {
"message": {
"kind": "message",
"messageId": "296eafc9233142bd98279e4055165f12",
"parts": [
{
"kind": "text",
"text": "How much is the exchange rate for 1 USD?"
}
],
"role": "user"
}
}
}
Response - Seq 2:
{
"id": "27be771b-708f-43b8-8366-968966d07ec0",
"jsonrpc": "2.0",
"result": {
"contextId": "a7cc0bef-17b5-41fc-9379-40b99f46a101",
"history": [
{
"contextId": "a7cc0bef-17b5-41fc-9379-40b99f46a101",
"kind": "message",
"messageId": "296eafc9233142bd98279e4055165f12",
"parts": [
{
"kind": "text",
"text": "How much is the exchange rate for 1 USD?"
}
],
"role": "user",
"taskId": "9d94c2d4-06e4-40e1-876b-22f5a2666e61"
}
],
"id": "9d94c2d4-06e4-40e1-876b-22f5a2666e61",
"kind": "task",
"status": {
"message": {
"contextId": "a7cc0bef-17b5-41fc-9379-40b99f46a101",
"kind": "message",
"messageId": "f0f5f3ff-335c-4e77-9b4a-01ff3908e7be",
"parts": [
{
"kind": "text",
"text": "Please specify which currency you would like to convert to."
}
],
"role": "agent",
"taskId": "9d94c2d4-06e4-40e1-876b-22f5a2666e61"
},
"state": "input-required"
}
}
}
Request - Seq 3:
POST http://localhost:10000
Content-Type: application/json
{
"id": "b88d818d-1192-42be-b4eb-3ee6b96a7e35",
"jsonrpc": "2.0",
"method": "message/send",
"params": {
"message": {
"contextId": "a7cc0bef-17b5-41fc-9379-40b99f46a101",
"kind": "message",
"messageId": "70371e1f231f4597b65ccdf534930ca9",
"parts": [
{
"kind": "text",
"text": "CAD"
}
],
"role": "user",
"taskId": "9d94c2d4-06e4-40e1-876b-22f5a2666e61"
}
}
}
Response - Seq 4:
{
"id": "b88d818d-1192-42be-b4eb-3ee6b96a7e35",
"jsonrpc": "2.0",
"result": {
"artifacts": [
{
"artifactId": "08373241-a745-4abe-a78b-9ca60882bcc6",
"name": "conversion_result",
"parts": [
{
"kind": "text",
"text": "The exchange rate for 1 USD to CAD is 1.3739."
}
]
}
],
"contextId": "a7cc0bef-17b5-41fc-9379-40b99f46a101",
"history": [
{
"contextId": "a7cc0bef-17b5-41fc-9379-40b99f46a101",
"kind": "message",
"messageId": "296eafc9233142bd98279e4055165f12",
"parts": [
{
"kind": "text",
"text": "How much is the exchange rate for 1 USD?"
}
],
"role": "user",
"taskId": "9d94c2d4-06e4-40e1-876b-22f5a2666e61"
},
{
"contextId": "a7cc0bef-17b5-41fc-9379-40b99f46a101",
"kind": "message",
"messageId": "f0f5f3ff-335c-4e77-9b4a-01ff3908e7be",
"parts": [
{
"kind": "text",
"text": "Please specify which currency you would like to convert to."
}
],
"role": "agent",
"taskId": "9d94c2d4-06e4-40e1-876b-22f5a2666e61"
},
{
"contextId": "a7cc0bef-17b5-41fc-9379-40b99f46a101",
"kind": "message",
"messageId": "70371e1f231f4597b65ccdf534930ca9",
"parts": [
{
"kind": "text",
"text": "CAD"
}
],
"role": "user",
"taskId": "9d94c2d4-06e4-40e1-876b-22f5a2666e61"
},
{
"contextId": "a7cc0bef-17b5-41fc-9379-40b99f46a101",
"kind": "message",
"messageId": "0eb4f200-a8cd-4d34-94f8-4d223eb1b2c0",
"parts": [
{
"kind": "text",
"text": "Looking up the exchange rates..."
}
],
"role": "agent",
"taskId": "9d94c2d4-06e4-40e1-876b-22f5a2666e61"
},
{
"contextId": "a7cc0bef-17b5-41fc-9379-40b99f46a101",
"kind": "message",
"messageId": "41c7c03a-a772-4dc8-a868-e8c7b7defc91",
"parts": [
{
"kind": "text",
"text": "Processing the exchange rates.."
}
],
"role": "agent",
"taskId": "9d94c2d4-06e4-40e1-876b-22f5a2666e61"
}
],
"id": "9d94c2d4-06e4-40e1-876b-22f5a2666e61",
"kind": "task",
"status": {
"state": "completed"
}
}
}
Streaming example
Request:
{
"id": "6d12d159-ec67-46e6-8d43-18480ce7f6ca",
"jsonrpc": "2.0",
"method": "message/stream",
"params": {
"message": {
"kind": "message",
"messageId": "2f9538ef0984471aa0d5179ce3c67a28",
"parts": [
{
"kind": "text",
"text": "how much is 10 USD in INR?"
}
],
"role": "user"
}
}
}
Response:
data: {"id":"6d12d159-ec67-46e6-8d43-18480ce7f6ca","jsonrpc":"2.0","result":{"contextId":"cd09e369-340a-4563-bca4-e5f2e0b9ff81","history":[{"contextId":"cd09e369-340a-4563-bca4-e5f2e0b9ff81","kind":"message","messageId":"2f9538ef0984471aa0d5179ce3c67a28","parts":[{"kind":"text","text":"how much is 10 USD in INR?"}],"role":"user","taskId":"423a2569-f272-4d75-a4d1-cdc6682188e5"}],"id":"423a2569-f272-4d75-a4d1-cdc6682188e5","kind":"task","status":{"state":"submitted"}}}
data: {"id":"6d12d159-ec67-46e6-8d43-18480ce7f6ca","jsonrpc":"2.0","result":{"contextId":"cd09e369-340a-4563-bca4-e5f2e0b9ff81","final":false,"kind":"status-update","status":{"message":{"contextId":"cd09e369-340a-4563-bca4-e5f2e0b9ff81","kind":"message","messageId":"1854a825-c64f-4f30-96f2-c8aa558b83f9","parts":[{"kind":"text","text":"Looking up the exchange rates..."}],"role":"agent","taskId":"423a2569-f272-4d75-a4d1-cdc6682188e5"},"state":"working"},"taskId":"423a2569-f272-4d75-a4d1-cdc6682188e5"}}
data: {"id":"6d12d159-ec67-46e6-8d43-18480ce7f6ca","jsonrpc":"2.0","result":{"contextId":"cd09e369-340a-4563-bca4-e5f2e0b9ff81","final":false,"kind":"status-update","status":{"message":{"contextId":"cd09e369-340a-4563-bca4-e5f2e0b9ff81","kind":"message","messageId":"e72127a6-4830-4320-bf23-235ac79b9a13","parts":[{"kind":"text","text":"Processing the exchange rates.."}],"role":"agent","taskId":"423a2569-f272-4d75-a4d1-cdc6682188e5"},"state":"working"},"taskId":"423a2569-f272-4d75-a4d1-cdc6682188e5"}}
data: {"id":"6d12d159-ec67-46e6-8d43-18480ce7f6ca","jsonrpc":"2.0","result":{"artifact":{"artifactId":"08373241-a745-4abe-a78b-9ca60882bcc6","name":"conversion_result","parts":[{"kind":"text","text":"10 USD is 856.2 INR."}]},"contextId":"cd09e369-340a-4563-bca4-e5f2e0b9ff81","kind":"artifact-update","taskId":"423a2569-f272-4d75-a4d1-cdc6682188e5"}}
data: {"id":"6d12d159-ec67-46e6-8d43-18480ce7f6ca","jsonrpc":"2.0","result":{"contextId":"cd09e369-340a-4563-bca4-e5f2e0b9ff81","final":true,"kind":"status-update","status":{"state":"completed"},"taskId":"423a2569-f272-4d75-a4d1-cdc6682188e5"}}
Learn More
Disclaimer
Important: The sample code provided is for demonstration purposes and illustrates the mechanics of the Agent-to-Agent (A2A) protocol. When building production applications, it is critical to treat any agent operating outside of your direct control as a potentially untrusted entity.
All data received from an external agent—including but not limited to its AgentCard, messages, artifacts, and task statuses—should be handled as untrusted input. For example, a malicious agent could provide an AgentCard containing crafted data in its fields (e.g., description, name, skills.description). If this data is used without sanitization to construct prompts for a Large Language Model (LLM), it could expose your application to prompt injection attacks. Failure to properly validate and sanitize this data before use can introduce security vulnerabilities into your application.
Developers are responsible for implementing appropriate security measures, such as input validation and secure handling of credentials to protect their systems and users.