Skip to content

Commit cfb9335

Browse files
authored
🚀 Release v0.3.0: LangChain v1 Migration & Enhanced Deep Agent
2 parents bbfa20f + efe6b36 commit cfb9335

File tree

25 files changed

+1200
-908
lines changed

25 files changed

+1200
-908
lines changed

README.md

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
**LangGraph-UP Monorepo** showcases how to build production-ready LangGraph agents using the latest **LangChain & LangGraph** V1 ecosystem, organized in a clean monorepo structure with shared libraries and multiple agent applications.
44

5-
[![Version](https://img.shields.io/badge/version-v0.2.0-blue.svg)](https://github.com/webup/langgraph-up-monorepo/releases/tag/v0.2.0)
6-
[![LangChain](https://img.shields.io/badge/LangChain-v1alpha-blue.svg)](https://github.com/langchain-ai/langchain)
7-
[![LangGraph](https://img.shields.io/badge/LangGraph-v1alpha-blue.svg)](https://github.com/langchain-ai/langgraph)
5+
[![Version](https://img.shields.io/badge/version-v0.3.0-blue.svg)](https://github.com/webup/langgraph-up-monorepo/releases/tag/v0.3.0)
6+
[![LangChain](https://img.shields.io/badge/LangChain-v1.0-blue.svg)](https://github.com/langchain-ai/langchain)
7+
[![LangGraph](https://img.shields.io/badge/LangGraph-v1.0-blue.svg)](https://github.com/langchain-ai/langgraph)
88
[![License](https://img.shields.io/badge/license-MIT-green.svg)](https://opensource.org/licenses/MIT)
99
[![PyPI](https://img.shields.io/badge/PyPI-langgraph--up--devkits-blue.svg)](https://test.pypi.org/project/langgraph-up-devkits/)
1010
[![Twitter](https://img.shields.io/twitter/follow/zhanghaili0610?style=social)](https://twitter.com/zhanghaili0610)
@@ -13,7 +13,8 @@
1313

1414
- 🌐 **Universal Model Loading** - OpenRouter, Qwen, QwQ, SiliconFlow with automatic registration
1515
- 🤖 **Multi-Agent Orchestration** - Supervisor & deep research patterns with specialized sub-agents
16-
- 🛠 **Custom Middleware** - Model switching, file masking, summarization, and state management
16+
- 🛠 **LangChain v1.0 Middleware** - Model switching, file masking, summarization with v1.0 pattern
17+
- 🔬 **Deep Research Agent** - Advanced research workflow with deepagents integration
1718
- 🧪 **Developer Experience** - Hot reload, comprehensive testing, strict linting, PyPI publishing
1819
- 🚀 **Deployment Ready** - LangGraph Cloud configurations included
1920
- 🌍 **Global Ready** - Region-based provider configuration (PRC/International)
@@ -154,17 +155,17 @@ math_to_research = create_handoff_tool("research_expert")
154155
research_to_math = create_handoff_tool("math_expert")
155156
```
156157

157-
#### 🔧 Custom Middleware (LangChain v1)
158+
#### 🔧 Custom Middleware (LangChain v1.0)
158159

159-
Built-in middleware for dynamic model switching, state management, and behavior modification:
160+
Built-in middleware for dynamic model switching, state management, and behavior modification using the **LangChain v1.0 middleware pattern**:
160161

161162
```python
162163
from langchain.agents import create_agent
163-
from langgraph_up_devkits import (
164+
from langgraph_up_devkits.middleware import (
164165
ModelProviderMiddleware,
165166
FileSystemMaskMiddleware,
166-
load_chat_model
167167
)
168+
from langgraph_up_devkits import load_chat_model
168169

169170
# Model provider middleware for automatic switching
170171
model_middleware = ModelProviderMiddleware()
@@ -183,11 +184,16 @@ context = {"model": "siliconflow:Qwen/Qwen3-8B"}
183184
result = await agent.ainvoke(messages, context=context)
184185
```
185186

186-
**Available Middleware:**
187+
**Available Middleware (v1.0 Compatible):**
187188
- `ModelProviderMiddleware` - Dynamic model switching based on context
188189
- `FileSystemMaskMiddleware` - Masks virtual file systems from LLM to save tokens
189190
- `SummarizationMiddleware` - Automatic message summarization for long conversations
190191

192+
**Key Changes in v1.0:**
193+
- Migrated to LangChain v1.0 middleware pattern with `before_model()` and `after_model()` hooks
194+
- Compatible with `langchain.agents.create_agent` middleware system
195+
- Improved state management and model switching reliability
196+
191197
For detailed documentation on additional features like middleware, tools, and utilities, see:
192198

193199
- **Framework Documentation**: [`libs/langgraph-up-devkits/README.md`](libs/langgraph-up-devkits/README.md)

apps/sample-deep-agent/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ readme = "README.md"
99
license = { text = "MIT" }
1010
requires-python = ">=3.11,<4.0"
1111
dependencies = [
12-
"deepagents>=0.0.11",
12+
"deepagents>=0.1.1",
1313
"langgraph-up-devkits",
1414
]
1515

apps/sample-deep-agent/src/sample_deep_agent/context.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class DeepAgentContext(BaseModel):
1010
"""Context configuration for deep agent runtime settings."""
1111

1212
# Model configuration
13-
model_name: str = Field(default="siliconflow:zai-org/GLM-4.5", description="Default model name")
13+
model_name: str = Field(default="siliconflow:deepseek-ai/DeepSeek-V3.2-Exp", description="Default model name")
1414

1515
# Graph configuration
1616
recursion_limit: int = Field(default=1000, description="Recursion limit for agent execution")

apps/sample-deep-agent/src/sample_deep_agent/graph.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from typing import Any
44

5-
from deepagents import async_create_deep_agent # type: ignore[import-untyped]
5+
from deepagents import create_deep_agent # type: ignore[import-untyped]
66
from langchain_core.runnables import RunnableConfig
77
from langgraph_up_devkits import load_chat_model
88
from langgraph_up_devkits.tools import deep_web_search, think_tool
@@ -35,12 +35,12 @@ def make_graph(config: RunnableConfig | None = None) -> Any:
3535
# Load model based on context configuration
3636
model = load_chat_model(context.model_name)
3737

38-
# Create deep agent with research capabilities (remove research_sub_agent from subagents list)
39-
agent = async_create_deep_agent(
40-
tools=[deep_web_search, think_tool],
41-
instructions=get_research_instructions(),
42-
subagents=RESEARCH_AGENTS, # Research agent in subagents list
38+
# Create deep agent with research capabilities
39+
agent = create_deep_agent(
4340
model=model,
41+
tools=[deep_web_search, think_tool],
42+
system_prompt=get_research_instructions(),
43+
subagents=RESEARCH_AGENTS,
4444
context_schema=DeepAgentContext,
4545
).with_config({"recursion_limit": context.recursion_limit})
4646

apps/sample-deep-agent/src/sample_deep_agent/subagents.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"into the necessary components, and then call multiple research agents in parallel, "
1818
"one for each sub question."
1919
),
20-
"prompt": SUB_RESEARCH_PROMPT,
20+
"system_prompt": SUB_RESEARCH_PROMPT,
2121
"middleware": [filesystem_mask],
2222
}
2323

@@ -27,7 +27,7 @@
2727
"Used to critique the final report. Give this agent some information about "
2828
"how you want it to critique the report."
2929
),
30-
"prompt": SUB_CRITIQUE_PROMPT,
30+
"system_prompt": SUB_CRITIQUE_PROMPT,
3131
"middleware": [filesystem_mask],
3232
}
3333

apps/sample-deep-agent/tests/conftest.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@
55
import pytest
66

77

8-
@pytest.fixture(autouse=True)
8+
@pytest.fixture
99
def mock_openai_key(monkeypatch):
10-
"""Mock API keys to prevent API key errors in unit tests."""
10+
"""Mock API keys to prevent API key errors in unit tests.
11+
12+
Note: This fixture is NOT autouse - it must be explicitly requested by unit tests.
13+
Integration tests need real API keys from the environment.
14+
"""
1115
monkeypatch.setenv("OPENAI_API_KEY", "sk-test-fake-key-for-unit-tests")
1216
monkeypatch.setenv("OPENROUTER_API_KEY", "sk-test-fake-key-for-unit-tests")
1317
monkeypatch.setenv("SILICONFLOW_API_KEY", "sk-test-fake-key-for-unit-tests")

apps/sample-deep-agent/tests/unit/test_graph.py

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
class TestGraphCreation:
1111
"""Test graph creation and configuration."""
1212

13-
@patch('sample_deep_agent.graph.async_create_deep_agent')
13+
@patch('sample_deep_agent.graph.create_deep_agent')
1414
@patch('sample_deep_agent.graph.load_chat_model')
1515
def test_make_graph_with_default_config(self, mock_load_model, mock_create_deep_agent):
1616
"""Test graph creation with default configuration."""
@@ -25,7 +25,7 @@ def test_make_graph_with_default_config(self, mock_load_model, mock_create_deep_
2525

2626
result = make_graph()
2727

28-
mock_load_model.assert_called_once_with("siliconflow:zai-org/GLM-4.5")
28+
mock_load_model.assert_called_once_with("siliconflow:deepseek-ai/DeepSeek-V3.2-Exp")
2929
mock_create_deep_agent.assert_called_once()
3030

3131
call_args = mock_create_deep_agent.call_args
@@ -40,7 +40,7 @@ def test_make_graph_with_default_config(self, mock_load_model, mock_create_deep_
4040

4141
assert result == mock_agent_with_config
4242

43-
@patch('sample_deep_agent.graph.async_create_deep_agent')
43+
@patch('sample_deep_agent.graph.create_deep_agent')
4444
@patch('sample_deep_agent.graph.load_chat_model')
4545
def test_make_graph_with_custom_config(self, mock_load_model, mock_create_deep_agent):
4646
"""Test graph creation with custom configuration."""
@@ -67,14 +67,14 @@ def test_make_graph_with_custom_config(self, mock_load_model, mock_create_deep_a
6767

6868
call_args = mock_create_deep_agent.call_args
6969

70-
# Verify instructions are present (runtime context will be used during actual execution)
71-
instructions = call_args[1]['instructions']
72-
assert "expert research coordinator" in instructions.lower()
73-
assert "TODOs" in instructions
70+
# Verify system_prompt is present (runtime context will be used during actual execution)
71+
system_prompt = call_args[1]['system_prompt']
72+
assert "expert research coordinator" in system_prompt.lower()
73+
assert "TODOs" in system_prompt
7474

7575
assert result == mock_agent_with_config
7676

77-
@patch('sample_deep_agent.graph.async_create_deep_agent')
77+
@patch('sample_deep_agent.graph.create_deep_agent')
7878
@patch('sample_deep_agent.graph.load_chat_model')
7979
def test_make_graph_with_none_config(self, mock_load_model, mock_create_deep_agent):
8080
"""Test graph creation with None config."""
@@ -90,12 +90,12 @@ def test_make_graph_with_none_config(self, mock_load_model, mock_create_deep_age
9090
result = make_graph(None)
9191

9292
# Should use default configuration
93-
mock_load_model.assert_called_once_with("siliconflow:zai-org/GLM-4.5")
93+
mock_load_model.assert_called_once_with("siliconflow:deepseek-ai/DeepSeek-V3.2-Exp")
9494
mock_create_deep_agent.assert_called_once()
9595

9696
assert result == mock_agent_with_config
9797

98-
@patch('sample_deep_agent.graph.async_create_deep_agent')
98+
@patch('sample_deep_agent.graph.create_deep_agent')
9999
@patch('sample_deep_agent.graph.load_chat_model')
100100
def test_tools_are_included(self, mock_load_model, mock_create_deep_agent):
101101
"""Test that required tools are included."""
@@ -156,36 +156,36 @@ class TestSubAgentConfiguration:
156156
def test_subagent_configuration_valid(self):
157157
"""Test that sub-agent configuration is valid for deepagents."""
158158
# Verify all required fields are present
159-
required_fields = ["name", "description", "prompt"]
159+
required_fields = ["name", "description", "system_prompt"]
160160
for field in required_fields:
161161
assert field in research_sub_agent
162162

163163
# Research agent no longer has explicit tools - they're passed from main agent
164-
# Check that the prompt contains TODO constraints instead
164+
# Check that the system_prompt contains TODO constraints instead
165165

166166
# Verify name and description
167167
assert research_sub_agent["name"] == "research-agent"
168168
assert "research" in research_sub_agent["description"].lower()
169169

170-
# Verify prompt contains expected elements
171-
prompt = research_sub_agent["prompt"]
172-
assert "researcher" in prompt.lower()
173-
assert "TODO CONSTRAINTS" in prompt
174-
assert "GLOBAL TODO LIMIT" in prompt
175-
assert str(3) in prompt # MAX_TODOS value
170+
# Verify system_prompt contains expected elements
171+
system_prompt = research_sub_agent["system_prompt"]
172+
assert "researcher" in system_prompt.lower()
173+
assert "TODO CONSTRAINTS" in system_prompt
174+
assert "GLOBAL TODO LIMIT" in system_prompt
175+
assert str(3) in system_prompt # MAX_TODOS value
176176

177177
def test_critique_agent_configuration(self):
178178
"""Test critique agent configuration."""
179179
assert critique_sub_agent["name"] == "critique-agent"
180180
assert "critique" in critique_sub_agent["description"].lower()
181-
assert "prompt" in critique_sub_agent
181+
assert "system_prompt" in critique_sub_agent
182182

183183
# Critique agent no longer has explicit tools - they're passed from main agent
184-
# Check that the prompt contains TODO constraints
185-
prompt = critique_sub_agent["prompt"]
186-
assert "TODO CONSTRAINTS" in prompt
187-
assert "GLOBAL TODO LIMIT" in prompt
188-
assert str(3) in prompt # MAX_TODOS value
184+
# Check that the system_prompt contains TODO constraints
185+
system_prompt = critique_sub_agent["system_prompt"]
186+
assert "TODO CONSTRAINTS" in system_prompt
187+
assert "GLOBAL TODO LIMIT" in system_prompt
188+
assert str(3) in system_prompt # MAX_TODOS value
189189

190190

191191
class TestAppExport:
@@ -228,9 +228,9 @@ def test_tool_integration_in_graph(self):
228228
assert hasattr(deep_web_search, "invoke") or callable(deep_web_search)
229229
assert hasattr(think_tool, "invoke") or callable(think_tool)
230230

231-
@patch('sample_deep_agent.graph.async_create_deep_agent')
231+
@patch('sample_deep_agent.graph.create_deep_agent')
232232
def test_tools_passed_to_deep_agent(self, mock_create_deep_agent):
233-
"""Test that tools are properly passed to async_create_deep_agent."""
233+
"""Test that tools are properly passed to create_deep_agent."""
234234
from sample_deep_agent.graph import make_graph
235235

236236
mock_agent = Mock()

0 commit comments

Comments
 (0)