langchain-core 0.3.71__py3-none-any.whl → 0.4.0.dev0__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.
Files changed (84) hide show
  1. langchain_core/__init__.py +1 -1
  2. langchain_core/_api/beta_decorator.py +1 -0
  3. langchain_core/_api/deprecation.py +2 -0
  4. langchain_core/beta/runnables/context.py +1 -0
  5. langchain_core/callbacks/base.py +23 -14
  6. langchain_core/callbacks/file.py +1 -0
  7. langchain_core/callbacks/manager.py +145 -19
  8. langchain_core/callbacks/streaming_stdout.py +4 -3
  9. langchain_core/callbacks/usage.py +15 -3
  10. langchain_core/chat_history.py +1 -0
  11. langchain_core/document_loaders/langsmith.py +2 -1
  12. langchain_core/documents/base.py +2 -0
  13. langchain_core/embeddings/fake.py +2 -0
  14. langchain_core/indexing/api.py +10 -0
  15. langchain_core/language_models/_utils.py +37 -0
  16. langchain_core/language_models/base.py +4 -1
  17. langchain_core/language_models/chat_models.py +48 -27
  18. langchain_core/language_models/fake_chat_models.py +71 -1
  19. langchain_core/language_models/llms.py +1 -0
  20. langchain_core/memory.py +1 -0
  21. langchain_core/messages/__init__.py +54 -0
  22. langchain_core/messages/ai.py +31 -18
  23. langchain_core/messages/content_blocks.py +1349 -69
  24. langchain_core/messages/human.py +1 -0
  25. langchain_core/messages/modifier.py +1 -1
  26. langchain_core/messages/tool.py +8 -83
  27. langchain_core/messages/utils.py +221 -6
  28. langchain_core/output_parsers/base.py +51 -14
  29. langchain_core/output_parsers/json.py +5 -2
  30. langchain_core/output_parsers/list.py +7 -2
  31. langchain_core/output_parsers/openai_functions.py +29 -5
  32. langchain_core/output_parsers/openai_tools.py +90 -47
  33. langchain_core/output_parsers/pydantic.py +3 -2
  34. langchain_core/output_parsers/transform.py +53 -12
  35. langchain_core/output_parsers/xml.py +14 -5
  36. langchain_core/outputs/llm_result.py +4 -1
  37. langchain_core/prompt_values.py +111 -7
  38. langchain_core/prompts/base.py +4 -0
  39. langchain_core/prompts/chat.py +3 -0
  40. langchain_core/prompts/few_shot.py +1 -0
  41. langchain_core/prompts/few_shot_with_templates.py +1 -0
  42. langchain_core/prompts/image.py +1 -0
  43. langchain_core/prompts/pipeline.py +1 -0
  44. langchain_core/prompts/prompt.py +1 -0
  45. langchain_core/prompts/structured.py +1 -0
  46. langchain_core/rate_limiters.py +1 -0
  47. langchain_core/retrievers.py +3 -0
  48. langchain_core/runnables/base.py +75 -57
  49. langchain_core/runnables/branch.py +1 -0
  50. langchain_core/runnables/config.py +2 -2
  51. langchain_core/runnables/configurable.py +2 -1
  52. langchain_core/runnables/fallbacks.py +3 -7
  53. langchain_core/runnables/graph.py +5 -3
  54. langchain_core/runnables/graph_ascii.py +1 -0
  55. langchain_core/runnables/graph_mermaid.py +1 -0
  56. langchain_core/runnables/history.py +1 -0
  57. langchain_core/runnables/passthrough.py +3 -0
  58. langchain_core/runnables/retry.py +1 -0
  59. langchain_core/runnables/router.py +1 -0
  60. langchain_core/runnables/schema.py +1 -0
  61. langchain_core/stores.py +3 -0
  62. langchain_core/tools/base.py +43 -11
  63. langchain_core/tools/convert.py +25 -3
  64. langchain_core/tools/retriever.py +8 -1
  65. langchain_core/tools/structured.py +10 -1
  66. langchain_core/tracers/base.py +14 -7
  67. langchain_core/tracers/context.py +1 -1
  68. langchain_core/tracers/core.py +27 -4
  69. langchain_core/tracers/event_stream.py +14 -3
  70. langchain_core/tracers/langchain.py +14 -3
  71. langchain_core/tracers/log_stream.py +4 -1
  72. langchain_core/utils/aiter.py +5 -0
  73. langchain_core/utils/function_calling.py +2 -1
  74. langchain_core/utils/iter.py +1 -0
  75. langchain_core/utils/json_schema.py +1 -1
  76. langchain_core/v1/__init__.py +1 -0
  77. langchain_core/v1/chat_models.py +1047 -0
  78. langchain_core/v1/messages.py +755 -0
  79. langchain_core/vectorstores/base.py +1 -0
  80. langchain_core/version.py +1 -1
  81. {langchain_core-0.3.71.dist-info → langchain_core-0.4.0.dev0.dist-info}/METADATA +1 -1
  82. {langchain_core-0.3.71.dist-info → langchain_core-0.4.0.dev0.dist-info}/RECORD +84 -81
  83. {langchain_core-0.3.71.dist-info → langchain_core-0.4.0.dev0.dist-info}/WHEEL +0 -0
  84. {langchain_core-0.3.71.dist-info → langchain_core-0.4.0.dev0.dist-info}/entry_points.txt +0 -0
