langchain-dev-utils 1.3.7__tar.gz → 1.4.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/.vscode/settings.json +2 -1
  2. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/PKG-INFO +8 -6
  3. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/README.md +6 -5
  4. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/README_cn.md +6 -5
  5. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/pyproject.toml +63 -47
  6. langchain_dev_utils-1.4.1/src/langchain_dev_utils/__init__.py +1 -0
  7. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/src/langchain_dev_utils/_utils.py +23 -3
  8. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/src/langchain_dev_utils/agents/middleware/__init__.py +5 -3
  9. langchain_dev_utils-1.4.1/src/langchain_dev_utils/agents/middleware/format_prompt.py +156 -0
  10. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/src/langchain_dev_utils/agents/middleware/tool_call_repair.py +3 -0
  11. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/src/langchain_dev_utils/agents/wrap.py +20 -7
  12. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/src/langchain_dev_utils/chat_models/adapters/openai_compatible.py +10 -5
  13. langchain_dev_utils-1.4.1/src/langchain_dev_utils/graph/__init__.py +7 -0
  14. langchain_dev_utils-1.4.1/src/langchain_dev_utils/graph/parallel.py +119 -0
  15. langchain_dev_utils-1.4.1/src/langchain_dev_utils/graph/sequential.py +78 -0
  16. langchain_dev_utils-1.4.1/src/langchain_dev_utils/graph/types.py +3 -0
  17. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/src/langchain_dev_utils/message_convert/format.py +34 -1
  18. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/src/langchain_dev_utils/pipeline/parallel.py +6 -0
  19. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/src/langchain_dev_utils/pipeline/sequential.py +6 -0
  20. langchain_dev_utils-1.4.1/tests/test_graph.py +64 -0
  21. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/tests/test_messages.py +16 -10
  22. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/tests/test_wrap_agent.py +37 -4
  23. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/uv.lock +4125 -2883
  24. langchain_dev_utils-1.3.7/src/langchain_dev_utils/__init__.py +0 -1
  25. langchain_dev_utils-1.3.7/src/langchain_dev_utils/agents/file_system.py +0 -252
  26. langchain_dev_utils-1.3.7/src/langchain_dev_utils/agents/middleware/format_prompt.py +0 -66
  27. langchain_dev_utils-1.3.7/src/langchain_dev_utils/agents/plan.py +0 -188
  28. langchain_dev_utils-1.3.7/tests/test_pipline.py +0 -76
  29. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/.gitignore +0 -0
  30. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/.python-version +0 -0
  31. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/LICENSE +0 -0
  32. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/src/langchain_dev_utils/agents/__init__.py +0 -0
  33. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/src/langchain_dev_utils/agents/factory.py +0 -0
  34. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/src/langchain_dev_utils/agents/middleware/handoffs.py +0 -0
  35. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/src/langchain_dev_utils/agents/middleware/model_fallback.py +0 -0
  36. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/src/langchain_dev_utils/agents/middleware/model_router.py +0 -0
  37. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/src/langchain_dev_utils/agents/middleware/plan.py +0 -0
  38. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/src/langchain_dev_utils/agents/middleware/summarization.py +0 -0
  39. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/src/langchain_dev_utils/agents/middleware/tool_emulator.py +0 -0
  40. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/src/langchain_dev_utils/agents/middleware/tool_selection.py +0 -0
  41. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/src/langchain_dev_utils/chat_models/__init__.py +0 -0
  42. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/src/langchain_dev_utils/chat_models/adapters/__init__.py +0 -0
  43. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/src/langchain_dev_utils/chat_models/adapters/create_utils.py +0 -0
  44. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/src/langchain_dev_utils/chat_models/adapters/register_profiles.py +0 -0
  45. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/src/langchain_dev_utils/chat_models/base.py +0 -0
  46. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/src/langchain_dev_utils/chat_models/types.py +0 -0
  47. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/src/langchain_dev_utils/embeddings/__init__.py +0 -0
  48. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/src/langchain_dev_utils/embeddings/adapters/__init__.py +0 -0
  49. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/src/langchain_dev_utils/embeddings/adapters/create_utils.py +0 -0
  50. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/src/langchain_dev_utils/embeddings/adapters/openai_compatible.py +0 -0
  51. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/src/langchain_dev_utils/embeddings/base.py +0 -0
  52. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/src/langchain_dev_utils/message_convert/__init__.py +0 -0
  53. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/src/langchain_dev_utils/message_convert/content.py +0 -0
  54. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/src/langchain_dev_utils/pipeline/__init__.py +0 -0
  55. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/src/langchain_dev_utils/pipeline/types.py +0 -0
  56. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/src/langchain_dev_utils/py.typed +0 -0
  57. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/src/langchain_dev_utils/tool_calling/__init__.py +0 -0
  58. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/src/langchain_dev_utils/tool_calling/human_in_the_loop.py +0 -0
  59. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/src/langchain_dev_utils/tool_calling/utils.py +0 -0
  60. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/tests/__init__.py +0 -0
  61. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/tests/test_agent.py +0 -0
  62. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/tests/test_chat_models.py +0 -0
  63. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/tests/test_embedding.py +0 -0
  64. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/tests/test_handoffs_middleware.py +0 -0
  65. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/tests/test_human_in_the_loop.py +0 -0
  66. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/tests/test_load_embbeding.py +0 -0
  67. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/tests/test_load_model.py +0 -0
  68. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/tests/test_model_tool_emulator.py +0 -0
  69. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/tests/test_plan_middleware.py +0 -0
  70. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/tests/test_router_model.py +0 -0
  71. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/tests/test_tool_call_repair.py +0 -0
  72. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/tests/test_tool_calling.py +0 -0
  73. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/tests/utils/__init__.py +0 -0
  74. {langchain_dev_utils-1.3.7 → langchain_dev_utils-1.4.1}/tests/utils/register.py +0 -0
