langchain-core 0.3.76__py3-none-any.whl → 0.3.78__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.
Potentially problematic release.
This version of langchain-core might be problematic. Click here for more details.
- langchain_core/_api/beta_decorator.py +6 -5
- langchain_core/_api/deprecation.py +11 -11
- langchain_core/callbacks/base.py +17 -11
- langchain_core/callbacks/manager.py +2 -2
- langchain_core/callbacks/usage.py +2 -2
- langchain_core/chat_history.py +26 -16
- langchain_core/document_loaders/langsmith.py +1 -1
- langchain_core/indexing/api.py +31 -31
- langchain_core/language_models/chat_models.py +4 -2
- langchain_core/language_models/fake_chat_models.py +5 -2
- langchain_core/language_models/llms.py +3 -1
- langchain_core/load/serializable.py +1 -1
- langchain_core/messages/ai.py +22 -10
- langchain_core/messages/base.py +30 -16
- langchain_core/messages/chat.py +4 -1
- langchain_core/messages/function.py +9 -5
- langchain_core/messages/human.py +11 -4
- langchain_core/messages/modifier.py +1 -0
- langchain_core/messages/system.py +9 -2
- langchain_core/messages/tool.py +27 -16
- langchain_core/messages/utils.py +97 -83
- langchain_core/outputs/chat_generation.py +10 -6
- langchain_core/prompt_values.py +6 -2
- langchain_core/prompts/chat.py +6 -3
- langchain_core/prompts/few_shot.py +4 -1
- langchain_core/runnables/base.py +14 -13
- langchain_core/runnables/graph.py +4 -1
- langchain_core/runnables/graph_ascii.py +1 -1
- langchain_core/runnables/graph_mermaid.py +27 -10
- langchain_core/runnables/retry.py +35 -18
- langchain_core/stores.py +6 -6
- langchain_core/tools/base.py +7 -5
- langchain_core/tools/convert.py +2 -2
- langchain_core/tools/simple.py +1 -5
- langchain_core/tools/structured.py +0 -10
- langchain_core/tracers/event_stream.py +13 -15
- langchain_core/utils/aiter.py +1 -1
- langchain_core/utils/function_calling.py +13 -8
- langchain_core/utils/iter.py +1 -1
- langchain_core/utils/json.py +7 -1
- langchain_core/utils/json_schema.py +145 -39
- langchain_core/utils/pydantic.py +6 -5
- langchain_core/utils/utils.py +1 -1
- langchain_core/vectorstores/in_memory.py +5 -5
- langchain_core/version.py +1 -1
- {langchain_core-0.3.76.dist-info → langchain_core-0.3.78.dist-info}/METADATA +8 -18
- {langchain_core-0.3.76.dist-info → langchain_core-0.3.78.dist-info}/RECORD +49 -49
- {langchain_core-0.3.76.dist-info → langchain_core-0.3.78.dist-info}/WHEEL +0 -0
- {langchain_core-0.3.76.dist-info → langchain_core-0.3.78.dist-info}/entry_points.txt +0 -0
|
@@ -6,6 +6,7 @@ import asyncio
|
|
|
6
6
|
import base64
|
|
7
7
|
import random
|
|
8
8
|
import re
|
|
9
|
+
import string
|
|
9
10
|
import time
|
|
10
11
|
from dataclasses import asdict
|
|
11
12
|
from pathlib import Path
|
|
@@ -148,7 +149,7 @@ def draw_mermaid(
|
|
|
148
149
|
+ "</em></small>"
|
|
149
150
|
)
|
|
150
151
|
node_label = format_dict.get(key, format_dict[default_class_label]).format(
|
|
151
|
-
|
|
152
|
+
_to_safe_id(key), label
|
|
152
153
|
)
|
|
153
154
|
return f"{indent}{node_label}\n"
|
|
154
155
|
|
|
@@ -211,8 +212,7 @@ def draw_mermaid(
|
|
|
211
212
|
edge_label = " -.-> " if edge.conditional else " --> "
|
|
212
213
|
|
|
213
214
|
mermaid_graph += (
|
|
214
|
-
f"\t{
|
|
215
|
-
f"{_escape_node_label(target)};\n"
|
|
215
|
+
f"\t{_to_safe_id(source)}{edge_label}{_to_safe_id(target)};\n"
|
|
216
216
|
)
|
|
217
217
|
|
|
218
218
|
# Recursively add nested subgraphs
|
|
@@ -256,9 +256,18 @@ def draw_mermaid(
|
|
|
256
256
|
return mermaid_graph
|
|
257
257
|
|
|
258
258
|
|
|
259
|
-
def
|
|
260
|
-
"""
|
|
261
|
-
|
|
259
|
+
def _to_safe_id(label: str) -> str:
|
|
260
|
+
"""Convert a string into a Mermaid-compatible node id.
|
|
261
|
+
|
|
262
|
+
Keep [a-zA-Z0-9_-] characters unchanged.
|
|
263
|
+
Map every other character -> backslash + lowercase hex codepoint.
|
|
264
|
+
|
|
265
|
+
Result is guaranteed to be unique and Mermaid-compatible,
|
|
266
|
+
so nodes with special characters always render correctly.
|
|
267
|
+
"""
|
|
268
|
+
allowed = string.ascii_letters + string.digits + "_-"
|
|
269
|
+
out = [ch if ch in allowed else "\\" + format(ord(ch), "x") for ch in label]
|
|
270
|
+
return "".join(out)
|
|
262
271
|
|
|
263
272
|
|
|
264
273
|
def _generate_mermaid_graph_styles(node_colors: NodeStyles) -> str:
|
|
@@ -277,6 +286,7 @@ def draw_mermaid_png(
|
|
|
277
286
|
padding: int = 10,
|
|
278
287
|
max_retries: int = 1,
|
|
279
288
|
retry_delay: float = 1.0,
|
|
289
|
+
base_url: Optional[str] = None,
|
|
280
290
|
) -> bytes:
|
|
281
291
|
"""Draws a Mermaid graph as PNG using provided syntax.
|
|
282
292
|
|
|
@@ -293,6 +303,8 @@ def draw_mermaid_png(
|
|
|
293
303
|
Defaults to 1.
|
|
294
304
|
retry_delay (float, optional): Delay between retries (MermaidDrawMethod.API).
|
|
295
305
|
Defaults to 1.0.
|
|
306
|
+
base_url (str, optional): Base URL for the Mermaid.ink API.
|
|
307
|
+
Defaults to None.
|
|
296
308
|
|
|
297
309
|
Returns:
|
|
298
310
|
bytes: PNG image bytes.
|
|
@@ -313,6 +325,7 @@ def draw_mermaid_png(
|
|
|
313
325
|
background_color=background_color,
|
|
314
326
|
max_retries=max_retries,
|
|
315
327
|
retry_delay=retry_delay,
|
|
328
|
+
base_url=base_url,
|
|
316
329
|
)
|
|
317
330
|
else:
|
|
318
331
|
supported_methods = ", ".join([m.value for m in MermaidDrawMethod])
|
|
@@ -404,8 +417,12 @@ def _render_mermaid_using_api(
|
|
|
404
417
|
file_type: Optional[Literal["jpeg", "png", "webp"]] = "png",
|
|
405
418
|
max_retries: int = 1,
|
|
406
419
|
retry_delay: float = 1.0,
|
|
420
|
+
base_url: Optional[str] = None,
|
|
407
421
|
) -> bytes:
|
|
408
422
|
"""Renders Mermaid graph using the Mermaid.INK API."""
|
|
423
|
+
# Defaults to using the public mermaid.ink server.
|
|
424
|
+
base_url = base_url if base_url is not None else "https://mermaid.ink"
|
|
425
|
+
|
|
409
426
|
if not _HAS_REQUESTS:
|
|
410
427
|
msg = (
|
|
411
428
|
"Install the `requests` module to use the Mermaid.INK API: "
|
|
@@ -425,7 +442,7 @@ def _render_mermaid_using_api(
|
|
|
425
442
|
background_color = f"!{background_color}"
|
|
426
443
|
|
|
427
444
|
image_url = (
|
|
428
|
-
f"
|
|
445
|
+
f"{base_url}/img/{mermaid_syntax_encoded}"
|
|
429
446
|
f"?type={file_type}&bgColor={background_color}"
|
|
430
447
|
)
|
|
431
448
|
|
|
@@ -457,7 +474,7 @@ def _render_mermaid_using_api(
|
|
|
457
474
|
|
|
458
475
|
# For other status codes, fail immediately
|
|
459
476
|
msg = (
|
|
460
|
-
"Failed to reach
|
|
477
|
+
f"Failed to reach {base_url} API while trying to render "
|
|
461
478
|
f"your graph. Status code: {response.status_code}.\n\n"
|
|
462
479
|
) + error_msg_suffix
|
|
463
480
|
raise ValueError(msg)
|
|
@@ -469,14 +486,14 @@ def _render_mermaid_using_api(
|
|
|
469
486
|
time.sleep(sleep_time)
|
|
470
487
|
else:
|
|
471
488
|
msg = (
|
|
472
|
-
"Failed to reach
|
|
489
|
+
f"Failed to reach {base_url} API while trying to render "
|
|
473
490
|
f"your graph after {max_retries} retries. "
|
|
474
491
|
) + error_msg_suffix
|
|
475
492
|
raise ValueError(msg) from e
|
|
476
493
|
|
|
477
494
|
# This should not be reached, but just in case
|
|
478
495
|
msg = (
|
|
479
|
-
"Failed to reach
|
|
496
|
+
f"Failed to reach {base_url} API while trying to render "
|
|
480
497
|
f"your graph after {max_retries} retries. "
|
|
481
498
|
) + error_msg_suffix
|
|
482
499
|
raise ValueError(msg)
|
|
@@ -238,31 +238,40 @@ class RunnableRetry(RunnableBindingBase[Input, Output]): # type: ignore[no-rede
|
|
|
238
238
|
) -> list[Union[Output, Exception]]:
|
|
239
239
|
results_map: dict[int, Output] = {}
|
|
240
240
|
|
|
241
|
-
def pending(iterable: list[U]) -> list[U]:
|
|
242
|
-
return [item for idx, item in enumerate(iterable) if idx not in results_map]
|
|
243
|
-
|
|
244
241
|
not_set: list[Output] = []
|
|
245
242
|
result = not_set
|
|
246
243
|
try:
|
|
247
244
|
for attempt in self._sync_retrying():
|
|
248
245
|
with attempt:
|
|
249
|
-
#
|
|
246
|
+
# Retry for inputs that have not yet succeeded
|
|
247
|
+
# Determine which original indices remain.
|
|
248
|
+
remaining_indices = [
|
|
249
|
+
i for i in range(len(inputs)) if i not in results_map
|
|
250
|
+
]
|
|
251
|
+
if not remaining_indices:
|
|
252
|
+
break
|
|
253
|
+
pending_inputs = [inputs[i] for i in remaining_indices]
|
|
254
|
+
pending_configs = [config[i] for i in remaining_indices]
|
|
255
|
+
pending_run_managers = [run_manager[i] for i in remaining_indices]
|
|
256
|
+
# Invoke underlying batch only on remaining elements.
|
|
250
257
|
result = super().batch(
|
|
251
|
-
|
|
258
|
+
pending_inputs,
|
|
252
259
|
self._patch_config_list(
|
|
253
|
-
|
|
260
|
+
pending_configs, pending_run_managers, attempt.retry_state
|
|
254
261
|
),
|
|
255
262
|
return_exceptions=True,
|
|
256
263
|
**kwargs,
|
|
257
264
|
)
|
|
258
|
-
# Register the results of the inputs that have succeeded
|
|
265
|
+
# Register the results of the inputs that have succeeded, mapping
|
|
266
|
+
# back to their original indices.
|
|
259
267
|
first_exception = None
|
|
260
|
-
for
|
|
268
|
+
for offset, r in enumerate(result):
|
|
261
269
|
if isinstance(r, Exception):
|
|
262
270
|
if not first_exception:
|
|
263
271
|
first_exception = r
|
|
264
272
|
continue
|
|
265
|
-
|
|
273
|
+
orig_idx = remaining_indices[offset]
|
|
274
|
+
results_map[orig_idx] = r
|
|
266
275
|
# If any exception occurred, raise it, to retry the failed ones
|
|
267
276
|
if first_exception:
|
|
268
277
|
raise first_exception
|
|
@@ -305,31 +314,39 @@ class RunnableRetry(RunnableBindingBase[Input, Output]): # type: ignore[no-rede
|
|
|
305
314
|
) -> list[Union[Output, Exception]]:
|
|
306
315
|
results_map: dict[int, Output] = {}
|
|
307
316
|
|
|
308
|
-
def pending(iterable: list[U]) -> list[U]:
|
|
309
|
-
return [item for idx, item in enumerate(iterable) if idx not in results_map]
|
|
310
|
-
|
|
311
317
|
not_set: list[Output] = []
|
|
312
318
|
result = not_set
|
|
313
319
|
try:
|
|
314
320
|
async for attempt in self._async_retrying():
|
|
315
321
|
with attempt:
|
|
316
|
-
#
|
|
322
|
+
# Retry for inputs that have not yet succeeded
|
|
323
|
+
# Determine which original indices remain.
|
|
324
|
+
remaining_indices = [
|
|
325
|
+
i for i in range(len(inputs)) if i not in results_map
|
|
326
|
+
]
|
|
327
|
+
if not remaining_indices:
|
|
328
|
+
break
|
|
329
|
+
pending_inputs = [inputs[i] for i in remaining_indices]
|
|
330
|
+
pending_configs = [config[i] for i in remaining_indices]
|
|
331
|
+
pending_run_managers = [run_manager[i] for i in remaining_indices]
|
|
317
332
|
result = await super().abatch(
|
|
318
|
-
|
|
333
|
+
pending_inputs,
|
|
319
334
|
self._patch_config_list(
|
|
320
|
-
|
|
335
|
+
pending_configs, pending_run_managers, attempt.retry_state
|
|
321
336
|
),
|
|
322
337
|
return_exceptions=True,
|
|
323
338
|
**kwargs,
|
|
324
339
|
)
|
|
325
|
-
# Register the results of the inputs that have succeeded
|
|
340
|
+
# Register the results of the inputs that have succeeded, mapping
|
|
341
|
+
# back to their original indices.
|
|
326
342
|
first_exception = None
|
|
327
|
-
for
|
|
343
|
+
for offset, r in enumerate(result):
|
|
328
344
|
if isinstance(r, Exception):
|
|
329
345
|
if not first_exception:
|
|
330
346
|
first_exception = r
|
|
331
347
|
continue
|
|
332
|
-
|
|
348
|
+
orig_idx = remaining_indices[offset]
|
|
349
|
+
results_map[orig_idx] = r
|
|
333
350
|
# If any exception occurred, raise it, to retry the failed ones
|
|
334
351
|
if first_exception:
|
|
335
352
|
raise first_exception
|
langchain_core/stores.py
CHANGED
|
@@ -56,22 +56,22 @@ class BaseStore(ABC, Generic[K, V]):
|
|
|
56
56
|
|
|
57
57
|
|
|
58
58
|
class MyInMemoryStore(BaseStore[str, int]):
|
|
59
|
-
def __init__(self):
|
|
60
|
-
self.store = {}
|
|
59
|
+
def __init__(self) -> None:
|
|
60
|
+
self.store: dict[str, int] = {}
|
|
61
61
|
|
|
62
|
-
def mget(self, keys):
|
|
62
|
+
def mget(self, keys: Sequence[str]) -> list[int | None]:
|
|
63
63
|
return [self.store.get(key) for key in keys]
|
|
64
64
|
|
|
65
|
-
def mset(self, key_value_pairs):
|
|
65
|
+
def mset(self, key_value_pairs: Sequence[tuple[str, int]]) -> None:
|
|
66
66
|
for key, value in key_value_pairs:
|
|
67
67
|
self.store[key] = value
|
|
68
68
|
|
|
69
|
-
def mdelete(self, keys):
|
|
69
|
+
def mdelete(self, keys: Sequence[str]) -> None:
|
|
70
70
|
for key in keys:
|
|
71
71
|
if key in self.store:
|
|
72
72
|
del self.store[key]
|
|
73
73
|
|
|
74
|
-
def yield_keys(self, prefix=None):
|
|
74
|
+
def yield_keys(self, prefix: str | None = None) -> Iterator[str]:
|
|
75
75
|
if prefix is None:
|
|
76
76
|
yield from self.store.keys()
|
|
77
77
|
else:
|
langchain_core/tools/base.py
CHANGED
|
@@ -82,6 +82,7 @@ TOOL_MESSAGE_BLOCK_TYPES = (
|
|
|
82
82
|
"search_result",
|
|
83
83
|
"custom_tool_call_output",
|
|
84
84
|
"document",
|
|
85
|
+
"file",
|
|
85
86
|
)
|
|
86
87
|
|
|
87
88
|
|
|
@@ -546,6 +547,8 @@ class ChildTool(BaseTool):
|
|
|
546
547
|
"""
|
|
547
548
|
if isinstance(self.args_schema, dict):
|
|
548
549
|
json_schema = self.args_schema
|
|
550
|
+
elif self.args_schema and issubclass(self.args_schema, BaseModelV1):
|
|
551
|
+
json_schema = self.args_schema.schema()
|
|
549
552
|
else:
|
|
550
553
|
input_schema = self.get_input_schema()
|
|
551
554
|
json_schema = input_schema.model_json_schema()
|
|
@@ -1322,8 +1325,7 @@ def get_all_basemodel_annotations(
|
|
|
1322
1325
|
"""
|
|
1323
1326
|
# cls has no subscript: cls = FooBar
|
|
1324
1327
|
if isinstance(cls, type):
|
|
1325
|
-
|
|
1326
|
-
fields = getattr(cls, "model_fields", {}) or getattr(cls, "__fields__", {})
|
|
1328
|
+
fields = get_fields(cls)
|
|
1327
1329
|
alias_map = {field.alias: name for name, field in fields.items() if field.alias}
|
|
1328
1330
|
|
|
1329
1331
|
annotations: dict[str, Union[type, TypeVar]] = {}
|
|
@@ -1362,8 +1364,8 @@ def get_all_basemodel_annotations(
|
|
|
1362
1364
|
continue
|
|
1363
1365
|
|
|
1364
1366
|
# if class = FooBar inherits from Baz[str]:
|
|
1365
|
-
# parent = Baz[str],
|
|
1366
|
-
# parent_origin = Baz,
|
|
1367
|
+
# parent = class Baz[str],
|
|
1368
|
+
# parent_origin = class Baz,
|
|
1367
1369
|
# generic_type_vars = (type vars in Baz)
|
|
1368
1370
|
# generic_map = {type var in Baz: str}
|
|
1369
1371
|
generic_type_vars: tuple = getattr(parent_origin, "__parameters__", ())
|
|
@@ -1400,7 +1402,7 @@ def _replace_type_vars(
|
|
|
1400
1402
|
if type_ in generic_map:
|
|
1401
1403
|
return generic_map[type_]
|
|
1402
1404
|
if default_to_bound:
|
|
1403
|
-
return type_.__bound__
|
|
1405
|
+
return type_.__bound__ if type_.__bound__ is not None else Any
|
|
1404
1406
|
return type_
|
|
1405
1407
|
if (origin := get_origin(type_)) and (args := get_args(type_)):
|
|
1406
1408
|
new_args = tuple(
|
langchain_core/tools/convert.py
CHANGED
|
@@ -227,7 +227,7 @@ def tool(
|
|
|
227
227
|
\"\"\"
|
|
228
228
|
return bar
|
|
229
229
|
|
|
230
|
-
""" # noqa: D214, D410, D411
|
|
230
|
+
""" # noqa: D214, D410, D411 # We're intentionally showing bad formatting in examples
|
|
231
231
|
|
|
232
232
|
def _create_tool_factory(
|
|
233
233
|
tool_name: str,
|
|
@@ -315,7 +315,7 @@ def tool(
|
|
|
315
315
|
|
|
316
316
|
if runnable is not None:
|
|
317
317
|
# tool is used as a function
|
|
318
|
-
# tool_from_runnable = tool("name", runnable)
|
|
318
|
+
# for instance tool_from_runnable = tool("name", runnable)
|
|
319
319
|
if not name_or_callable:
|
|
320
320
|
msg = "Runnable without name for tool constructor"
|
|
321
321
|
raise ValueError(msg)
|
langchain_core/tools/simple.py
CHANGED
|
@@ -64,11 +64,7 @@ class Tool(BaseTool):
|
|
|
64
64
|
The input arguments for the tool.
|
|
65
65
|
"""
|
|
66
66
|
if self.args_schema is not None:
|
|
67
|
-
|
|
68
|
-
json_schema = self.args_schema
|
|
69
|
-
else:
|
|
70
|
-
json_schema = self.args_schema.model_json_schema()
|
|
71
|
-
return json_schema["properties"]
|
|
67
|
+
return super().args
|
|
72
68
|
# For backwards compatibility, if the function signature is ambiguous,
|
|
73
69
|
# assume it takes a single string input.
|
|
74
70
|
return {"tool_input": {"type": "string"}}
|
|
@@ -67,16 +67,6 @@ class StructuredTool(BaseTool):
|
|
|
67
67
|
|
|
68
68
|
# --- Tool ---
|
|
69
69
|
|
|
70
|
-
@property
|
|
71
|
-
def args(self) -> dict:
|
|
72
|
-
"""The tool's input arguments."""
|
|
73
|
-
if isinstance(self.args_schema, dict):
|
|
74
|
-
json_schema = self.args_schema
|
|
75
|
-
else:
|
|
76
|
-
input_schema = self.get_input_schema()
|
|
77
|
-
json_schema = input_schema.model_json_schema()
|
|
78
|
-
return json_schema["properties"]
|
|
79
|
-
|
|
80
70
|
def _run(
|
|
81
71
|
self,
|
|
82
72
|
*args: Any,
|
|
@@ -224,7 +224,7 @@ class _AstreamEventsCallbackHandler(AsyncCallbackHandler, _StreamingCallbackHand
|
|
|
224
224
|
yield chunk
|
|
225
225
|
|
|
226
226
|
def tap_output_iter(self, run_id: UUID, output: Iterator[T]) -> Iterator[T]:
|
|
227
|
-
"""Tap the output
|
|
227
|
+
"""Tap the output iter.
|
|
228
228
|
|
|
229
229
|
Args:
|
|
230
230
|
run_id: The ID of the run.
|
|
@@ -315,7 +315,7 @@ class _AstreamEventsCallbackHandler(AsyncCallbackHandler, _StreamingCallbackHand
|
|
|
315
315
|
name: Optional[str] = None,
|
|
316
316
|
**kwargs: Any,
|
|
317
317
|
) -> None:
|
|
318
|
-
"""Start a trace for
|
|
318
|
+
"""Start a trace for a chat model run."""
|
|
319
319
|
name_ = _assign_name(name, serialized)
|
|
320
320
|
run_type = "chat_model"
|
|
321
321
|
|
|
@@ -357,7 +357,7 @@ class _AstreamEventsCallbackHandler(AsyncCallbackHandler, _StreamingCallbackHand
|
|
|
357
357
|
name: Optional[str] = None,
|
|
358
358
|
**kwargs: Any,
|
|
359
359
|
) -> None:
|
|
360
|
-
"""Start a trace for
|
|
360
|
+
"""Start a trace for a (non-chat model) LLM run."""
|
|
361
361
|
name_ = _assign_name(name, serialized)
|
|
362
362
|
run_type = "llm"
|
|
363
363
|
|
|
@@ -421,6 +421,10 @@ class _AstreamEventsCallbackHandler(AsyncCallbackHandler, _StreamingCallbackHand
|
|
|
421
421
|
parent_run_id: Optional[UUID] = None,
|
|
422
422
|
**kwargs: Any,
|
|
423
423
|
) -> None:
|
|
424
|
+
"""Run on new output token. Only available when streaming is enabled.
|
|
425
|
+
|
|
426
|
+
For both chat models and non-chat models (legacy LLMs).
|
|
427
|
+
"""
|
|
424
428
|
run_info = self.run_map.get(run_id)
|
|
425
429
|
chunk_: Union[GenerationChunk, BaseMessageChunk]
|
|
426
430
|
|
|
@@ -466,17 +470,15 @@ class _AstreamEventsCallbackHandler(AsyncCallbackHandler, _StreamingCallbackHand
|
|
|
466
470
|
async def on_llm_end(
|
|
467
471
|
self, response: LLMResult, *, run_id: UUID, **kwargs: Any
|
|
468
472
|
) -> None:
|
|
469
|
-
"""End a trace for
|
|
473
|
+
"""End a trace for a model run.
|
|
470
474
|
|
|
471
|
-
|
|
472
|
-
response (LLMResult): The response which was generated.
|
|
473
|
-
run_id (UUID): The run ID. This is the ID of the current run.
|
|
475
|
+
For both chat models and non-chat models (legacy LLMs).
|
|
474
476
|
|
|
475
477
|
Raises:
|
|
476
478
|
ValueError: If the run type is not ``'llm'`` or ``'chat_model'``.
|
|
477
479
|
"""
|
|
478
480
|
run_info = self.run_map.pop(run_id)
|
|
479
|
-
inputs_ = run_info
|
|
481
|
+
inputs_ = run_info.get("inputs")
|
|
480
482
|
|
|
481
483
|
generations: Union[list[list[GenerationChunk]], list[list[ChatGenerationChunk]]]
|
|
482
484
|
output: Union[dict, BaseMessage] = {}
|
|
@@ -654,10 +656,6 @@ class _AstreamEventsCallbackHandler(AsyncCallbackHandler, _StreamingCallbackHand
|
|
|
654
656
|
async def on_tool_end(self, output: Any, *, run_id: UUID, **kwargs: Any) -> None:
|
|
655
657
|
"""End a trace for a tool run.
|
|
656
658
|
|
|
657
|
-
Args:
|
|
658
|
-
output: The output of the tool.
|
|
659
|
-
run_id: The run ID. This is the ID of the current run.
|
|
660
|
-
|
|
661
659
|
Raises:
|
|
662
660
|
AssertionError: If the run ID is a tool call and does not have inputs
|
|
663
661
|
"""
|
|
@@ -742,7 +740,7 @@ class _AstreamEventsCallbackHandler(AsyncCallbackHandler, _StreamingCallbackHand
|
|
|
742
740
|
"event": "on_retriever_end",
|
|
743
741
|
"data": {
|
|
744
742
|
"output": documents,
|
|
745
|
-
"input": run_info
|
|
743
|
+
"input": run_info.get("inputs"),
|
|
746
744
|
},
|
|
747
745
|
"run_id": str(run_id),
|
|
748
746
|
"name": run_info["name"],
|
|
@@ -854,12 +852,12 @@ async def _astream_events_implementation_v1(
|
|
|
854
852
|
# Usually they will NOT be available for components that operate
|
|
855
853
|
# on streams, since those components stream the input and
|
|
856
854
|
# don't know its final value until the end of the stream.
|
|
857
|
-
inputs = log_entry
|
|
855
|
+
inputs = log_entry.get("inputs")
|
|
858
856
|
if inputs is not None:
|
|
859
857
|
data["input"] = inputs
|
|
860
858
|
|
|
861
859
|
if event_type == "end":
|
|
862
|
-
inputs = log_entry
|
|
860
|
+
inputs = log_entry.get("inputs")
|
|
863
861
|
if inputs is not None:
|
|
864
862
|
data["input"] = inputs
|
|
865
863
|
|
langchain_core/utils/aiter.py
CHANGED
|
@@ -165,7 +165,7 @@ class Tee(Generic[T]):
|
|
|
165
165
|
A ``tee`` works lazily and can handle an infinite ``iterable``, provided
|
|
166
166
|
that all iterators advance.
|
|
167
167
|
|
|
168
|
-
.. code-block::
|
|
168
|
+
.. code-block:: python
|
|
169
169
|
|
|
170
170
|
async def derivative(sensor_data):
|
|
171
171
|
previous, current = a.tee(sensor_data, n=2)
|
|
@@ -667,14 +667,13 @@ def tool_example_to_messages(
|
|
|
667
667
|
The ``ToolMessage`` is required because some chat models are hyper-optimized for
|
|
668
668
|
agents rather than for an extraction use case.
|
|
669
669
|
|
|
670
|
-
|
|
671
|
-
input:
|
|
672
|
-
tool_calls:
|
|
673
|
-
|
|
674
|
-
tool_outputs: Optional[list[str]], a list of tool call outputs.
|
|
670
|
+
Args:
|
|
671
|
+
input: The user input
|
|
672
|
+
tool_calls: Tool calls represented as Pydantic BaseModels
|
|
673
|
+
tool_outputs: Tool call outputs.
|
|
675
674
|
Does not need to be provided. If not provided, a placeholder value
|
|
676
675
|
will be inserted. Defaults to None.
|
|
677
|
-
ai_response:
|
|
676
|
+
ai_response: If provided, content for a final ``AIMessage``.
|
|
678
677
|
|
|
679
678
|
Returns:
|
|
680
679
|
A list of messages
|
|
@@ -833,8 +832,14 @@ def _recursive_set_additional_properties_false(
|
|
|
833
832
|
if isinstance(schema, dict):
|
|
834
833
|
# Check if 'required' is a key at the current level or if the schema is empty,
|
|
835
834
|
# in which case additionalProperties still needs to be specified.
|
|
836
|
-
if
|
|
837
|
-
"
|
|
835
|
+
if (
|
|
836
|
+
"required" in schema
|
|
837
|
+
or ("properties" in schema and not schema["properties"])
|
|
838
|
+
# Since Pydantic 2.11, it will always add `additionalProperties: True`
|
|
839
|
+
# for arbitrary dictionary schemas
|
|
840
|
+
# See: https://pydantic.dev/articles/pydantic-v2-11-release#changes
|
|
841
|
+
# If it is already set to True, we need override it to False
|
|
842
|
+
or "additionalProperties" in schema
|
|
838
843
|
):
|
|
839
844
|
schema["additionalProperties"] = False
|
|
840
845
|
|
langchain_core/utils/iter.py
CHANGED
|
@@ -102,7 +102,7 @@ class Tee(Generic[T]):
|
|
|
102
102
|
A ``tee`` works lazily and can handle an infinite ``iterable``, provided
|
|
103
103
|
that all iterators advance.
|
|
104
104
|
|
|
105
|
-
.. code-block::
|
|
105
|
+
.. code-block:: python
|
|
106
106
|
|
|
107
107
|
async def derivative(sensor_data):
|
|
108
108
|
previous, current = a.tee(sensor_data, n=2)
|
langchain_core/utils/json.py
CHANGED
|
@@ -101,7 +101,7 @@ def parse_partial_json(s: str, *, strict: bool = False) -> Any:
|
|
|
101
101
|
# If we're still inside a string at the end of processing,
|
|
102
102
|
# we need to close the string.
|
|
103
103
|
if is_inside_string:
|
|
104
|
-
if escaped: #
|
|
104
|
+
if escaped: # Remove unterminated escape character
|
|
105
105
|
new_chars.pop()
|
|
106
106
|
new_chars.append('"')
|
|
107
107
|
|
|
@@ -190,6 +190,12 @@ def parse_and_check_json_markdown(text: str, expected_keys: list[str]) -> dict:
|
|
|
190
190
|
except json.JSONDecodeError as e:
|
|
191
191
|
msg = f"Got invalid JSON object. Error: {e}"
|
|
192
192
|
raise OutputParserException(msg) from e
|
|
193
|
+
if not isinstance(json_obj, dict):
|
|
194
|
+
error_message = (
|
|
195
|
+
f"Expected JSON object (dict), but got: {type(json_obj).__name__}. "
|
|
196
|
+
)
|
|
197
|
+
raise OutputParserException(error_message, llm_output=text)
|
|
198
|
+
|
|
193
199
|
for key in expected_keys:
|
|
194
200
|
if key not in json_obj:
|
|
195
201
|
msg = (
|