@@ -1,8 +1,10 @@
1
+ import copy
1
2
  import re
2
3
  from collections.abc import Sequence
3
4
  from typing import Optional
4
5
 
5
6
  from langchain_core.messages import BaseMessage
7
+ from langchain_core.v1.messages import MessageV1
6
8
 
7
9
 
8
10
  def _is_openai_data_block(block: dict) -> bool:
@@ -51,6 +53,7 @@ def _parse_data_uri(uri: str) -> Optional[dict]:
51
53
  "mime_type": "image/jpeg",
52
54
  "data": "/9j/4AAQSkZJRg...",
53
55
  }
56
+
54
57
  """
55
58
  regex = r"^data:(?P<mime_type>[^;]+);base64,(?P<data>.+)$"
56
59
  match = re.match(regex, uri)
@@ -137,3 +140,37 @@ def _normalize_messages(messages: Sequence[BaseMessage]) -> list[BaseMessage]:
137
140
  formatted_messages.append(formatted_message)
138
141
 
139
142
  return formatted_messages
143
+
144
+
145
+ def _normalize_messages_v1(messages: Sequence[MessageV1]) -> list[MessageV1]:
146
+ """Extend support for message formats.
147
+
148
+ Chat models implement support for images in OpenAI Chat Completions format, as well
149
+ as other multimodal data as standard data blocks. This function extends support to
150
+ audio and file data in OpenAI Chat Completions format by converting them to standard
151
+ data blocks.
152
+ """
153
+ formatted_messages = []
154
+ for message in messages:
155
+ formatted_message = message
156
+ if isinstance(message.content, list):
157
+ for idx, block in enumerate(message.content):
158
+ if (
159
+ isinstance(block, dict)
160
+ # Subset to (PDF) files and audio, as most relevant chat models
161
+ # support images in OAI format (and some may not yet support the
162
+ # standard data block format)
163
+ and block.get("type") in {"file", "input_audio"}
164
+ and _is_openai_data_block(block) # type: ignore[arg-type]
165
+ ):
166
+ if formatted_message is message:
167
+ formatted_message = copy.copy(message)
168
+ # Also shallow-copy content
169
+ formatted_message.content = list(formatted_message.content)
170
+
171
+ formatted_message.content[idx] = ( # type: ignore[call-overload]
172
+ _convert_openai_format_to_data_block(block) # type: ignore[arg-type]
173
+ )
174
+ formatted_messages.append(formatted_message)
175
+
176
+ return formatted_messages
@@ -31,6 +31,7 @@ from langchain_core.messages import (
31
31
  from langchain_core.prompt_values import PromptValue
32
32
  from langchain_core.runnables import Runnable, RunnableSerializable
33
33
  from langchain_core.utils import get_pydantic_field_names
34
+ from langchain_core.v1.messages import AIMessage as AIMessageV1
34
35
 
35
36
  if TYPE_CHECKING:
36
37
  from langchain_core.outputs import LLMResult
@@ -85,7 +86,9 @@ def _get_token_ids_default_method(text: str) -> list[int]:
85
86
  LanguageModelInput = Union[PromptValue, str, Sequence[MessageLikeRepresentation]]
86
87
  LanguageModelOutput = Union[BaseMessage, str]
87
88
  LanguageModelLike = Runnable[LanguageModelInput, LanguageModelOutput]
88
- LanguageModelOutputVar = TypeVar("LanguageModelOutputVar", BaseMessage, str)
89
+ LanguageModelOutputVar = TypeVar(
90
+ "LanguageModelOutputVar", BaseMessage, str, AIMessageV1
91
+ )
89
92
 
90
93
 
91
94
  def _get_verbosity() -> bool:
@@ -11,22 +11,9 @@ from abc import ABC, abstractmethod
11
11
  from collections.abc import AsyncIterator, Iterator, Sequence
12
12
  from functools import cached_property
13
13
  from operator import itemgetter
14
- from typing import (
15
- TYPE_CHECKING,
16
- Any,
17
- Callable,
18
- Literal,
19
- Optional,
20
- Union,
21
- cast,
22
- )
14
+ from typing import TYPE_CHECKING, Any, Callable, Literal, Optional, Union, cast
23
15
 
24
- from pydantic import (
25
- BaseModel,
26
- ConfigDict,
27
- Field,
28
- model_validator,
29
- )
16
+ from pydantic import BaseModel, ConfigDict, Field, model_validator
30
17
  from typing_extensions import override
31
18
 
32
19
  from langchain_core._api import deprecated
@@ -63,6 +50,7 @@ from langchain_core.outputs import (
63
50
  ChatGeneration,
64
51
  ChatGenerationChunk,
65
52
  ChatResult,
53
+ Generation,
66
54
  LLMResult,
67
55
  RunInfo,
68
56
  )
@@ -653,6 +641,34 @@ class BaseChatModel(BaseLanguageModel[BaseMessage], ABC):
653
641
  def _combine_llm_outputs(self, llm_outputs: list[Optional[dict]]) -> dict: # noqa: ARG002
654
642
  return {}
655
643
 
644
+ def _convert_cached_generations(self, cache_val: list) -> list[ChatGeneration]:
645
+ """Convert cached Generation objects to ChatGeneration objects.
646
+
647
+ Handle case where cache contains Generation objects instead of
648
+ ChatGeneration objects. This can happen due to serialization/deserialization
649
+ issues or legacy cache data (see #22389).
650
+
651
+ Args:
652
+ cache_val: List of cached generation objects.
653
+
654
+ Returns:
655
+ List of ChatGeneration objects.
656
+ """
657
+ converted_generations = []
658
+ for gen in cache_val:
659
+ if isinstance(gen, Generation) and not isinstance(gen, ChatGeneration):
660
+ # Convert Generation to ChatGeneration by creating AIMessage
661
+ # from the text content
662
+ chat_gen = ChatGeneration(
663
+ message=AIMessage(content=gen.text),
664
+ generation_info=gen.generation_info,
665
+ )
666
+ converted_generations.append(chat_gen)
667
+ else:
668
+ # Already a ChatGeneration or other expected type
669
+ converted_generations.append(gen)
670
+ return converted_generations
671
+
656
672
  def _get_invocation_params(
657
673
  self,
658
674
  stop: Optional[list[str]] = None,
@@ -1010,7 +1026,8 @@ class BaseChatModel(BaseLanguageModel[BaseMessage], ABC):
1010
1026
  prompt = dumps(messages)
1011
1027
  cache_val = llm_cache.lookup(prompt, llm_string)
1012
1028
  if isinstance(cache_val, list):
1013
- return ChatResult(generations=cache_val)
1029
+ converted_generations = self._convert_cached_generations(cache_val)
1030
+ return ChatResult(generations=converted_generations)
1014
1031
  elif self.cache is None:
1015
1032
  pass
1016
1033
  else:
@@ -1082,7 +1099,8 @@ class BaseChatModel(BaseLanguageModel[BaseMessage], ABC):
1082
1099
  prompt = dumps(messages)
1083
1100
  cache_val = await llm_cache.alookup(prompt, llm_string)
1084
1101
  if isinstance(cache_val, list):
1085
- return ChatResult(generations=cache_val)
1102
+ converted_generations = self._convert_cached_generations(cache_val)
1103
+ return ChatResult(generations=converted_generations)
1086
1104
  elif self.cache is None:
1087
1105
  pass
1088
1106
  else:
@@ -1367,12 +1385,13 @@ class BaseChatModel(BaseLanguageModel[BaseMessage], ABC):
1367
1385
  """Model wrapper that returns outputs formatted to match the given schema.
1368
1386
 
1369
1387
  Args:
1370
- schema:
1371
- The output schema. Can be passed in as:
1372
- - an OpenAI function/tool schema,
1373
- - a JSON Schema,
1374
- - a TypedDict class,
1375
- - or a Pydantic class.
1388
+ schema: The output schema. Can be passed in as:
1389
+
1390
+ - an OpenAI function/tool schema,
1391
+ - a JSON Schema,
1392
+ - a TypedDict class,
1393
+ - or a Pydantic class.
1394
+
1376
1395
  If ``schema`` is a Pydantic class then the model output will be a
1377
1396
  Pydantic instance of that class, and the model-generated fields will be
1378
1397
  validated by the Pydantic class. Otherwise the model output will be a
@@ -1386,7 +1405,7 @@ class BaseChatModel(BaseLanguageModel[BaseMessage], ABC):
1386
1405
  then both the raw model response (a BaseMessage) and the parsed model
1387
1406
  response will be returned. If an error occurs during output parsing it
1388
1407
  will be caught and returned as well. The final output is always a dict
1389
- with keys "raw", "parsed", and "parsing_error".
1408
+ with keys ``'raw'``, ``'parsed'``, and ``'parsing_error'``.
1390
1409
 
1391
1410
  Returns:
1392
1411
  A Runnable that takes same inputs as a :class:`langchain_core.language_models.chat.BaseChatModel`.
@@ -1397,9 +1416,10 @@ class BaseChatModel(BaseLanguageModel[BaseMessage], ABC):
1397
1416
  Otherwise, if ``include_raw`` is False then Runnable outputs a dict.
1398
1417
 
1399
1418
  If ``include_raw`` is True, then Runnable outputs a dict with keys:
1400
- - ``"raw"``: BaseMessage
1401
- - ``"parsed"``: None if there was a parsing error, otherwise the type depends on the ``schema`` as described above.
1402
- - ``"parsing_error"``: Optional[BaseException]
1419
+
1420
+ - ``'raw'``: BaseMessage
1421
+ - ``'parsed'``: None if there was a parsing error, otherwise the type depends on the ``schema`` as described above.
1422
+ - ``'parsing_error'``: Optional[BaseException]
1403
1423
 
1404
1424
  Example: Pydantic schema (include_raw=False):
1405
1425
  .. code-block:: python
@@ -1465,6 +1485,7 @@ class BaseChatModel(BaseLanguageModel[BaseMessage], ABC):
1465
1485
  .. versionchanged:: 0.2.26
1466
1486
 
1467
1487
  Added support for TypedDict class.
1488
+
1468
1489
  """ # noqa: E501
1469
1490
  _ = kwargs.pop("method", None)
1470
1491
  _ = kwargs.pop("strict", None)
@@ -3,7 +3,7 @@
3
3
  import asyncio
4
4
  import re
5
5
  import time
6
- from collections.abc import AsyncIterator, Iterator
6
+ from collections.abc import AsyncIterator, Iterable, Iterator
7
7
  from typing import Any, Optional, Union, cast
8
8
 
9
9
  from typing_extensions import override
@@ -16,6 +16,10 @@ from langchain_core.language_models.chat_models import BaseChatModel, SimpleChat
16
16
  from langchain_core.messages import AIMessage, AIMessageChunk, BaseMessage
17
17
  from langchain_core.outputs import ChatGeneration, ChatGenerationChunk, ChatResult
18
18
  from langchain_core.runnables import RunnableConfig
19
+ from langchain_core.v1.chat_models import BaseChatModel as BaseChatModelV1
20
+ from langchain_core.v1.messages import AIMessage as AIMessageV1
21
+ from langchain_core.v1.messages import AIMessageChunk as AIMessageChunkV1
22
+ from langchain_core.v1.messages import MessageV1
19
23
 
20
24
 
21
25
  class FakeMessagesListChatModel(BaseChatModel):
@@ -367,3 +371,69 @@ class ParrotFakeChatModel(BaseChatModel):
367
371
  @property
368
372
  def _llm_type(self) -> str:
369
373
  return "parrot-fake-chat-model"
374
+
375
+
376
+ class GenericFakeChatModelV1(BaseChatModelV1):
377
+ """Generic fake chat model that can be used to test the chat model interface."""
378
+
379
+ messages: Optional[Iterator[Union[AIMessageV1, str]]] = None
380
+ message_chunks: Optional[Iterable[Union[AIMessageChunkV1, str]]] = None
381
+
382
+ @override
383
+ def _invoke(
384
+ self,
385
+ messages: list[MessageV1],
386
+ run_manager: Optional[CallbackManagerForLLMRun] = None,
387
+ **kwargs: Any,
388
+ ) -> AIMessageV1:
389
+ """Top Level call."""
390
+ if self.messages is None:
391
+ error_msg = "Messages iterator is not set."
392
+ raise ValueError(error_msg)
393
+ message = next(self.messages)
394
+ return AIMessageV1(content=message) if isinstance(message, str) else message
395
+
396
+ @override
397
+ def _stream(
398
+ self,
399
+ messages: list[MessageV1],
400
+ run_manager: Optional[CallbackManagerForLLMRun] = None,
401
+ **kwargs: Any,
402
+ ) -> Iterator[AIMessageChunkV1]:
403
+ """Top Level call."""
404
+ if self.message_chunks is None:
405
+ error_msg = "Message chunks iterator is not set."
406
+ raise ValueError(error_msg)
407
+ for chunk in self.message_chunks:
408
+ if isinstance(chunk, str):
409
+ yield AIMessageChunkV1(chunk)
410
+ else:
411
+ yield chunk
412
+
413
+ @property
414
+ def _llm_type(self) -> str:
415
+ return "generic-fake-chat-model"
416
+
417
+
418
+ class ParrotFakeChatModelV1(BaseChatModelV1):
419
+ """Generic fake chat model that can be used to test the chat model interface.
420
+
421
+ * Chat model should be usable in both sync and async tests
422
+ """
423
+
424
+ @override
425
+ def _invoke(
426
+ self,
427
+ messages: list[MessageV1],
428
+ stop: Optional[list[str]] = None,
429
+ run_manager: Optional[CallbackManagerForLLMRun] = None,
430
+ **kwargs: Any,
431
+ ) -> AIMessageV1:
432
+ """Top Level call."""
433
+ if isinstance(messages[-1], AIMessageV1):
434
+ return messages[-1]
435
+ return AIMessageV1(content=messages[-1].content)
436
+
437
+ @property
438
+ def _llm_type(self) -> str:
439
+ return "parrot-fake-chat-model"
@@ -1418,6 +1418,7 @@ class BaseLLM(BaseLanguageModel[str], ABC):
1418
1418
  .. code-block:: python
1419
1419
 
1420
1420
  llm.save(file_path="path/llm.yaml")
1421
+
1421
1422
  """
1422
1423
  # Convert file to Path object.
1423
1424
  save_path = Path(file_path)
langchain_core/memory.py CHANGED
@@ -53,6 +53,7 @@ class BaseMemory(Serializable, ABC):
53
53
 
54
54
  def clear(self) -> None:
55
55
  pass
56
+
56
57
  """ # noqa: E501
57
58
 
58
59
  model_config = ConfigDict(
@@ -33,6 +33,24 @@ if TYPE_CHECKING:
33
33
  )
34
34
  from langchain_core.messages.chat import ChatMessage, ChatMessageChunk
35
35
  from langchain_core.messages.content_blocks import (
36
+ Annotation,
37
+ AudioContentBlock,
38
+ Citation,
39
+ CodeInterpreterCall,
40
+ CodeInterpreterOutput,
41
+ CodeInterpreterResult,
42
+ ContentBlock,
43
+ DataContentBlock,
44
+ FileContentBlock,
45
+ ImageContentBlock,
46
+ NonStandardAnnotation,
47
+ NonStandardContentBlock,
48
+ PlainTextContentBlock,
49
+ ReasoningContentBlock,
50
+ TextContentBlock,
51
+ VideoContentBlock,
52
+ WebSearchCall,
53
+ WebSearchResult,
36
54
  convert_to_openai_data_block,
37
55
  convert_to_openai_image_block,
38
56
  is_data_content_block,
@@ -65,24 +83,42 @@ if TYPE_CHECKING:
65
83
  __all__ = (
66
84
  "AIMessage",
67
85
  "AIMessageChunk",
86
+ "Annotation",
68
87
  "AnyMessage",
88
+ "AudioContentBlock",
69
89
  "BaseMessage",
70
90
  "BaseMessageChunk",
71
91
  "ChatMessage",
72
92
  "ChatMessageChunk",
93
+ "Citation",
94
+ "CodeInterpreterCall",
95
+ "CodeInterpreterOutput",
96
+ "CodeInterpreterResult",
97
+ "ContentBlock",
98
+ "DataContentBlock",
99
+ "FileContentBlock",
73
100
  "FunctionMessage",
74
101
  "FunctionMessageChunk",
75
102
  "HumanMessage",
76
103
  "HumanMessageChunk",
104
+ "ImageContentBlock",
77
105
  "InvalidToolCall",
78
106
  "MessageLikeRepresentation",
107
+ "NonStandardAnnotation",
108
+ "NonStandardContentBlock",
109
+ "PlainTextContentBlock",
110
+ "ReasoningContentBlock",
79
111
  "RemoveMessage",
80
112
  "SystemMessage",
81
113
  "SystemMessageChunk",
114
+ "TextContentBlock",
82
115
  "ToolCall",
83
116
  "ToolCallChunk",
84
117
  "ToolMessage",
85
118
  "ToolMessageChunk",
119
+ "VideoContentBlock",
120
+ "WebSearchCall",
121
+ "WebSearchResult",
86
122
  "_message_from_dict",
87
123
  "convert_to_messages",
88
124
  "convert_to_openai_data_block",
@@ -103,25 +139,43 @@ __all__ = (
103
139
  _dynamic_imports = {
104
140
  "AIMessage": "ai",
105
141
  "AIMessageChunk": "ai",
142
+ "Annotation": "content_blocks",
143
+ "AudioContentBlock": "content_blocks",
106
144
  "BaseMessage": "base",
107
145
  "BaseMessageChunk": "base",
108
146
  "merge_content": "base",
109
147
  "message_to_dict": "base",
110
148
  "messages_to_dict": "base",
149
+ "Citation": "content_blocks",
150
+ "ContentBlock": "content_blocks",
111
151
  "ChatMessage": "chat",
112
152
  "ChatMessageChunk": "chat",
153
+ "CodeInterpreterCall": "content_blocks",
154
+ "CodeInterpreterOutput": "content_blocks",
155
+ "CodeInterpreterResult": "content_blocks",
156
+ "DataContentBlock": "content_blocks",
157
+ "FileContentBlock": "content_blocks",
113
158
  "FunctionMessage": "function",
114
159
  "FunctionMessageChunk": "function",
115
160
  "HumanMessage": "human",
116
161
  "HumanMessageChunk": "human",
162
+ "NonStandardAnnotation": "content_blocks",
163
+ "NonStandardContentBlock": "content_blocks",
164
+ "PlainTextContentBlock": "content_blocks",
165
+ "ReasoningContentBlock": "content_blocks",
117
166
  "RemoveMessage": "modifier",
118
167
  "SystemMessage": "system",
119
168
  "SystemMessageChunk": "system",
169
+ "WebSearchCall": "content_blocks",
170
+ "WebSearchResult": "content_blocks",
171
+ "ImageContentBlock": "content_blocks",
120
172
  "InvalidToolCall": "tool",
173
+ "TextContentBlock": "content_blocks",
121
174
  "ToolCall": "tool",
122
175
  "ToolCallChunk": "tool",
123
176
  "ToolMessage": "tool",
124
177
  "ToolMessageChunk": "tool",
178
+ "VideoContentBlock": "content_blocks",
125
179
  "AnyMessage": "utils",
126
180
  "MessageLikeRepresentation": "utils",
127
181
  "_message_from_dict": "utils",
@@ -8,11 +8,7 @@ from typing import Any, Literal, Optional, Union, cast
8
8
  from pydantic import model_validator
9
9
  from typing_extensions import NotRequired, Self, TypedDict, override
10
10
 
11
- from langchain_core.messages.base import (
12
- BaseMessage,
13
- BaseMessageChunk,
14
- merge_content,
15
- )
11
+ from langchain_core.messages.base import BaseMessage, BaseMessageChunk, merge_content
16
12
  from langchain_core.messages.tool import (
17
13
  InvalidToolCall,
18
14
  ToolCall,
@@ -20,15 +16,9 @@ from langchain_core.messages.tool import (
20
16
  default_tool_chunk_parser,
21
17
  default_tool_parser,
22
18
  )
23
- from langchain_core.messages.tool import (
24
- invalid_tool_call as create_invalid_tool_call,
25
- )
26
- from langchain_core.messages.tool import (
27
- tool_call as create_tool_call,
28
- )
29
- from langchain_core.messages.tool import (
30
- tool_call_chunk as create_tool_call_chunk,
31
- )
19
+ from langchain_core.messages.tool import invalid_tool_call as create_invalid_tool_call
20
+ from langchain_core.messages.tool import tool_call as create_tool_call
21
+ from langchain_core.messages.tool import tool_call_chunk as create_tool_call_chunk
32
22
  from langchain_core.utils._merge import merge_dicts, merge_lists
33
23
  from langchain_core.utils.json import parse_partial_json
34
24
  from langchain_core.utils.usage import _dict_int_op
@@ -37,6 +27,16 @@ logger = logging.getLogger(__name__)
37
27
 
38
28
 
39
29
  _LC_ID_PREFIX = "run-"
30
+ """Internal tracing/callback system identifier.
31
+
32
+ Used for:
33
+ - Tracing. Every LangChain operation (LLM call, chain execution, tool use, etc.)
34
+ gets a unique run_id (UUID)
35
+ - Enables tracking parent-child relationships between operations
36
+ """
37
+
38
+ _LC_AUTO_PREFIX = "lc_"
39
+ """LangChain auto-generated ID prefix for messages and content blocks."""
40
40
 
41
41
 
42
42
  class InputTokenDetails(TypedDict, total=False):
@@ -57,6 +57,7 @@ class InputTokenDetails(TypedDict, total=False):
57
57
  .. versionadded:: 0.3.9
58
58
 
59
59
  May also hold extra provider-specific keys.
60
+
60
61
  """
61
62
 
62
63
  audio: int
@@ -89,6 +90,7 @@ class OutputTokenDetails(TypedDict, total=False):
89
90
  }