@@ -3,5 +3,6 @@
3
3
  "python.testing.unittestEnabled": false,
4
4
  "python.testing.pytestEnabled": true,
5
5
  "search.useIgnoreFiles": true,
6
- "python.analysis.typeCheckingMode": "off"
6
+ "python.analysis.typeCheckingMode": "off",
7
+ "python.languageServer": "None"
7
8
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langchain-dev-utils
3
- Version: 1.3.7
3
+ Version: 1.4.1
4
4
  Summary: A practical utility library for LangChain and LangGraph development
5
5
  Project-URL: Source Code, https://github.com/TBice123123/langchain-dev-utils
6
6
  Project-URL: repository, https://github.com/TBice123123/langchain-dev-utils
@@ -12,6 +12,7 @@ Requires-Dist: langchain-core>=1.2.5
12
12
  Requires-Dist: langchain>=1.2.0
13
13
  Requires-Dist: langgraph>=1.0.0
14
14
  Provides-Extra: standard
15
+ Requires-Dist: jinja2>=3.1.6; extra == 'standard'
15
16
  Requires-Dist: json-repair>=0.53.1; extra == 'standard'
16
17
  Requires-Dist: langchain-openai; extra == 'standard'
17
18
  Description-Content-Type: text/markdown
@@ -27,10 +28,11 @@ Description-Content-Type: text/markdown
27
28
  <a href="https://tbice123123.github.io/langchain-dev-utils/zh/">中文</a>
28
29
  </p>
29
30
 
