langchain-dev-utils 1.2.11__tar.gz → 1.2.13__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 (59) hide show
  1. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/PKG-INFO +2 -2
  2. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/pyproject.toml +2 -5
  3. langchain_dev_utils-1.2.13/src/langchain_dev_utils/__init__.py +1 -0
  4. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/src/langchain_dev_utils/agents/middleware/__init__.py +2 -0
  5. langchain_dev_utils-1.2.13/src/langchain_dev_utils/agents/middleware/format_prompt.py +66 -0
  6. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/src/langchain_dev_utils/chat_models/adapters/openai_compatible.py +63 -1
  7. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/src/langchain_dev_utils/chat_models/base.py +0 -1
  8. langchain_dev_utils-1.2.13/tests/test_chat_models.py +185 -0
  9. langchain_dev_utils-1.2.11/src/langchain_dev_utils/__init__.py +0 -1
  10. langchain_dev_utils-1.2.11/tests/test_chat_models.py +0 -59
  11. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/.gitignore +0 -0
  12. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/.python-version +0 -0
  13. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/.vscode/settings.json +0 -0
  14. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/LICENSE +0 -0
  15. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/README.md +0 -0
  16. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/README_cn.md +0 -0
  17. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/src/langchain_dev_utils/_utils.py +0 -0
  18. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/src/langchain_dev_utils/agents/__init__.py +0 -0
  19. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/src/langchain_dev_utils/agents/factory.py +0 -0
  20. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/src/langchain_dev_utils/agents/file_system.py +0 -0
  21. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/src/langchain_dev_utils/agents/middleware/model_fallback.py +0 -0
  22. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/src/langchain_dev_utils/agents/middleware/model_router.py +0 -0
  23. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/src/langchain_dev_utils/agents/middleware/plan.py +0 -0
  24. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/src/langchain_dev_utils/agents/middleware/summarization.py +0 -0
  25. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/src/langchain_dev_utils/agents/middleware/tool_call_repair.py +0 -0
  26. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/src/langchain_dev_utils/agents/middleware/tool_emulator.py +0 -0
  27. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/src/langchain_dev_utils/agents/middleware/tool_selection.py +0 -0
  28. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/src/langchain_dev_utils/agents/plan.py +0 -0
  29. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/src/langchain_dev_utils/agents/wrap.py +0 -0
  30. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/src/langchain_dev_utils/chat_models/__init__.py +0 -0
  31. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/src/langchain_dev_utils/chat_models/adapters/__init__.py +0 -0
  32. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/src/langchain_dev_utils/chat_models/types.py +0 -0
  33. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/src/langchain_dev_utils/embeddings/__init__.py +0 -0
  34. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/src/langchain_dev_utils/embeddings/base.py +0 -0
  35. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/src/langchain_dev_utils/message_convert/__init__.py +0 -0
  36. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/src/langchain_dev_utils/message_convert/content.py +0 -0
  37. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/src/langchain_dev_utils/message_convert/format.py +0 -0
  38. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/src/langchain_dev_utils/pipeline/__init__.py +0 -0
  39. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/src/langchain_dev_utils/pipeline/parallel.py +0 -0
  40. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/src/langchain_dev_utils/pipeline/sequential.py +0 -0
  41. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/src/langchain_dev_utils/pipeline/types.py +0 -0
  42. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/src/langchain_dev_utils/py.typed +0 -0
  43. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/src/langchain_dev_utils/tool_calling/__init__.py +0 -0
  44. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/src/langchain_dev_utils/tool_calling/human_in_the_loop.py +0 -0
  45. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/src/langchain_dev_utils/tool_calling/utils.py +0 -0
  46. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/tests/__init__.py +0 -0
  47. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/tests/test_agent.py +0 -0
  48. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/tests/test_human_in_the_loop.py +0 -0
  49. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/tests/test_load_embbeding.py +0 -0
  50. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/tests/test_load_model.py +0 -0
  51. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/tests/test_messages.py +0 -0
  52. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/tests/test_model_tool_emulator.py +0 -0
  53. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/tests/test_pipline.py +0 -0
  54. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/tests/test_plan_middleware.py +0 -0
  55. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/tests/test_router_model.py +0 -0
  56. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/tests/test_tool_call_repair.py +0 -0
  57. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/tests/test_tool_calling.py +0 -0
  58. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/tests/test_wrap_agent.py +0 -0
  59. {langchain_dev_utils-1.2.11 → langchain_dev_utils-1.2.13}/uv.lock +0 -0
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langchain-dev-utils
3
- Version: 1.2.11
3
+ Version: 1.2.13
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
7
- Project-URL: documentation, https://tbice123123.github.io/langchain-dev-utils-docs/en/
7
+ Project-URL: documentation, https://tbice123123.github.io/langchain-dev-utils
8
8
  Author-email: tiebingice <tiebingice123@outlook.com>