90
91
 
91
92
  .. versionadded:: 0.3.9
93
+
92
94
  """
93
95
 
94
96
  audio: int
@@ -128,6 +130,7 @@ class UsageMetadata(TypedDict):
128
130
  .. versionchanged:: 0.3.9
129
131
 
130
132
  Added ``input_token_details`` and ``output_token_details``.
133
+
131
134
  """
132
135
 
133
136
  input_tokens: int
@@ -425,17 +428,27 @@ def add_ai_message_chunks(
425
428
 
426
429
  chunk_id = None
427
430
  candidates = [left.id] + [o.id for o in others]
428
- # first pass: pick the first non-run-* id
431
+ # first pass: pick the first provider-assigned id (non-run-* and non-lc_*)
429
432
  for id_ in candidates:
430
- if id_ and not id_.startswith(_LC_ID_PREFIX):
433
+ if (
434
+ id_
435
+ and not id_.startswith(_LC_ID_PREFIX)
436
+ and not id_.startswith(_LC_AUTO_PREFIX)
437
+ ):
431
438
  chunk_id = id_
432
439
  break
433
440
  else:
434
- # second pass: no provider-assigned id found, just take the first non-null
441
+ # second pass: prefer lc_* ids over run-* ids
435
442
  for id_ in candidates:
436
- if id_:
443
+ if id_ and id_.startswith(_LC_AUTO_PREFIX):
437
444
  chunk_id = id_
438
445
  break
446
+ else:
447
+ # third pass: take any remaining id (run-* ids)
448
+ for id_ in candidates:
449
+ if id_:
450
+ chunk_id = id_
451
+ break
439
452
 
440
453
  return left.__class__(
441
454
  example=left.example,