30
- [![PyPI](https://img.shields.io/pypi/v/langchain-dev-utils.svg?color=%2334D058&label=pypi%20package)](https://pypi.org/project/langchain-dev-utils/)
31
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
32
- [![Python](https://img.shields.io/badge/python-3.11|3.12|3.13|3.14-%2334D058)](https://www.python.org/downloads)
33
- [![Downloads](https://static.pepy.tech/badge/langchain-dev-utils/month)](https://pepy.tech/project/langchain-dev-utils)
31
+ [![GitHub Repo](https://img.shields.io/badge/GitHub-Repo-black.svg?logo=github)](https://github.com/TBice123123/langchain-dev-utils)
32
+ [![PyPI](https://img.shields.io/pypi/v/langchain-dev-utils.svg?color=%2334D058&label=pypi%20package&logo=python)](https://pypi.org/project/langchain-dev-utils/)
33
+ [![Python Version](https://img.shields.io/badge/python-3.11%2B-blue.svg?logo=python&label=Python)](https://python.org)
34
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?label=License)](https://opensource.org/licenses/MIT)
35
+ [![Last Commit](https://img.shields.io/github/last-commit/TBice123123/langchain-dev-utils)](https://github.com/TBice123123/langchain-dev-utils)
34
36
  [![Documentation](https://img.shields.io/badge/docs-latest-blue)](https://tbice123123.github.io/langchain-dev-utils)
35
37
 
36
38
  > This is the English version. For the Chinese version, please visit [中文版本](https://github.com/TBice123123/langchain-dev-utils/blob/master/README_cn.md)
@@ -51,7 +53,7 @@ Tired of writing repetitive code in LangChain development? `langchain-dev-utils`
51
53
  - **💬 Flexible message handling** - Support for chain-of-thought concatenation, streaming processing, and message formatting
52
54
  - **🛠️ Powerful tool calling** - Built-in tool call detection, parameter parsing, and human review functionality
53
55
  - **🤖 Efficient Agent development** - Simplify agent creation process, expand more common middleware
54
- - **📊 Flexible state graph composition** - Support for serial and parallel composition of multiple StateGraphs
56
+ - **📊 Convenient State Graph Construction** - Provides pre-built functions to easily create sequential or parallel state graphs
55
57
 
56
58
  ## ⚡ Quick Start
57
59
 
@@ -9,10 +9,11 @@
9
9
  <a href="https://tbice123123.github.io/langchain-dev-utils/zh/">中文</a>
10
10
  </p>
11
11
 
12
- [![PyPI](https://img.shields.io/pypi/v/langchain-dev-utils.svg?color=%2334D058&label=pypi%20package)](https://pypi.org/project/langchain-dev-utils/)
13
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
14
- [![Python](https://img.shields.io/badge/python-3.11|3.12|3.13|3.14-%2334D058)](https://www.python.org/downloads)
15
- [![Downloads](https://static.pepy.tech/badge/langchain-dev-utils/month)](https://pepy.tech/project/langchain-dev-utils)
12
+ [![GitHub Repo](https://img.shields.io/badge/GitHub-Repo-black.svg?logo=github)](https://github.com/TBice123123/langchain-dev-utils)
13
+ [![PyPI](https://img.shields.io/pypi/v/langchain-dev-utils.svg?color=%2334D058&label=pypi%20package&logo=python)](https://pypi.org/project/langchain-dev-utils/)
14
+ [![Python Version](https://img.shields.io/badge/python-3.11%2B-blue.svg?logo=python&label=Python)](https://python.org)
15
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?label=License)](https://opensource.org/licenses/MIT)
16
+ [![Last Commit](https://img.shields.io/github/last-commit/TBice123123/langchain-dev-utils)](https://github.com/TBice123123/langchain-dev-utils)
16
17
  [![Documentation](https://img.shields.io/badge/docs-latest-blue)](https://tbice123123.github.io/langchain-dev-utils)
17
18
 
18
19
  > This is the English version. For the Chinese version, please visit [中文版本](https://github.com/TBice123123/langchain-dev-utils/blob/master/README_cn.md)
@@ -33,7 +34,7 @@ Tired of writing repetitive code in LangChain development? `langchain-dev-utils`
33
34
  - **💬 Flexible message handling** - Support for chain-of-thought concatenation, streaming processing, and message formatting
34
35
  - **🛠️ Powerful tool calling** - Built-in tool call detection, parameter parsing, and human review functionality
35
36
  - **🤖 Efficient Agent development** - Simplify agent creation process, expand more common middleware
36
- - **📊 Flexible state graph composition** - Support for serial and parallel composition of multiple StateGraphs
37
+ - **📊 Convenient State Graph Construction** - Provides pre-built functions to easily create sequential or parallel state graphs
37
38
 
38
39
  ## ⚡ Quick Start
39
40
 
@@ -9,10 +9,11 @@
9
9
  <a href="https://tbice123123.github.io/langchain-dev-utils/zh/">中文</a>
10
10
  </p>
11
11
 
12
- [![PyPI](https://img.shields.io/pypi/v/langchain-dev-utils.svg?color=%2334D058&label=pypi%20package)](https://pypi.org/project/langchain-dev-utils/)
13
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
14
- [![Python](https://img.shields.io/badge/python-3.11|3.12|3.13|3.14-%2334D058)](https://www.python.org/downloads)
15
- [![Downloads](https://static.pepy.tech/badge/langchain-dev-utils/month)](https://pepy.tech/project/langchain-dev-utils)
12
+ [![GitHub Repo](https://img.shields.io/badge/GitHub-Repo-black.svg?logo=github)](https://github.com/TBice123123/langchain-dev-utils)
13
+ [![PyPI](https://img.shields.io/pypi/v/langchain-dev-utils.svg?color=%2334D058&label=pypi%20package&logo=python)](https://pypi.org/project/langchain-dev-utils/)
14
+ [![Python Version](https://img.shields.io/badge/python-3.11%2B-blue.svg?logo=python&label=Python)](https://python.org)
15
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?label=License)](https://opensource.org/licenses/MIT)
16
+ [![Last Commit](https://img.shields.io/github/last-commit/TBice123123/langchain-dev-utils)](https://github.com/TBice123123/langchain-dev-utils)
16
17
  [![Documentation](https://img.shields.io/badge/docs-latest-blue)](https://tbice123123.github.io/langchain-dev-utils/zh/)
17
18
 
18
19
  > 当前为中文版,英文版请访问[English Version](https://github.com/TBice123123/langchain-dev-utils/blob/master/README.md)
@@ -33,7 +34,7 @@
33
34
  - **💬 灵活的消息处理** - 支持思维链拼接、流式处理和消息格式化
34
35
  - **🛠️ 强大的工具调用** - 内置工具调用检测、参数解析和人工审核功能
35
36
  - **🤖 高效的 Agent 开发** - 简化智能体创建流程,扩充更多的常用中间件
36
- - **📊 灵活的状态图组合** - 支持串行和并行方式组合多个 StateGraph
37
+ - **📊 便捷的状态图构建** - 提供预构建函数方便构建顺序或者并行的状态图
37
38
 
38
39
  ## ⚡ 快速开始
39
40
 
@@ -1,47 +1,63 @@
1
- [project]
2
- name = "langchain-dev-utils"
3
- version = "1.3.7"
4
- description = "A practical utility library for LangChain and LangGraph development"
5
- readme = "README.md"
6
- authors = [{ name = "tiebingice", email = "tiebingice123@outlook.com" }]
7
- requires-python = ">=3.11"
8
- dependencies = ["langchain>=1.2.0", "langchain-core>=1.2.5", "langgraph>=1.0.0"]
9
-
10
- [project.urls]
11
- "Source Code" = "https://github.com/TBice123123/langchain-dev-utils"
12
- repository = "https://github.com/TBice123123/langchain-dev-utils"
13
- documentation = "https://tbice123123.github.io/langchain-dev-utils"
14
-
15
-
16
- [project.optional-dependencies]
17
- standard = ["json-repair>=0.53.1", "langchain-openai"]
18
-
19
- [build-system]
20
- requires = ["hatchling"]
21
- build-backend = "hatchling.build"
22
-
23
- [tool.hatch.build]
24
- exclude = ["/data", "/docs", "mkdocs.yml"]
25
-
26
- [tool.pytest.ini_options]
27
- asyncio_mode = "auto"
28
- testpaths = ["tests"]
29
- python_files = ["test_*.py"]
30
- python_functions = ["test_*"]
31
-
32
- [dependency-groups]
33
- dev = ["langchain-model-profiles>=0.0.5", "ruff>=0.14.5"]
34
- docs = ["mkdocs-material>=9.7.0", "mkdocs-static-i18n>=1.3.0"]
35
- tests = [
36
- "python-dotenv>=1.1.1",
37
- "langchain-tests>=1.0.0",
38
- "langchain-deepseek>=1.0.0",
39
- "langchain-qwq>=0.3.0",
40
- "langchain-ollama>=1.0.0",
41
- "langchain-community>=0.4.1",
42
- ]
43
-
44
-
45
- [tool.ruff.lint]
46
- select = ["E", "F", "I", "PGH003", "T201"]
47
- ignore = ["E501"]
1
+ [project]
2
+ name = "langchain-dev-utils"
3
+ version = "1.4.1"
4
+ description = "A practical utility library for LangChain and LangGraph development"
5
+ readme = "README.md"
6
+ authors = [{ name = "tiebingice", email = "tiebingice123@outlook.com" }]
7
+ requires-python = ">=3.11"
8
+ dependencies = [
9
+ "langchain>=1.2.0",
10
+ "langchain-core>=1.2.5",
11
+ "langgraph>=1.0.0",
12
+ ]
13
+
14
+ [project.urls]
15
+ "Source Code" = "https://github.com/TBice123123/langchain-dev-utils"
16
+ repository = "https://github.com/TBice123123/langchain-dev-utils"
17
+ documentation = "https://tbice123123.github.io/langchain-dev-utils"
18
+
19
+
20
+ [project.optional-dependencies]
21
+ standard = [
22
+ "jinja2>=3.1.6",
23
+ "json-repair>=0.53.1",
24
+ "langchain-openai",
25
+ ]
26
+
27
+ [build-system]
28
+ requires = ["hatchling"]
29
+ build-backend = "hatchling.build"
30
+
31
+ [tool.hatch.build]
32
+ exclude = ["/data", "/docs", "mkdocs.yml"]
33
+
34
+ [tool.pytest.ini_options]
35
+ asyncio_mode = "auto"
36
+ testpaths = ["tests"]
37
+ python_files = ["test_*.py"]
38
+ python_functions = ["test_*"]
39
+
40
+ [dependency-groups]
41
+ dev = [
42
+ "dashscope>=1.25.9",
43
+ "langchain-model-profiles>=0.0.5",
44
+ "ruff>=0.14.5",
45
+ ]
46
+ docs = [
47
+ "jupyter>=1.1.1",
48
+ "mkdocs-material>=9.7.0",
49
+ "mkdocs-static-i18n>=1.3.0",
50
+ ]
51
+ tests = [
52
+ "python-dotenv>=1.1.1",
53
+ "langchain-tests>=1.0.0",
54
+ "langchain-deepseek>=1.0.0",
55
+ "langchain-qwq>=0.3.0",
56
+ "langchain-ollama>=1.0.0",
57
+ "langchain-community>=0.4.1",
58
+ ]
59
+
60
+
61
+ [tool.ruff.lint]
62
+ select = ["E", "F", "I", "PGH003", "T201"]
63
+ ignore = ["E501"]
@@ -0,0 +1 @@
1
+ __version__ = "1.4.1"
@@ -1,17 +1,37 @@
1
1
  from importlib import util
2
- from typing import Literal, Optional
2
+ from typing import Literal, Optional, cast
3
3
 
4
+ from langgraph.graph import StateGraph
5
+ from langgraph.graph.state import StateNode
4
6
  from pydantic import BaseModel
5
7
 
6
8
 
9
+ def _transform_node_to_tuple(
10
+ node: StateNode | tuple[str, StateNode],
11
+ ) -> tuple[str, StateNode]:
12
+ if not isinstance(node, tuple):
13
+ if isinstance(node, StateGraph):
14
+ node = node.compile()
15
+ name = node.name
16
+ return name, node
17
+ name = cast(str, getattr(node, "name", getattr(node, "__name__", None)))
18
+ if name is None:
19
+ raise ValueError("Node name must be provided if action is not a function")
20
+ return name, node
21
+ else:
22
+ return node
23
+
24
+
7
25
  def _check_pkg_install(
8
- pkg: Literal["langchain_openai", "json_repair"],
26
+ pkg: Literal["langchain_openai", "json_repair", "jinja2"],
9
27
  ) -> None:
10
28
  if not util.find_spec(pkg):
11
29
  if pkg == "langchain_openai":
12
30
  msg = "Please install langchain_dev_utils[standard],when use 'openai-compatible'"
13
- else:
31
+ elif pkg == "json_repair":
14
32
  msg = "Please install langchain_dev_utils[standard] to use ToolCallRepairMiddleware."
33
+ else:
34
+ msg = "Please install langchain_dev_utils[standard] to use FormatPromptMiddleware."
15
35
  raise ImportError(msg)
16
36
 
17
37
 
@@ -1,10 +1,10 @@
1
- from .format_prompt import format_prompt
1
+ from .format_prompt import FormatPromptMiddleware, format_prompt
2
2
  from .handoffs import HandoffAgentMiddleware
3
3
  from .model_fallback import ModelFallbackMiddleware
4
4
  from .model_router import ModelRouterMiddleware
5
5
  from .plan import PlanMiddleware
6
6
  from .summarization import SummarizationMiddleware
7
- from .tool_call_repair import ToolCallRepairMiddleware
7
+ from .tool_call_repair import ToolCallRepairMiddleware, tool_call_repair
8
8
  from .tool_emulator import LLMToolEmulator
9
9
  from .tool_selection import LLMToolSelectorMiddleware
10
10
 
@@ -16,6 +16,8 @@ __all__ = [
16
16
  "LLMToolEmulator",
17
17
  "ModelRouterMiddleware",
18
18
  "ToolCallRepairMiddleware",
19
- "format_prompt",
19
+ "FormatPromptMiddleware",
20
20
  "HandoffAgentMiddleware",
21
+ "tool_call_repair",
22
+ "format_prompt",
21
23
  ]
@@ -0,0 +1,156 @@
1
+ from typing import Awaitable, Callable, Literal
2
+
3
+ from langchain.agents.middleware import ModelRequest
4
+ from langchain.agents.middleware.types import (
5
+ AgentMiddleware,
6
+ ModelCallResult,
7
+ ModelResponse,
8
+ )
9
+ from langchain_core.messages import SystemMessage
10
+ from langchain_core.prompts.string import get_template_variables
11
+
12
+ from langchain_dev_utils._utils import _check_pkg_install
13
+
14
+
15
+ class FormatPromptMiddleware(AgentMiddleware):
16
+ """Format the system prompt with variables from state and context.
17
+
18
+ This middleware function extracts template variables from the system prompt
19
+ and populates them with values from the agent's state and runtime context.
20
+ Variables are first resolved from the state, then from the context if not found.
21
+
22
+ Args:
23
+ template_format: The format of the template. Defaults to "f-string".
24
+
25
+ Example:
26
+
27
+ # Use format_prompt middleware instance rather than FormatPromptMiddleware class (Recommended)
28
+
29
+ ```python
30
+ from langchain_dev_utils.agents.middleware import format_prompt
31
+ from langchain.agents import create_agent
32
+ from langchain_core.messages import HumanMessage
33
+ from dataclasses import dataclass
34
+
35
+ @dataclass
36
+ class Context:
37
+ name: str
38
+ user: str
39
+
40
+ agent = create_agent(
41
+ model=model,
42
+ tools=tools,
43
+ system_prompt="You are a helpful assistant. Your name is {name}. Your user is {user}.",
44
+ middleware=[format_prompt],
45
+ context_schema=Context,
46
+ )
47
+ agent.invoke(
48
+ {
49
+ "messages": [HumanMessage(content="Hello")],
50
+ },
51
+ context=Context(name="assistant", user="Tom"),
52
+ )
53
+ ```
54
+
55
+ # Use FormatPromptMiddleware class(Use when template_format is jinja2)
56
+
57
+ ```python
58
+ from langchain_dev_utils.agents.middleware import FormatPromptMiddleware
59
+ from langchain.agents import create_agent
60
+ from langchain_core.messages import HumanMessage
61
+ from dataclasses import dataclass
62
+
63
+ @dataclass
64
+ class Context:
65
+ name: str
66
+ user: str
67
+
68
+ agent = create_agent(
69
+ model=model,
70
+ tools=tools,
71
+ system_prompt="You are a helpful assistant. Your name is {{ name }}. Your user is {{ user }}.",
72
+ middleware=[FormatPromptMiddleware(template_format="jinja2")],
73
+ context_schema=Context,
74
+ )
75
+ agent.invoke(
76
+ {
77
+ "messages": [HumanMessage(content="Hello")],
78
+ },
79
+ context=Context(name="assistant", user="Tom"),
80
+ )
81
+ ```
82
+ """
83
+
84
+ def __init__(
85
+ self,
86
+ *,
87
+ template_format: Literal["f-string", "jinja2"] = "f-string",
88
+ ) -> None:
89
+ super().__init__()
90
+
91
+ self.template_format = template_format
92
+
93
+ if template_format == "jinja2":
94
+ _check_pkg_install("jinja2")
95
+
96
+ def _format_prompt(self, request: ModelRequest) -> str:
97
+ """Add the plan system prompt to the system message."""
98
+ system_msg = request.system_message
99
+ if system_msg is None:
100
+ raise ValueError(
101
+ "system_message must be provided,while use format_prompt in middleware."
102
+ )
103
+
104
+ system_prompt = "\n".join(
105
+ [content.get("text", "") for content in system_msg.content_blocks]
106
+ )
107
+ variables = get_template_variables(system_prompt, self.template_format)
108
+
109
+ format_params = {}
110
+
111
+ state = request.state
112
+ for key in variables:
113
+ if var := state.get(key, None):
114
+ format_params[key] = var
115
+
116
+ other_var_keys = set(variables) - set(format_params.keys())
117
+
118
+ if other_var_keys:
119
+ context = request.runtime.context
120
+ if context is not None:
121
+ for key in other_var_keys:
122
+ if var := getattr(context, key, None):
123
+ format_params[key] = var
124
+
125
+ if self.template_format == "jinja2":
126
+ from jinja2 import Template
127
+
128
+ template = Template(system_prompt)
129
+ return template.render(**format_params)
130
+ else:
131
+ return system_prompt.format(**format_params)
132
+
133
+ def wrap_model_call(
134
+ self,
135
+ request: ModelRequest,
136
+ handler: Callable[[ModelRequest], ModelResponse],
137
+ ) -> ModelCallResult:
138
+ """Update the system prompt with variables from state and context."""
139
+ prompt = self._format_prompt(request)
140
+ request = request.override(system_message=SystemMessage(content=prompt))
141
+ return handler(request)
142
+
143
+ async def awrap_model_call(
144
+ self,
145
+ request: ModelRequest,
146
+ handler: Callable[[ModelRequest], Awaitable[ModelResponse]],
147
+ ) -> ModelCallResult:
148
+ """Update the system prompt with variables from state and context."""
149
+ prompt = self._format_prompt(request)
150
+ override_request = request.override(
151
+ system_message=SystemMessage(content=prompt)
152
+ )
153
+ return await handler(override_request)
154
+
155
+
156
+ format_prompt = FormatPromptMiddleware()
@@ -94,3 +94,6 @@ class ToolCallRepairMiddleware(AgentMiddleware):
94
94
  result=results,
95
95
  structured_response=response.structured_response,
96
96
  )
97
+
98
+
99
+ tool_call_repair = ToolCallRepairMiddleware()
@@ -1,12 +1,13 @@
1
- import asyncio
2
- from typing import Any, Awaitable, Callable, Optional
1
+ import inspect
2
+ from typing import Any, Awaitable, Callable, Optional, cast
3
3
 
4
4
  from langchain.tools import ToolRuntime
5
- from langchain_core.messages import HumanMessage
5
+ from langchain_core.messages import AIMessage, HumanMessage
6
6
  from langchain_core.tools import BaseTool, StructuredTool
7
7
  from langgraph.graph.state import CompiledStateGraph
8
8
 
9
9
  from langchain_dev_utils.message_convert import format_sequence
10
+ from langchain_dev_utils.tool_calling import parse_tool_calling
10
11
 
11
12
 
12
13
  def _process_input(request: str, runtime: ToolRuntime) -> str:
@@ -19,6 +20,18 @@ def _process_output(
19
20
  return response["messages"][-1].content
20
21
 
21
22
 
23
+ def get_subagent_name(runtime: ToolRuntime) -> str:
24
+ messages = runtime.state.get("messages", [])
25
+ last_ai_msg = cast(
26
+ AIMessage,
27
+ next((msg for msg in reversed(messages) if isinstance(msg, AIMessage)), None),
28
+ )
29
+
30
+ _, args = parse_tool_calling(last_ai_msg, first_tool_call_only=True)
31
+ args = cast(dict[str, Any], args)
32
+ return args["agent_name"]
33
+
34
+
22
35
  def wrap_agent_as_tool(
23
36
  agent: CompiledStateGraph,
24
37
  tool_name: Optional[str] = None,
@@ -115,7 +128,7 @@ def wrap_agent_as_tool(
115
128
  request: str,
116
129
  runtime: ToolRuntime,
117
130
  ):
118
- if asyncio.iscoroutinefunction(process_input_async):
131
+ if inspect.iscoroutinefunction(process_input_async):
119
132
  _processed_input = await process_input_async(request, runtime)
120
133
  else:
121
134
  _processed_input = (
@@ -135,7 +148,7 @@ def wrap_agent_as_tool(
135
148
 
136
149
  response = await agent.ainvoke(agent_input)
137
150
 
138
- if asyncio.iscoroutinefunction(process_output_async):
151
+ if inspect.iscoroutinefunction(process_output_async):
139
152
  response = await process_output_async(request, response, runtime)
140
153
  else:
141
154
  response = (
@@ -277,7 +290,7 @@ def wrap_all_agents_as_tool(
277
290
  if agent_name not in agents_map:
278
291
  raise ValueError(f"Agent {agent_name} not found")
279
292
 
280
- if asyncio.iscoroutinefunction(process_input_async):
293
+ if inspect.iscoroutinefunction(process_input_async):
281
294
  _processed_input = await process_input_async(description, runtime)
282
295
  else:
283
296
  _processed_input = (
@@ -297,7 +310,7 @@ def wrap_all_agents_as_tool(
297
310
 
298
311
  response = await agents_map[agent_name].ainvoke(agent_input)
299
312
 
300
- if asyncio.iscoroutinefunction(process_output_async):
313
+ if inspect.iscoroutinefunction(process_output_async):
301
314
  response = await process_output_async(description, response, runtime)
302
315
  else:
303
316
  response = (
@@ -218,14 +218,15 @@ class _BaseChatOpenAICompatible(BaseChatOpenAI):
218
218
  stop: list[str] | None = None,
219
219
  **kwargs: Any,
220
220
  ) -> dict:
221
+ if stop is not None:
222
+ kwargs["stop"] = stop
223
+
221
224
  payload = {**self._default_params, **kwargs}
222
225
 
223
226
  if self._use_responses_api(payload):
224
227
  return super()._get_request_payload(input_, stop=stop, **kwargs)
225
228
 
226
229
  messages = self._convert_input(input_).to_messages()
227
- if stop is not None:
228
- kwargs["stop"] = stop
229
230
 
230
231
  payload_messages = []
231
232
  last_human_index = -1
@@ -260,6 +261,8 @@ class _BaseChatOpenAICompatible(BaseChatOpenAI):
260
261
  payload_messages.append(_convert_message_to_dict(m))
261
262
 
262
263
  payload["messages"] = payload_messages
264
+ if "tools" in payload and len(payload["tools"]) == 0:
265
+ payload.pop("tools")
263
266
  return payload
264
267
 
265
268
  @model_validator(mode="after")
@@ -301,6 +304,8 @@ class _BaseChatOpenAICompatible(BaseChatOpenAI):
301
304
  ModelProfile,
302
305
  _get_profile_by_provider_and_model(self._provider, self.model_name),
303
306
  )
307
+ if "json_schema" in self.supported_response_format:
308
+ self.profile.update({"structured_output": True})
304
309
  return self
305
310
 
306
311
  def _create_chat_result(
@@ -346,9 +351,9 @@ class _BaseChatOpenAICompatible(BaseChatOpenAI):
346
351
  if isinstance(model_extra, dict) and (
347
352
  reasoning := model_extra.get("reasoning")
348
353
  ):
349
- rtn.generations[0].message.additional_kwargs["reasoning_content"] = (
350
- reasoning
351
- )
354
+ rtn.generations[0].message.additional_kwargs[
355
+ "reasoning_content"
356
+ ] = reasoning
352
357
 
353
358
  return rtn
354
359
 
@@ -0,0 +1,7 @@
1
+ from .parallel import create_parallel_graph
2
+ from .sequential import create_sequential_graph
3
+
4
+ __all__ = [
5
+ "create_parallel_graph",
6
+ "create_sequential_graph",
7
+ ]