9
9
  License-File: LICENSE
10
10
  Requires-Python: >=3.11
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "langchain-dev-utils"
3
- version = "1.2.11"
3
+ version = "1.2.13"
4
4
  description = "A practical utility library for LangChain and LangGraph development"
5
5
  readme = "README.md"
6
6
  authors = [{ name = "tiebingice", email = "tiebingice123@outlook.com" }]
@@ -10,7 +10,7 @@ dependencies = ["langchain>=1.1.0", "langgraph>=1.0.0"]
10
10
  [project.urls]
11
11
  "Source Code" = "https://github.com/TBice123123/langchain-dev-utils"
12
12
  repository = "https://github.com/TBice123123/langchain-dev-utils"
13
- documentation = "https://tbice123123.github.io/langchain-dev-utils-docs/en/"
13
+ documentation = "https://tbice123123.github.io/langchain-dev-utils"
14
14
 
15
15
 
16
16
  [project.optional-dependencies]
@@ -20,9 +20,6 @@ standard = ["json-repair>=0.53.1", "langchain-openai"]
20
20
  requires = ["hatchling"]
21
21
  build-backend = "hatchling.build"
22
22
 
23
- [tool.uv.build-backend]
24
- source-exclude = ["/data", "/docs", "mkdocs.yml"]
25
-
26
23
  [tool.hatch.build]
27
24
  exclude = ["/data", "/docs", "mkdocs.yml"]
28
25
 
@@ -0,0 +1 @@
1
+ __version__ = "1.2.13"
@@ -1,3 +1,4 @@
1
+ from .format_prompt import format_prompt
1
2
  from .model_fallback import ModelFallbackMiddleware
2
3
  from .model_router import ModelRouterMiddleware
