langchain-dev-utils 1.2.8__py3-none-any.whl → 1.2.9__py3-none-any.whl
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.
- langchain_dev_utils/__init__.py +1 -1
- langchain_dev_utils/chat_models/adapters/openai_compatible.py +9 -14
- langchain_dev_utils/chat_models/types.py +2 -2
- langchain_dev_utils/embeddings/base.py +26 -26
- langchain_dev_utils/message_convert/content.py +18 -8
- langchain_dev_utils/tool_calling/utils.py +7 -7
- {langchain_dev_utils-1.2.8.dist-info → langchain_dev_utils-1.2.9.dist-info}/METADATA +33 -19
- {langchain_dev_utils-1.2.8.dist-info → langchain_dev_utils-1.2.9.dist-info}/RECORD +10 -10
- {langchain_dev_utils-1.2.8.dist-info → langchain_dev_utils-1.2.9.dist-info}/WHEEL +0 -0
- {langchain_dev_utils-1.2.8.dist-info → langchain_dev_utils-1.2.9.dist-info}/licenses/LICENSE +0 -0
langchain_dev_utils/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "1.2.
|
|
1
|
+
__version__ = "1.2.9"
|
|
@@ -41,7 +41,7 @@ from typing_extensions import Self
|
|
|
41
41
|
|
|
42
42
|
from ..types import (
|
|
43
43
|
CompatibilityOptions,
|
|
44
|
-
|
|
44
|
+
ReasoningKeepPolicy,
|
|
45
45
|
ResponseFormatType,
|
|
46
46
|
ToolChoiceType,
|
|
47
47
|
)
|
|
@@ -119,7 +119,7 @@ class _BaseChatOpenAICompatible(BaseChatOpenAI):
|
|
|
119
119
|
"""Supported tool choice"""
|
|
120
120
|
supported_response_format: ResponseFormatType = Field(default_factory=list)
|
|
121
121
|
"""Supported response format"""
|
|
122
|
-
|
|
122
|
+
reasoning_keep_policy: ReasoningKeepPolicy = Field(default="never")
|
|
123
123
|
"""How to keep reasoning content in the messages"""
|
|
124
124
|
include_usage: bool = Field(default=True)
|
|
125
125
|
"""Whether to include usage information in the output"""
|
|
@@ -159,7 +159,7 @@ class _BaseChatOpenAICompatible(BaseChatOpenAI):
|
|
|
159
159
|
|
|
160
160
|
payload_messages = []
|
|
161
161
|
last_human_index = -1
|
|
162
|
-
if self.
|
|
162
|
+
if self.reasoning_keep_policy == "current":
|
|
163
163
|
last_human_index = _get_last_human_message_index(messages)
|
|
164
164
|
|
|
165
165
|
for index, m in enumerate(messages):
|
|
@@ -167,15 +167,14 @@ class _BaseChatOpenAICompatible(BaseChatOpenAI):
|
|
|
167
167
|
msg_dict = _convert_message_to_dict(
|
|
168
168
|
_convert_from_v1_to_chat_completions(m)
|
|
169
169
|
)
|
|
170
|
-
if (
|
|
171
|
-
|
|
172
|
-
and m.additional_kwargs.get("reasoning_content")
|
|
170
|
+
if self.reasoning_keep_policy == "all" and m.additional_kwargs.get(
|
|
171
|
+
"reasoning_content"
|
|
173
172
|
):
|
|
174
173
|
msg_dict["reasoning_content"] = m.additional_kwargs.get(
|
|
175
174
|
"reasoning_content"
|
|
176
175
|
)
|
|
177
176
|
elif (
|
|
178
|
-
self.
|
|
177
|
+
self.reasoning_keep_policy == "current"
|
|
179
178
|
and index > last_human_index
|
|
180
179
|
and m.additional_kwargs.get("reasoning_content")
|
|
181
180
|
):
|
|
@@ -558,13 +557,9 @@ def _create_openai_compatible_model(
|
|
|
558
557
|
ToolChoiceType,
|
|
559
558
|
Field(default=compatibility_options.get("supported_tool_choice", ["auto"])),
|
|
560
559
|
),
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
Field(
|
|
564
|
-
default=compatibility_options.get(
|
|
565
|
-
"reasoning_content_keep_type", "discard"
|
|
566
|
-
)
|
|
567
|
-
),
|
|
560
|
+
reasoning_keep_policy=(
|
|
561
|
+
ReasoningKeepPolicy,
|
|
562
|
+
Field(default=compatibility_options.get("reasoning_keep_policy", "never")),
|
|
568
563
|
),
|
|
569
564
|
supported_response_format=(
|
|
570
565
|
ResponseFormatType,
|
|
@@ -9,11 +9,11 @@ ToolChoiceType = list[Literal["auto", "none", "required", "specific"]]
|
|
|
9
9
|
|
|
10
10
|
ResponseFormatType = list[Literal["json_schema", "json_mode"]]
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
ReasoningKeepPolicy = Literal["never", "current", "all"]
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class CompatibilityOptions(TypedDict):
|
|
16
16
|
supported_tool_choice: NotRequired[ToolChoiceType]
|
|
17
17
|
supported_response_format: NotRequired[ResponseFormatType]
|
|
18
|
-
|
|
18
|
+
reasoning_keep_policy: NotRequired[ReasoningKeepPolicy]
|
|
19
19
|
include_usage: NotRequired[bool]
|
|
@@ -218,30 +218,30 @@ def load_embeddings(
|
|
|
218
218
|
):
|
|
219
219
|
raise ValueError(f"Provider {provider} not registered")
|
|
220
220
|
|
|
221
|
-
if provider in
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
221
|
+
if provider in _EMBEDDINGS_PROVIDERS_DICT:
|
|
222
|
+
embeddings = _EMBEDDINGS_PROVIDERS_DICT[provider]["embeddings_model"]
|
|
223
|
+
if isinstance(embeddings, str):
|
|
224
|
+
if not (api_key := kwargs.get("api_key")):
|
|
225
|
+
api_key = secret_from_env(f"{provider.upper()}_API_KEY", default=None)()
|
|
226
|
+
if not api_key:
|
|
227
|
+
raise ValueError(
|
|
228
|
+
f"API key for {provider} not found. Please set it in the environment."
|
|
229
|
+
)
|
|
230
|
+
kwargs["api_key"] = api_key
|
|
231
|
+
if embeddings == "openai-compatible":
|
|
232
|
+
kwargs["check_embedding_ctx_length"] = False
|
|
233
|
+
embeddings = "openai"
|
|
234
|
+
return init_embeddings(
|
|
235
|
+
model=model,
|
|
236
|
+
provider=embeddings,
|
|
237
|
+
base_url=_EMBEDDINGS_PROVIDERS_DICT[provider]["base_url"],
|
|
238
|
+
**kwargs,
|
|
239
|
+
)
|
|
240
|
+
else:
|
|
241
|
+
if base_url := _EMBEDDINGS_PROVIDERS_DICT[provider].get("base_url"):
|
|
242
|
+
url_key = _get_base_url_field_name(embeddings)
|
|
243
|
+
if url_key is not None:
|
|
244
|
+
kwargs.update({url_key: base_url})
|
|
245
|
+
return embeddings(model=model, **kwargs)
|
|
242
246
|
else:
|
|
243
|
-
|
|
244
|
-
url_key = _get_base_url_field_name(embeddings)
|
|
245
|
-
if url_key is not None:
|
|
246
|
-
kwargs.update({url_key: base_url})
|
|
247
|
-
return embeddings(model=model, **kwargs)
|
|
247
|
+
return init_embeddings(model, provider=provider, **kwargs)
|
|
@@ -52,8 +52,10 @@ def convert_reasoning_content_for_ai_message(
|
|
|
52
52
|
reasoning_content = _get_reasoning_content(model_response)
|
|
53
53
|
|
|
54
54
|
if reasoning_content:
|
|
55
|
-
model_response.
|
|
56
|
-
|
|
55
|
+
return model_response.model_copy(
|
|
56
|
+
update={
|
|
57
|
+
"content": f"{think_tag[0]}{reasoning_content}{think_tag[1]}{model_response.content}"
|
|
58
|
+
}
|
|
57
59
|
)
|
|
58
60
|
return model_response
|
|
59
61
|
|
|
@@ -99,12 +101,16 @@ def convert_reasoning_content_for_chunk_iterator(
|
|
|
99
101
|
reasoning_content = _get_reasoning_content(chunk)
|
|
100
102
|
if reasoning_content:
|
|
101
103
|
if isfirst:
|
|
102
|
-
chunk
|
|
104
|
+
chunk = chunk.model_copy(
|
|
105
|
+
update={"content": f"{think_tag[0]}{reasoning_content}"}
|
|
106
|
+
)
|
|
103
107
|
isfirst = False
|
|
104
108
|
else:
|
|
105
|
-
chunk.content
|
|
109
|
+
chunk = chunk.model_copy(update={"content": reasoning_content})
|
|
106
110
|
elif chunk.content and isend and not isfirst:
|
|
107
|
-
chunk
|
|
111
|
+
chunk = chunk.model_copy(
|
|
112
|
+
update={"content": f"{think_tag[1]}{chunk.content}"}
|
|
113
|
+
)
|
|
108
114
|
isend = False
|
|
109
115
|
yield chunk
|
|
110
116
|
|
|
@@ -149,12 +155,16 @@ async def aconvert_reasoning_content_for_chunk_iterator(
|
|
|
149
155
|
reasoning_content = _get_reasoning_content(chunk)
|
|
150
156
|
if reasoning_content:
|
|
151
157
|
if isfirst:
|
|
152
|
-
chunk
|
|
158
|
+
chunk = chunk.model_copy(
|
|
159
|
+
update={"content": f"{think_tag[0]}{reasoning_content}"}
|
|
160
|
+
)
|
|
153
161
|
isfirst = False
|
|
154
162
|
else:
|
|
155
|
-
chunk.content
|
|
163
|
+
chunk = chunk.model_copy(update={"content": reasoning_content})
|
|
156
164
|
elif chunk.content and isend and not isfirst:
|
|
157
|
-
chunk
|
|
165
|
+
chunk = chunk.model_copy(
|
|
166
|
+
update={"content": f"{think_tag[1]}{chunk.content}"}
|
|
167
|
+
)
|
|
158
168
|
isend = False
|
|
159
169
|
yield chunk
|
|
160
170
|
|
|
@@ -62,20 +62,20 @@ def parse_tool_calling(
|
|
|
62
62
|
... tool_calls = parse_tool_calling(response)
|
|
63
63
|
"""
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
tool_calls = None
|
|
66
66
|
|
|
67
67
|
tool_call_blocks = [
|
|
68
68
|
block for block in message.content_blocks if block["type"] == "tool_call"
|
|
69
69
|
]
|
|
70
70
|
if tool_call_blocks:
|
|
71
|
-
|
|
71
|
+
tool_calls = tool_call_blocks
|
|
72
72
|
|
|
73
|
-
if not
|
|
74
|
-
|
|
73
|
+
if not tool_calls:
|
|
74
|
+
tool_calls = message.tool_calls
|
|
75
75
|
|
|
76
|
-
if not
|
|
76
|
+
if not tool_calls:
|
|
77
77
|
raise ValueError("No tool call found in message")
|
|
78
78
|
|
|
79
79
|
if first_tool_call_only:
|
|
80
|
-
return (
|
|
81
|
-
return [(tool_call["name"], tool_call["args"]) for tool_call in
|
|
80
|
+
return (tool_calls[0]["name"], tool_calls[0]["args"])
|
|
81
|
+
return [(tool_call["name"], tool_call["args"]) for tool_call in tool_calls]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: langchain-dev-utils
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.9
|
|
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
|
|
@@ -192,52 +192,66 @@ def get_current_time() -> str:
|
|
|
192
192
|
|
|
193
193
|
### 4. **Agent Development**
|
|
194
194
|
|
|
195
|
-
Includes the following
|
|
195
|
+
Includes the following capabilities:
|
|
196
196
|
|
|
197
|
-
-
|
|
198
|
-
-
|
|
197
|
+
- Multi-agent construction
|
|
198
|
+
- Commonly used middleware components
|
|
199
199
|
|
|
200
|
-
#### 4.1 Agent
|
|
200
|
+
#### 4.1 Multi-Agent Construction
|
|
201
201
|
|
|
202
|
-
|
|
202
|
+
Wrapping an agent as a tool is a common implementation pattern in multi-agent systems, as elaborated in the official LangChain documentation. To support this pattern, this library provides a pre-built utility function `wrap_agent_as_tool`, which encapsulates an agent instance into a tool that can be invoked by other agents.
|
|
203
203
|
|
|
204
|
-
Usage
|
|
204
|
+
**Usage Example**:
|
|
205
205
|
|
|
206
206
|
```python
|
|
207
|
-
|
|
207
|
+
import datetime
|
|
208
|
+
from langchain_dev_utils.agents import create_agent, wrap_agent_as_tool
|
|
208
209
|
from langchain.agents import AgentState
|
|
209
210
|
|
|
211
|
+
|
|
212
|
+
@tool
|
|
213
|
+
def get_current_time() -> str:
|
|
214
|
+
"""Get the current time."""
|
|
215
|
+
return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
216
|
+
|
|
217
|
+
|
|
210
218
|
agent = create_agent("vllm:qwen3-4b", tools=[get_current_time], name="time-agent")
|
|
211
|
-
|
|
219
|
+
call_time_agent_tool = wrap_agent_as_tool(agent)
|
|
220
|
+
response = call_time_agent_tool.invoke(
|
|
221
|
+
{"messages": [{"role": "user", "content": "What time is it now?"}]}
|
|
222
|
+
)
|
|
212
223
|
print(response)
|
|
213
224
|
```
|
|
214
225
|
|
|
215
226
|
#### 4.2 Middleware
|
|
216
227
|
|
|
217
|
-
Provides
|
|
218
|
-
|
|
219
|
-
`ToolCallRepairMiddleware` is used to repair `invalid_tool_calls` generated by large language models.
|
|
228
|
+
Provides several commonly used middleware components. Below are examples using `ToolCallRepairMiddleware` and `PlanMiddleware`.
|
|
220
229
|
|
|
221
|
-
`
|
|
230
|
+
- `ToolCallRepairMiddleware` automatically repairs malformed tool calls found in the model's `invalid_tool_calls` output.
|
|
231
|
+
- `PlanMiddleware` enables task planning capabilities for agents.
|
|
222
232
|
|
|
223
233
|
```python
|
|
224
234
|
from langchain_dev_utils.agents.middleware import (
|
|
225
|
-
|
|
235
|
+
ToolCallRepairMiddleware,
|
|
226
236
|
PlanMiddleware,
|
|
227
237
|
)
|
|
228
238
|
|
|
229
239
|
agent = create_agent(
|
|
230
240
|
"vllm:qwen3-4b",
|
|
231
241
|
name="plan-agent",
|
|
232
|
-
middleware=[
|
|
233
|
-
|
|
234
|
-
|
|
242
|
+
middleware=[
|
|
243
|
+
ToolCallRepairMiddleware(),
|
|
244
|
+
PlanMiddleware(use_read_plan_tool=False)
|
|
245
|
+
]
|
|
235
246
|
)
|
|
236
|
-
response = agent.invoke({"messages": [{"role": "user", "content": "Give me a travel plan
|
|
247
|
+
response = agent.invoke({"messages": [{"role": "user", "content": "Give me a travel plan for visiting New York."}]})
|
|
237
248
|
print(response)
|
|
238
249
|
```
|
|
239
250
|
|
|
240
|
-
**For more
|
|
251
|
+
**For more details on agent development and a complete list of built-in middleware, please refer to**:
|
|
252
|
+
[Multi-Agent Construction](https://tbice123123.github.io/langchain-dev-utils-docs/en/agent-development/multi-agent.html),
|
|
253
|
+
[Middleware](https://tbice123123.github.io/langchain-dev-utils-docs/en/agent-development/middleware.html)
|
|
254
|
+
|
|
241
255
|
|
|
242
256
|
### 5. **State Graph Orchestration**
|
|
243
257
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
langchain_dev_utils/__init__.py,sha256=
|
|
1
|
+
langchain_dev_utils/__init__.py,sha256=Oh3Y6CIypkhAjW-aquBTyP3_cA-gKgKTwq9EpcWpjps,22
|
|
2
2
|
langchain_dev_utils/_utils.py,sha256=MFEzR1BjXMj6HEVwt2x2omttFuDJ_rYAEbNqe99r9pM,1338
|
|
3
3
|
langchain_dev_utils/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
4
|
langchain_dev_utils/agents/__init__.py,sha256=PJ-lSDZv_AXMYA3H4fx-HzJa14tPbkGmq1HX8LNfaPo,125
|
|
@@ -16,13 +16,13 @@ langchain_dev_utils/agents/middleware/tool_emulator.py,sha256=OgtPhqturaWzF4fRSJ
|
|
|
16
16
|
langchain_dev_utils/agents/middleware/tool_selection.py,sha256=dRH5ejR6N02Djwxt6Gd63MYkg6SV5pySlzaRt53OoZk,3113
|
|
17
17
|
langchain_dev_utils/chat_models/__init__.py,sha256=YSLUyHrWEEj4y4DtGFCOnDW02VIYZdfAH800m4Klgeg,224
|
|
18
18
|
langchain_dev_utils/chat_models/base.py,sha256=CVMfgqMRnIKv8z4babusa2c4RKVuiWTL39mPD8cHAf4,11880
|
|
19
|
-
langchain_dev_utils/chat_models/types.py,sha256=
|
|
19
|
+
langchain_dev_utils/chat_models/types.py,sha256=kVLbT-IbvNtWPVmyVmh58le5r8XCqrEwuFB9-TWCBJk,672
|
|
20
20
|
langchain_dev_utils/chat_models/adapters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
|
-
langchain_dev_utils/chat_models/adapters/openai_compatible.py,sha256=
|
|
21
|
+
langchain_dev_utils/chat_models/adapters/openai_compatible.py,sha256=YgC8ups0owOVmY-Fwi7oxiktsC9LG6UZka8XNTCBW9g,21457
|
|
22
22
|
langchain_dev_utils/embeddings/__init__.py,sha256=zbEOaV86TUi9Zrg_dH9dpdgacWg31HMJTlTQknA9EKk,244
|
|
23
|
-
langchain_dev_utils/embeddings/base.py,sha256=
|
|
23
|
+
langchain_dev_utils/embeddings/base.py,sha256=BGoWY0L7nG9iRV3d4sSagXhECXrwvS1xA-A_OVltn3k,9406
|
|
24
24
|
langchain_dev_utils/message_convert/__init__.py,sha256=ZGrHGXPKMrZ_p9MqfIVZ4jgbEyb7aC4Q7X-muuThIYU,457
|
|
25
|
-
langchain_dev_utils/message_convert/content.py,sha256=
|
|
25
|
+
langchain_dev_utils/message_convert/content.py,sha256=2V1g21byg3iLv5RjUW8zv3jwYwV7IH2hNim7jGRsIes,8096
|
|
26
26
|
langchain_dev_utils/message_convert/format.py,sha256=1TOcJ09atH7LRtn_IIuBshKDXAyqoy3Q9b0Po-S-F9g,2377
|
|
27
27
|
langchain_dev_utils/pipeline/__init__.py,sha256=eE6WktaLHDkqMeXDIDaLtm-OPTwtsX_Av8iK9uYrceo,186
|
|
28
28
|
langchain_dev_utils/pipeline/parallel.py,sha256=nwZWbdSNeyanC9WufoJBTceotgT--UnPOfStXjgNMOc,5271
|
|
@@ -30,8 +30,8 @@ langchain_dev_utils/pipeline/sequential.py,sha256=sYJXQzVHDKUc-UV-HMv38JTPnse1A7
|
|
|
30
30
|
langchain_dev_utils/pipeline/types.py,sha256=T3aROKKXeWvd0jcH5XkgMDQfEkLfPaiOhhV2q58fDHs,112
|
|
31
31
|
langchain_dev_utils/tool_calling/__init__.py,sha256=mu_WxKMcu6RoTf4vkTPbA1WSBSNc6YIqyBtOQ6iVQj4,322
|
|
32
32
|
langchain_dev_utils/tool_calling/human_in_the_loop.py,sha256=7Z_QO5OZUR6K8nLoIcafc6osnvX2IYNorOJcbx6bVso,9672
|
|
33
|
-
langchain_dev_utils/tool_calling/utils.py,sha256=
|
|
34
|
-
langchain_dev_utils-1.2.
|
|
35
|
-
langchain_dev_utils-1.2.
|
|
36
|
-
langchain_dev_utils-1.2.
|
|
37
|
-
langchain_dev_utils-1.2.
|
|
33
|
+
langchain_dev_utils/tool_calling/utils.py,sha256=S4-KXQ8jWmpGTXYZitovF8rxKpaSSUkFruM8LDwvcvE,2765
|
|
34
|
+
langchain_dev_utils-1.2.9.dist-info/METADATA,sha256=zzBTgY8EUmuz08ofh1t1letaKywRXuqQgfPdqWBVw2Q,13279
|
|
35
|
+
langchain_dev_utils-1.2.9.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
36
|
+
langchain_dev_utils-1.2.9.dist-info/licenses/LICENSE,sha256=AWAOzNEcsvCEzHOF0qby5OKxviVH_eT9Yce1sgJTico,1084
|
|
37
|
+
langchain_dev_utils-1.2.9.dist-info/RECORD,,
|
|
File without changes
|
{langchain_dev_utils-1.2.8.dist-info → langchain_dev_utils-1.2.9.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|