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.
@@ -1 +1 @@
1
- __version__ = "1.2.8"
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
- ReasoningContentKeepType,
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
- reasoning_content_keep_type: ReasoningContentKeepType = Field(default="discard")
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.reasoning_content_keep_type == "temp":
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
- self.reasoning_content_keep_type == "retain"
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.reasoning_content_keep_type == "temp"
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
- reasoning_content_keep_type=(
562
- ReasoningContentKeepType,
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
- ReasoningContentKeepType = Literal["discard", "temp", "retain"]
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
- reasoning_content_keep_type: NotRequired[ReasoningContentKeepType]
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 _SUPPORTED_PROVIDERS:
222
- return init_embeddings(model, provider=provider, **kwargs)
223
-
224
- embeddings = _EMBEDDINGS_PROVIDERS_DICT[provider]["embeddings_model"]
225
- if isinstance(embeddings, str):
226
- if not (api_key := kwargs.get("api_key")):
227
- api_key = secret_from_env(f"{provider.upper()}_API_KEY", default=None)()
228
- if not api_key:
229
- raise ValueError(
230
- f"API key for {provider} not found. Please set it in the environment."
231
- )
232
- kwargs["api_key"] = api_key
233
- if embeddings == "openai-compatible":
234
- kwargs["check_embedding_ctx_length"] = False
235
- embeddings = "openai"
236
- return init_embeddings(
237
- model=model,
238
- provider=embeddings,
239
- base_url=_EMBEDDINGS_PROVIDERS_DICT[provider]["base_url"],
240
- **kwargs,
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
- if base_url := _EMBEDDINGS_PROVIDERS_DICT[provider].get("base_url"):
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.content = (
56
- f"{think_tag[0]}{reasoning_content}{think_tag[1]}{model_response.content}"
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.content = f"{think_tag[0]}{reasoning_content}"
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 = reasoning_content
109
+ chunk = chunk.model_copy(update={"content": reasoning_content})
106
110
  elif chunk.content and isend and not isfirst:
107
- chunk.content = f"{think_tag[1]}{chunk.content}"
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.content = f"{think_tag[0]}{reasoning_content}"
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 = reasoning_content
163
+ chunk = chunk.model_copy(update={"content": reasoning_content})
156
164
  elif chunk.content and isend and not isfirst:
157
- chunk.content = f"{think_tag[1]}{chunk.content}"
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
- tool_call = None
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
- tool_call = tool_call_blocks
71
+ tool_calls = tool_call_blocks
72
72
 
73
- if not tool_call:
74
- tool_call = message.tool_calls
73
+ if not tool_calls:
74
+ tool_calls = message.tool_calls
75
75
 
76
- if not tool_call:
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 (tool_call[0]["name"], tool_call[0]["args"])
81
- return [(tool_call["name"], tool_call["args"]) for tool_call in tool_call]
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.8
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 features:
195
+ Includes the following capabilities:
196
196
 
197
- - Predefined agent factory functions
198
- - Common middleware components
197
+ - Multi-agent construction
198
+ - Commonly used middleware components
199
199
 
200
- #### 4.1 Agent Factory Functions
200
+ #### 4.1 Multi-Agent Construction
201
201
 
202
- In LangChain v1, the official `create_agent` function can be used to create a single agent; its `model` parameter accepts either a BaseChatModel instance or a specific string (when a string is provided, only models supported by `init_chat_model` are allowed). To extend the flexibility of specifying models via string, this library provides an equivalent `create_agent` function that lets you designate any model supported by `load_chat_model` (registration required beforehand).
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 example:
204
+ **Usage Example**:
205
205
 
206
206
  ```python
207
- from langchain_dev_utils.agents import create_agent
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
- response = agent.invoke({"messages": [{"role": "user", "content": "What time is it?"}]})
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 some commonly used middleware components. Below, we illustrate with `ToolCallRepairMiddleware` and `PlanMiddleware`.
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
- `PlanMiddleware` is used for agent planning.
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
- ToolcallRepairMiddleware,
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=[ToolCallRepairMiddleware(), PlanMiddleware(
233
- use_read_plan_tool=False
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 to New York"}]})
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 information about agent development and all built-in middleware, please refer to**: [Pre-built Agent Functions](https://tbice123123.github.io/langchain-dev-utils-docs/en/agent-development/prebuilt.html), [Middleware](https://tbice123123.github.io/langchain-dev-utils-docs/en/agent-development/middleware.html)
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=CfVXm0wwlKPW0khOcwhWw61TpgtZiLijCePsAIOK3aU,22
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=w9Zu2I_HtpWQ1jNEUE9QkEunxD6UUtIh0hGJVb7b5gk,690
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=hH713hs4LRfqUbYlqJKR0geJUjXkQAbU6-segYyuLCE,21599
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=l4uCB5ecr3GAkfYGpYxqamOPIM6fkP1H_QK-277YEic,9295
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=LhrFXL1zYkkpp4ave6SBorDLig5xnllQ2VYCgFz-eR4,7681
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=W2ZRRMhn7SHHZxFfCXVaPIh2uFkY2XkO6EWrdRuv6VE,2757
34
- langchain_dev_utils-1.2.8.dist-info/METADATA,sha256=gzvPmy60STe1Yg-qZHJVMBRPRwXZ8_3uabCEdZjeXmE,13100
35
- langchain_dev_utils-1.2.8.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
36
- langchain_dev_utils-1.2.8.dist-info/licenses/LICENSE,sha256=AWAOzNEcsvCEzHOF0qby5OKxviVH_eT9Yce1sgJTico,1084
37
- langchain_dev_utils-1.2.8.dist-info/RECORD,,
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,,