3
4
  from .plan import (
@@ -22,4 +23,5 @@ __all__ = [
22
23
  "LLMToolEmulator",
23
24
  "ModelRouterMiddleware",
24
25
  "ToolCallRepairMiddleware",
26
+ "format_prompt",
25
27
  ]
@@ -0,0 +1,66 @@
1
+ from langchain.agents.middleware import ModelRequest, dynamic_prompt
2
+ from langchain_core.prompts.string import get_template_variables
3
+
4
+
5
+ @dynamic_prompt
6
+ def format_prompt(request: ModelRequest) -> str:
7
+ """Format the system prompt with variables from state and context.
8
+
9
+ This middleware function extracts template variables from the system prompt
10
+ and populates them with values from the agent's state and runtime context.
11
+ Variables are first resolved from the state, then from the context if not found.
12
+
13
+ Example:
14
+ >>> from langchain_dev_utils.agents.middleware.format_prompt import format_prompt
15
+ >>> from langchain.agents import create_agent
16
+ >>> from langchain_core.messages import HumanMessage
17
+ >>> from dataclasses import dataclass
18
+ >>>
19
+ >>> @dataclass
20
+ ... class Context:
21
+ ... name: str
22
+ ... user: str
23
+ >>>
24
+ >>> agent=create_agent(
25
+ ... model=model,
26
+ ... tools=tools,
27
+ ... system_prompt="You are a helpful assistant. Your name is {name}. Your user is {user}.",
28
+ ... middleware=[format_prompt],
29
+ ... context_schema=Context,
30
+ ... )
31
+ >>> agent.invoke(
32
+ ... {
33
+ ... "messages": [HumanMessage(content="Hello")],
34
+ ... },
35
+ ... context=Context(name="assistant", user="tbice"),
36
+ ... )
37
+
38
+ """
39
+ system_msg = request.system_message
40
+ if system_msg is None:
41
+ raise ValueError(
42
+ "system_message must be provided,while use format_prompt in middleware."
43
+ )
44
+
45
+ system_prompt = "\n".join(
46
+ [content.get("text", "") for content in system_msg.content_blocks]
47
+ )
48
+ variables = get_template_variables(system_prompt, "f-string")
49
+
50
+ format_params = {}
51
+
52
+ state = request.state
53
+ for key in variables:
54
+ if var := state.get(key, None):
55
+ format_params[key] = var
56
+
57
+ other_var_keys = set(variables) - set(format_params.keys())
58
+
59
+ if other_var_keys:
60
+ context = request.runtime.context
61
+ if context is not None:
62
+ for key in other_var_keys:
63
+ if var := getattr(context, key, None):
64
+ format_params[key] = var
65
+
66
+ return system_prompt.format(**format_params)
@@ -20,7 +20,13 @@ from langchain_core.callbacks import (
20
20
  CallbackManagerForLLMRun,
21
21
  )
22
22
  from langchain_core.language_models import LangSmithParams, LanguageModelInput
23
- from langchain_core.messages import AIMessage, AIMessageChunk, BaseMessage, HumanMessage
23
+ from langchain_core.messages import (
24
+ AIMessage,
25
+ AIMessageChunk,
26
+ BaseMessage,
27
+ HumanMessage,
28
+ ToolMessage,
29
+ )
24
30
  from langchain_core.outputs import ChatGenerationChunk, ChatResult
25
31
  from langchain_core.runnables import Runnable
26
32
  from langchain_core.tools import BaseTool
@@ -63,6 +69,58 @@ def _get_last_human_message_index(messages: list[BaseMessage]) -> int:
63
69
  )
64
70
 
65
71
 
72
+ def _transform_video_block(block: dict[str, Any]) -> dict:
73
+ """Transform video block to video_url block."""
74
+ if "url" in block:
75
+ return {
76
+ "type": "video_url",
77
+ "video_url": {
78
+ "url": block["url"],
79
+ },
80
+ }
81
+ if "base64" in block or block.get("source_type") == "base64":
82
+ if "mime_type" not in block:
83
+ error_message = "mime_type key is required for base64 data."
84
+ raise ValueError(error_message)
85
+ mime_type = block["mime_type"]
86
+ base64_data = block["data"] if "data" in block else block["base64"]
87
+ return {
88
+ "type": "video_url",
89
+ "video_url": {
90
+ "url": f"data:{mime_type};base64,{base64_data}",
91
+ },
92
+ }
93
+ error_message = "Unsupported source type. Only 'url' and 'base64' are supported."
94
+ raise ValueError(error_message)
95
+
96
+
97
+ def _process_video_input(message: BaseMessage):
98
+ """
99
+ Process BaseMessage with video input.
100
+
101
+ Args:
102
+ message (BaseMessage): The HumanMessage instance to process.
103
+
104
+ Returns:
105
+ None: The method modifies the message in-place.
106
+ """
107
+ if not message.content:
108
+ return message
109
+ content = message.content
110
+
111
+ if not isinstance(content, list):
112
+ return message
113
+
114
+ formatted_content = []
115
+ for block in content:
116
+ if isinstance(block, dict) and block.get("type") == "video":
117
+ formatted_content.append(_transform_video_block(block))
118
+ else:
119
+ formatted_content.append(block)
120
+ message = message.model_copy(update={"content": formatted_content})
121
+ return message
122
+
123
+
66
124
  class _BaseChatOpenAICompatible(BaseChatOpenAI):
67
125
  """
68
126
  Base template class for OpenAI-compatible chat model implementations.
@@ -183,6 +241,10 @@ class _BaseChatOpenAICompatible(BaseChatOpenAI):
183
241
  )
184
242
  payload_messages.append(msg_dict)
185
243
  else:
244
+ if (
245
+ isinstance(m, HumanMessage) or isinstance(m, ToolMessage)
246
+ ) and isinstance(m.content, list):
247
+ m = _process_video_input(m)
186
248
  payload_messages.append(_convert_message_to_dict(m))
187
249
 
188
250
  payload["messages"] = payload_messages
@@ -11,7 +11,6 @@ from langchain_dev_utils._utils import (
11
11
 
12
12
  from .types import ChatModelProvider, ChatModelType, CompatibilityOptions
13
13
 
14
-
15
14
  _MODEL_PROVIDERS_DICT = {}
16
15
 
17
16
 
@@ -0,0 +1,185 @@
1
+ import base64
2
+ from typing import Any, cast
3
+
4
+ import httpx
5
+ import pytest
6
+ from langchain_core.language_models import BaseChatModel
7
+ from langchain_core.messages import AIMessage, HumanMessage, ToolMessage
8
+ from langchain_tests.integration_tests.chat_models import ChatModelIntegrationTests
9
+
10
+ from langchain_dev_utils.chat_models.base import load_chat_model
11
+
12
+
13
+ class TestStandard(ChatModelIntegrationTests):
14
+ @pytest.fixture
15
+ def model(self, request: Any) -> BaseChatModel:
16
+ """Model fixture."""
17
+ extra_init_params = getattr(request, "param", None) or {}
18
+ if extra_init_params.get("output_version") == "v1":
19
+ pytest.skip("Output version v1 is not supported")
20
+ return self.chat_model_class(
21
+ **{
22
+ **self.standard_chat_model_params,
23
+ **self.chat_model_params,
24
+ **extra_init_params,
25
+ },
26
+ )
27
+
28
+ @property
29
+ def chat_model_class(self) -> type[BaseChatModel]:
30
+ return cast("type[BaseChatModel]", load_chat_model)
31
+
32
+ @property
33
+ def chat_model_params(self) -> dict:
34
+ return {
35
+ "model": "zai:glm-4.5",
36
+ "extra_body": {
37
+ "thinking": {
38
+ "type": "disabled",
39
+ }
40
+ },
41
+ }
42
+
43
+ @property
44
+ def has_tool_calling(self) -> bool:
45
+ return True
46
+
47
+ @property
48
+ def has_structured_output(self) -> bool:
49
+ return True
50
+
51
+ @property
52
+ def has_tool_choice(self) -> bool:
53
+ return False
54
+
55
+ @property
56
+ def supports_image_tool_message(self) -> bool:
57
+ return False
58
+
59
+ @property
60
+ def supports_json_mode(self) -> bool:
61
+ """(bool) whether the chat model supports JSON mode."""
62
+ return False
63
+
64
+
65
+ class TestImageProcessing:
66
+ @pytest.fixture(autouse=True)
67
+ def setup(self) -> None:
68
+ self.model = load_chat_model("zai:glm-4.6v")
69
+ self.image_url = "https://cloudcovert-1305175928.cos.ap-guangzhou.myqcloud.com/%E5%9B%BE%E7%89%87grounding.PNG"
70
+ self.image_data = base64.b64encode(httpx.get(self.image_url).content).decode(
71
+ "utf-8"
72
+ )
73
+
74
+ def test_image_url_format(self) -> None:
75
+ message = HumanMessage(
76
+ content=[
77
+ {"type": "text", "text": "Give a concise description of this image."},
78
+ {
79
+ "type": "image_url",
80
+ "image_url": {"url": f"data:image/png;base64,{self.image_data}"},
81
+ },
82
+ ],
83
+ )
84
+ _ = self.model.invoke([message])
85
+
86
+ def test_image_base64_format(self) -> None:
87
+ message = HumanMessage(
88
+ content=[
89
+ {"type": "text", "text": "Give a concise description of this image."},
90
+ {
91
+ "type": "image",
92
+ "base64": self.image_data,
93
+ "mime_type": "image/png",
94
+ },
95
+ ],
96
+ )
97
+ _ = self.model.invoke([message])
98
+
99
+ def test_image_url_direct_format(self) -> None:
100
+ message = HumanMessage(
101
+ content=[
102
+ {
103
+ "type": "text",
104
+ "text": "Give a concise description of this image. answer in Chinese",
105
+ },
106
+ {
107
+ "type": "image",
108
+ "url": self.image_url,
109
+ },
110
+ ],
111
+ )
112
+ _ = self.model.invoke([message])
113
+
114
+ def test_oai_format_tool_message(self) -> None:
115
+ oai_format_message = ToolMessage(
116
+ content=[
117
+ {
118
+ "type": "image_url",
119
+ "image_url": {"url": f"data:image/png;base64,{self.image_data}"},
120
+ },
121
+ ],
122
+ tool_call_id="1",
123
+ name="random_image",
124
+ )
125
+
126
+ messages = [
127
+ HumanMessage(
128
+ "get a random diagram using the tool and give it a concise description,answer in Chinese"
129
+ ),
130
+ AIMessage(
131
+ [],
132
+ tool_calls=[
133
+ {
134
+ "type": "tool_call",
135
+ "id": "1",
136
+ "name": "random_image",
137
+ "args": {},
138
+ }
139
+ ],
140
+ ),
141
+ oai_format_message,
142
+ ]
143
+
144
+ def random_image() -> str:
145
+ """Return a random image."""
146
+ return ""
147
+
148
+ _ = self.model.bind_tools([random_image]).invoke(messages)
149
+
150
+ def test_standard_format_tool_message(self) -> None:
151
+ standard_format_message = ToolMessage(
152
+ content=[
153
+ {
154
+ "type": "image",
155
+ "base64": self.image_data,
156
+ "mime_type": "image/png",
157
+ },
158
+ ],
159
+ tool_call_id="1",
160
+ name="random_image",
161
+ )
162
+
163
+ messages = [
164
+ HumanMessage(
165
+ "get a random diagram using the tool and give it a concise description,answer in Chinese"
166
+ ),
167
+ AIMessage(
168
+ [],
169
+ tool_calls=[
170
+ {
171
+ "type": "tool_call",
172
+ "id": "1",
173
+ "name": "random_image",
174
+ "args": {},
175
+ }
176
+ ],
177
+ ),
178
+ standard_format_message,
179
+ ]
180
+
181
+ def random_image() -> str:
182
+ """Return a random image."""
183
+ return ""
184
+
185
+ _ = self.model.bind_tools([random_image]).invoke(messages)
@@ -1 +0,0 @@
1
- __version__ = "1.2.11"
@@ -1,59 +0,0 @@
1
- from typing import Any, cast
2
-
3
- import pytest
4
- from langchain_core.language_models import BaseChatModel
5
- from langchain_tests.integration_tests.chat_models import ChatModelIntegrationTests
6
-
7
- from langchain_dev_utils.chat_models.base import load_chat_model
8
-
9
-
10
- class TestStandard(ChatModelIntegrationTests):
11
- @pytest.fixture
12
- def model(self, request: Any) -> BaseChatModel:
13
- """Model fixture."""
14
- extra_init_params = getattr(request, "param", None) or {}
15
- if extra_init_params.get("output_version") == "v1":
16
- pytest.skip("Output version v1 is not supported")
17
- return self.chat_model_class(
18
- **{
19
- **self.standard_chat_model_params,
20
- **self.chat_model_params,
21
- **extra_init_params,
22
- },
23
- )
24
-
25
- @property
26
- def chat_model_class(self) -> type[BaseChatModel]:
27
- return cast("type[BaseChatModel]", load_chat_model)
28
-
29
- @property
30
- def chat_model_params(self) -> dict:
31
- return {
32
- "model": "zai:glm-4.5",
33
- "extra_body": {
34
- "thinking": {
35
- "type": "disabled",
36
- }
37
- },
38
- }
39
-
40
- @property
41
- def has_tool_calling(self) -> bool:
42
- return True
43
-
44
- @property
45
- def has_structured_output(self) -> bool:
46
- return True
47
-
48
- @property
49
- def has_tool_choice(self) -> bool:
50
- return False
51
-
52
- @property
53
- def supports_image_tool_message(self) -> bool:
54
- return False
55
-
56
- @property
57
- def supports_json_mode(self) -> bool:
58
- """(bool) whether the chat model supports JSON mode."""
59
- return False