langchain-core 0.4.0.dev0__py3-none-any.whl → 1.0.0a2__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_core/_api/beta_decorator.py +2 -2
- langchain_core/_api/deprecation.py +1 -1
- langchain_core/beta/runnables/context.py +1 -1
- langchain_core/callbacks/base.py +14 -23
- langchain_core/callbacks/file.py +13 -2
- langchain_core/callbacks/manager.py +74 -157
- langchain_core/callbacks/streaming_stdout.py +3 -4
- langchain_core/callbacks/usage.py +2 -12
- langchain_core/chat_history.py +6 -6
- langchain_core/documents/base.py +1 -1
- langchain_core/documents/compressor.py +9 -6
- langchain_core/indexing/base.py +2 -2
- langchain_core/language_models/_utils.py +232 -101
- langchain_core/language_models/base.py +35 -23
- langchain_core/language_models/chat_models.py +248 -54
- langchain_core/language_models/fake_chat_models.py +28 -81
- langchain_core/load/dump.py +3 -4
- langchain_core/messages/__init__.py +30 -24
- langchain_core/messages/ai.py +188 -30
- langchain_core/messages/base.py +164 -25
- langchain_core/messages/block_translators/__init__.py +89 -0
- langchain_core/messages/block_translators/anthropic.py +451 -0
- langchain_core/messages/block_translators/bedrock.py +45 -0
- langchain_core/messages/block_translators/bedrock_converse.py +47 -0
- langchain_core/messages/block_translators/google_genai.py +45 -0
- langchain_core/messages/block_translators/google_vertexai.py +47 -0
- langchain_core/messages/block_translators/groq.py +45 -0
- langchain_core/messages/block_translators/langchain_v0.py +164 -0
- langchain_core/messages/block_translators/ollama.py +45 -0
- langchain_core/messages/block_translators/openai.py +798 -0
- langchain_core/messages/{content_blocks.py → content.py} +303 -278
- langchain_core/messages/human.py +29 -9
- langchain_core/messages/system.py +29 -9
- langchain_core/messages/tool.py +94 -13
- langchain_core/messages/utils.py +34 -234
- langchain_core/output_parsers/base.py +14 -50
- langchain_core/output_parsers/json.py +2 -5
- langchain_core/output_parsers/list.py +2 -7
- langchain_core/output_parsers/openai_functions.py +5 -28
- langchain_core/output_parsers/openai_tools.py +49 -90
- langchain_core/output_parsers/pydantic.py +2 -3
- langchain_core/output_parsers/transform.py +12 -53
- langchain_core/output_parsers/xml.py +9 -17
- langchain_core/prompt_values.py +8 -112
- langchain_core/prompts/chat.py +1 -3
- langchain_core/runnables/base.py +500 -451
- langchain_core/runnables/branch.py +1 -1
- langchain_core/runnables/fallbacks.py +4 -4
- langchain_core/runnables/history.py +1 -1
- langchain_core/runnables/passthrough.py +3 -3
- langchain_core/runnables/retry.py +1 -1
- langchain_core/runnables/router.py +1 -1
- langchain_core/structured_query.py +3 -7
- langchain_core/tools/base.py +14 -41
- langchain_core/tools/convert.py +2 -22
- langchain_core/tools/retriever.py +1 -8
- langchain_core/tools/structured.py +2 -10
- langchain_core/tracers/_streaming.py +6 -7
- langchain_core/tracers/base.py +7 -14
- langchain_core/tracers/core.py +4 -27
- langchain_core/tracers/event_stream.py +4 -15
- langchain_core/tracers/langchain.py +3 -14
- langchain_core/tracers/log_stream.py +2 -3
- langchain_core/utils/_merge.py +45 -7
- langchain_core/utils/function_calling.py +22 -9
- langchain_core/utils/utils.py +29 -0
- langchain_core/version.py +1 -1
- {langchain_core-0.4.0.dev0.dist-info → langchain_core-1.0.0a2.dist-info}/METADATA +7 -9
- {langchain_core-0.4.0.dev0.dist-info → langchain_core-1.0.0a2.dist-info}/RECORD +71 -64
- langchain_core/v1/__init__.py +0 -1
- langchain_core/v1/chat_models.py +0 -1047
- langchain_core/v1/messages.py +0 -755
- {langchain_core-0.4.0.dev0.dist-info → langchain_core-1.0.0a2.dist-info}/WHEEL +0 -0
- {langchain_core-0.4.0.dev0.dist-info → langchain_core-1.0.0a2.dist-info}/entry_points.txt +0 -0
langchain_core/messages/human.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"""Human message."""
|
|
2
2
|
|
|
3
|
-
from typing import Any, Literal, Union
|
|
3
|
+
from typing import Any, Literal, Optional, Union, cast, overload
|
|
4
4
|
|
|
5
|
+
from langchain_core.messages import content as types
|
|
5
6
|
from langchain_core.messages.base import BaseMessage, BaseMessageChunk
|
|
6
7
|
|
|
7
8
|
|
|
@@ -41,16 +42,35 @@ class HumanMessage(BaseMessage):
|
|
|
41
42
|
type: Literal["human"] = "human"
|
|
42
43
|
"""The type of the message (used for serialization). Defaults to "human"."""
|
|
43
44
|
|
|
45
|
+
@overload
|
|
44
46
|
def __init__(
|
|
45
|
-
self,
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
self,
|
|
48
|
+
content: Union[str, list[Union[str, dict]]],
|
|
49
|
+
**kwargs: Any,
|
|
50
|
+
) -> None: ...
|
|
51
|
+
|
|
52
|
+
@overload
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
content: Optional[Union[str, list[Union[str, dict]]]] = None,
|
|
56
|
+
content_blocks: Optional[list[types.ContentBlock]] = None,
|
|
57
|
+
**kwargs: Any,
|
|
58
|
+
) -> None: ...
|
|
48
59
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
60
|
+
def __init__(
|
|
61
|
+
self,
|
|
62
|
+
content: Optional[Union[str, list[Union[str, dict]]]] = None,
|
|
63
|
+
content_blocks: Optional[list[types.ContentBlock]] = None,
|
|
64
|
+
**kwargs: Any,
|
|
65
|
+
) -> None:
|
|
66
|
+
"""Specify ``content`` as positional arg or ``content_blocks`` for typing."""
|
|
67
|
+
if content_blocks is not None:
|
|
68
|
+
super().__init__(
|
|
69
|
+
content=cast("Union[str, list[Union[str, dict]]]", content_blocks),
|
|
70
|
+
**kwargs,
|
|
71
|
+
)
|
|
72
|
+
else:
|
|
73
|
+
super().__init__(content=content, **kwargs)
|
|
54
74
|
|
|
55
75
|
|
|
56
76
|
class HumanMessageChunk(HumanMessage, BaseMessageChunk):
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"""System message."""
|
|
2
2
|
|
|
3
|
-
from typing import Any, Literal, Union
|
|
3
|
+
from typing import Any, Literal, Optional, Union, cast, overload
|
|
4
4
|
|
|
5
|
+
from langchain_core.messages import content as types
|
|
5
6
|
from langchain_core.messages.base import BaseMessage, BaseMessageChunk
|
|
6
7
|
|
|
7
8
|
|
|
@@ -34,16 +35,35 @@ class SystemMessage(BaseMessage):
|
|
|
34
35
|
type: Literal["system"] = "system"
|
|
35
36
|
"""The type of the message (used for serialization). Defaults to "system"."""
|
|
36
37
|
|
|
38
|
+
@overload
|
|
37
39
|
def __init__(
|
|
38
|
-
self,
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
self,
|
|
41
|
+
content: Union[str, list[Union[str, dict]]],
|
|
42
|
+
**kwargs: Any,
|
|
43
|
+
) -> None: ...
|
|
44
|
+
|
|
45
|
+
@overload
|
|
46
|
+
def __init__(
|
|
47
|
+
self,
|
|
48
|
+
content: Optional[Union[str, list[Union[str, dict]]]] = None,
|
|
49
|
+
content_blocks: Optional[list[types.ContentBlock]] = None,
|
|
50
|
+
**kwargs: Any,
|
|
51
|
+
) -> None: ...
|
|
41
52
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
content: Optional[Union[str, list[Union[str, dict]]]] = None,
|
|
56
|
+
content_blocks: Optional[list[types.ContentBlock]] = None,
|
|
57
|
+
**kwargs: Any,
|
|
58
|
+
) -> None:
|
|
59
|
+
"""Specify ``content`` as positional arg or ``content_blocks`` for typing."""
|
|
60
|
+
if content_blocks is not None:
|
|
61
|
+
super().__init__(
|
|
62
|
+
content=cast("Union[str, list[Union[str, dict]]]", content_blocks),
|
|
63
|
+
**kwargs,
|
|
64
|
+
)
|
|
65
|
+
else:
|
|
66
|
+
super().__init__(content=content, **kwargs)
|
|
47
67
|
|
|
48
68
|
|
|
49
69
|
class SystemMessageChunk(SystemMessage, BaseMessageChunk):
|
langchain_core/messages/tool.py
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
"""Messages for tools."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
-
from typing import Any, Literal, Optional, Union
|
|
4
|
+
from typing import Any, Literal, Optional, Union, cast, overload
|
|
5
5
|
from uuid import UUID
|
|
6
6
|
|
|
7
7
|
from pydantic import Field, model_validator
|
|
8
|
-
from typing_extensions import override
|
|
8
|
+
from typing_extensions import NotRequired, TypedDict, override
|
|
9
9
|
|
|
10
|
+
from langchain_core.messages import content as types
|
|
10
11
|
from langchain_core.messages.base import BaseMessage, BaseMessageChunk, merge_content
|
|
11
|
-
from langchain_core.messages.
|
|
12
|
-
from langchain_core.messages.content_blocks import ToolCall as ToolCall
|
|
13
|
-
from langchain_core.messages.content_blocks import ToolCallChunk as ToolCallChunk
|
|
12
|
+
from langchain_core.messages.content import InvalidToolCall as InvalidToolCall
|
|
14
13
|
from langchain_core.utils._merge import merge_dicts, merge_obj
|
|
15
14
|
|
|
16
15
|
|
|
@@ -136,16 +135,35 @@ class ToolMessage(BaseMessage, ToolOutputMixin):
|
|
|
136
135
|
values["tool_call_id"] = str(tool_call_id)
|
|
137
136
|
return values
|
|
138
137
|
|
|
138
|
+
@overload
|
|
139
139
|
def __init__(
|
|
140
|
-
self,
|
|
141
|
-
|
|
142
|
-
|
|
140
|
+
self,
|
|
141
|
+
content: Union[str, list[Union[str, dict]]],
|
|
142
|
+
**kwargs: Any,
|
|
143
|
+
) -> None: ...
|
|
143
144
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
145
|
+
@overload
|
|
146
|
+
def __init__(
|
|
147
|
+
self,
|
|
148
|
+
content: Optional[Union[str, list[Union[str, dict]]]] = None,
|
|
149
|
+
content_blocks: Optional[list[types.ContentBlock]] = None,
|
|
150
|
+
**kwargs: Any,
|
|
151
|
+
) -> None: ...
|
|
152
|
+
|
|
153
|
+
def __init__(
|
|
154
|
+
self,
|
|
155
|
+
content: Optional[Union[str, list[Union[str, dict]]]] = None,
|
|
156
|
+
content_blocks: Optional[list[types.ContentBlock]] = None,
|
|
157
|
+
**kwargs: Any,
|
|
158
|
+
) -> None:
|
|
159
|
+
"""Specify ``content`` as positional arg or ``content_blocks`` for typing."""
|
|
160
|
+
if content_blocks is not None:
|
|
161
|
+
super().__init__(
|
|
162
|
+
content=cast("Union[str, list[Union[str, dict]]]", content_blocks),
|
|
163
|
+
**kwargs,
|
|
164
|
+
)
|
|
165
|
+
else:
|
|
166
|
+
super().__init__(content=content, **kwargs)
|
|
149
167
|
|
|
150
168
|
|
|
151
169
|
class ToolMessageChunk(ToolMessage, BaseMessageChunk):
|
|
@@ -180,6 +198,37 @@ class ToolMessageChunk(ToolMessage, BaseMessageChunk):
|
|
|
180
198
|
return super().__add__(other)
|
|
181
199
|
|
|
182
200
|
|
|
201
|
+
class ToolCall(TypedDict):
|
|
202
|
+
"""Represents a request to call a tool.
|
|
203
|
+
|
|
204
|
+
Example:
|
|
205
|
+
|
|
206
|
+
.. code-block:: python
|
|
207
|
+
|
|
208
|
+
{
|
|
209
|
+
"name": "foo",
|
|
210
|
+
"args": {"a": 1},
|
|
211
|
+
"id": "123"
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
This represents a request to call the tool named "foo" with arguments {"a": 1}
|
|
215
|
+
and an identifier of "123".
|
|
216
|
+
|
|
217
|
+
"""
|
|
218
|
+
|
|
219
|
+
name: str
|
|
220
|
+
"""The name of the tool to be called."""
|
|
221
|
+
args: dict[str, Any]
|
|
222
|
+
"""The arguments to the tool call."""
|
|
223
|
+
id: Optional[str]
|
|
224
|
+
"""An identifier associated with the tool call.
|
|
225
|
+
|
|
226
|
+
An identifier is needed to associate a tool call request with a tool
|
|
227
|
+
call result in events when multiple concurrent tool calls are made.
|
|
228
|
+
"""
|
|
229
|
+
type: NotRequired[Literal["tool_call"]]
|
|
230
|
+
|
|
231
|
+
|
|
183
232
|
def tool_call(
|
|
184
233
|
*,
|
|
185
234
|
name: str,
|
|
@@ -196,6 +245,38 @@ def tool_call(
|
|
|
196
245
|
return ToolCall(name=name, args=args, id=id, type="tool_call")
|
|
197
246
|
|
|
198
247
|
|
|
248
|
+
class ToolCallChunk(TypedDict):
|
|
249
|
+
"""A chunk of a tool call (e.g., as part of a stream).
|
|
250
|
+
|
|
251
|
+
When merging ToolCallChunks (e.g., via AIMessageChunk.__add__),
|
|
252
|
+
all string attributes are concatenated. Chunks are only merged if their
|
|
253
|
+
values of `index` are equal and not None.
|
|
254
|
+
|
|
255
|
+
Example:
|
|
256
|
+
|
|
257
|
+
.. code-block:: python
|
|
258
|
+
|
|
259
|
+
left_chunks = [ToolCallChunk(name="foo", args='{"a":', index=0)]
|
|
260
|
+
right_chunks = [ToolCallChunk(name=None, args='1}', index=0)]
|
|
261
|
+
|
|
262
|
+
(
|
|
263
|
+
AIMessageChunk(content="", tool_call_chunks=left_chunks)
|
|
264
|
+
+ AIMessageChunk(content="", tool_call_chunks=right_chunks)
|
|
265
|
+
).tool_call_chunks == [ToolCallChunk(name='foo', args='{"a":1}', index=0)]
|
|
266
|
+
|
|
267
|
+
"""
|
|
268
|
+
|
|
269
|
+
name: Optional[str]
|
|
270
|
+
"""The name of the tool to be called."""
|
|
271
|
+
args: Optional[str]
|
|
272
|
+
"""The arguments to the tool call."""
|
|
273
|
+
id: Optional[str]
|
|
274
|
+
"""An identifier associated with the tool call."""
|
|
275
|
+
index: Optional[int]
|
|
276
|
+
"""The index of the tool call in a sequence."""
|
|
277
|
+
type: NotRequired[Literal["tool_call_chunk"]]
|
|
278
|
+
|
|
279
|
+
|
|
199
280
|
def tool_call_chunk(
|
|
200
281
|
*,
|
|
201
282
|
name: Optional[str] = None,
|
langchain_core/messages/utils.py
CHANGED
|
@@ -31,21 +31,20 @@ from typing import (
|
|
|
31
31
|
from pydantic import Discriminator, Field, Tag
|
|
32
32
|
|
|
33
33
|
from langchain_core.exceptions import ErrorCode, create_message
|
|
34
|
-
from langchain_core.messages import convert_to_openai_data_block, is_data_content_block
|
|
35
34
|
from langchain_core.messages.ai import AIMessage, AIMessageChunk
|
|
36
35
|
from langchain_core.messages.base import BaseMessage, BaseMessageChunk
|
|
36
|
+
from langchain_core.messages.block_translators.openai import (
|
|
37
|
+
convert_to_openai_data_block,
|
|
38
|
+
)
|
|
37
39
|
from langchain_core.messages.chat import ChatMessage, ChatMessageChunk
|
|
40
|
+
from langchain_core.messages.content import (
|
|
41
|
+
is_data_content_block,
|
|
42
|
+
)
|
|
38
43
|
from langchain_core.messages.function import FunctionMessage, FunctionMessageChunk
|
|
39
44
|
from langchain_core.messages.human import HumanMessage, HumanMessageChunk
|
|
40
45
|
from langchain_core.messages.modifier import RemoveMessage
|
|
41
46
|
from langchain_core.messages.system import SystemMessage, SystemMessageChunk
|
|
42
47
|
from langchain_core.messages.tool import ToolCall, ToolMessage, ToolMessageChunk
|
|
43
|
-
from langchain_core.v1.messages import AIMessage as AIMessageV1
|
|
44
|
-
from langchain_core.v1.messages import AIMessageChunk as AIMessageChunkV1
|
|
45
|
-
from langchain_core.v1.messages import HumanMessage as HumanMessageV1
|
|
46
|
-
from langchain_core.v1.messages import MessageV1, MessageV1Types
|
|
47
|
-
from langchain_core.v1.messages import SystemMessage as SystemMessageV1
|
|
48
|
-
from langchain_core.v1.messages import ToolMessage as ToolMessageV1
|
|
49
48
|
|
|
50
49
|
if TYPE_CHECKING:
|
|
51
50
|
from langchain_text_splitters import TextSplitter
|
|
@@ -136,7 +135,7 @@ def get_buffer_string(
|
|
|
136
135
|
else:
|
|
137
136
|
msg = f"Got unsupported message type: {m}"
|
|
138
137
|
raise ValueError(msg) # noqa: TRY004
|
|
139
|
-
message = f"{role}: {m.text
|
|
138
|
+
message = f"{role}: {m.text}"
|
|
140
139
|
if isinstance(m, AIMessage) and "function_call" in m.additional_kwargs:
|
|
141
140
|
message += f"{m.additional_kwargs['function_call']}"
|
|
142
141
|
string_messages.append(message)
|
|
@@ -202,14 +201,14 @@ def message_chunk_to_message(chunk: BaseMessageChunk) -> BaseMessage:
|
|
|
202
201
|
# chunk classes always have the equivalent non-chunk class as their first parent
|
|
203
202
|
ignore_keys = ["type"]
|
|
204
203
|
if isinstance(chunk, AIMessageChunk):
|
|
205
|
-
ignore_keys.
|
|
204
|
+
ignore_keys.extend(["tool_call_chunks", "chunk_position"])
|
|
206
205
|
return chunk.__class__.__mro__[1](
|
|
207
206
|
**{k: v for k, v in chunk.__dict__.items() if k not in ignore_keys}
|
|
208
207
|
)
|
|
209
208
|
|
|
210
209
|
|
|
211
210
|
MessageLikeRepresentation = Union[
|
|
212
|
-
BaseMessage, list[str], tuple[str, str], str, dict[str, Any]
|
|
211
|
+
BaseMessage, list[str], tuple[str, str], str, dict[str, Any]
|
|
213
212
|
]
|
|
214
213
|
|
|
215
214
|
|
|
@@ -300,130 +299,6 @@ def _create_message_from_message_type(
|
|
|
300
299
|
return message
|
|
301
300
|
|
|
302
301
|
|
|
303
|
-
def _create_message_from_message_type_v1(
|
|
304
|
-
message_type: str,
|
|
305
|
-
content: str,
|
|
306
|
-
name: Optional[str] = None,
|
|
307
|
-
tool_call_id: Optional[str] = None,
|
|
308
|
-
tool_calls: Optional[list[dict[str, Any]]] = None,
|
|
309
|
-
id: Optional[str] = None,
|
|
310
|
-
**kwargs: Any,
|
|
311
|
-
) -> MessageV1:
|
|
312
|
-
"""Create a message from a message type and content string.
|
|
313
|
-
|
|
314
|
-
Args:
|
|
315
|
-
message_type: (str) the type of the message (e.g., "human", "ai", etc.).
|
|
316
|
-
content: (str) the content string.
|
|
317
|
-
name: (str) the name of the message. Default is None.
|
|
318
|
-
tool_call_id: (str) the tool call id. Default is None.
|
|
319
|
-
tool_calls: (list[dict[str, Any]]) the tool calls. Default is None.
|
|
320
|
-
id: (str) the id of the message. Default is None.
|
|
321
|
-
kwargs: (dict[str, Any]) additional keyword arguments.
|
|
322
|
-
|
|
323
|
-
Returns:
|
|
324
|
-
a message of the appropriate type.
|
|
325
|
-
|
|
326
|
-
Raises:
|
|
327
|
-
ValueError: if the message type is not one of "human", "user", "ai",
|
|
328
|
-
"assistant", "tool", "system", or "developer".
|
|
329
|
-
"""
|
|
330
|
-
if name is not None:
|
|
331
|
-
kwargs["name"] = name
|
|
332
|
-
if tool_call_id is not None:
|
|
333
|
-
kwargs["tool_call_id"] = tool_call_id
|
|
334
|
-
if kwargs and (response_metadata := kwargs.pop("response_metadata", None)):
|
|
335
|
-
kwargs["response_metadata"] = response_metadata
|
|
336
|
-
if id is not None:
|
|
337
|
-
kwargs["id"] = id
|
|
338
|
-
if tool_calls is not None:
|
|
339
|
-
kwargs["tool_calls"] = []
|
|
340
|
-
for tool_call in tool_calls:
|
|
341
|
-
# Convert OpenAI-format tool call to LangChain format.
|
|
342
|
-
if "function" in tool_call:
|
|
343
|
-
args = tool_call["function"]["arguments"]
|
|
344
|
-
if isinstance(args, str):
|
|
345
|
-
args = json.loads(args, strict=False)
|
|
346
|
-
kwargs["tool_calls"].append(
|
|
347
|
-
{
|
|
348
|
-
"name": tool_call["function"]["name"],
|
|
349
|
-
"args": args,
|
|
350
|
-
"id": tool_call["id"],
|
|
351
|
-
"type": "tool_call",
|
|
352
|
-
}
|
|
353
|
-
)
|
|
354
|
-
else:
|
|
355
|
-
kwargs["tool_calls"].append(tool_call)
|
|
356
|
-
if message_type in {"human", "user"}:
|
|
357
|
-
message: MessageV1 = HumanMessageV1(content=content, **kwargs)
|
|
358
|
-
elif message_type in {"ai", "assistant"}:
|
|
359
|
-
message = AIMessageV1(content=content, **kwargs)
|
|
360
|
-
elif message_type in {"system", "developer"}:
|
|
361
|
-
if message_type == "developer":
|
|
362
|
-
kwargs["custom_role"] = "developer"
|
|
363
|
-
message = SystemMessageV1(content=content, **kwargs)
|
|
364
|
-
elif message_type == "tool":
|
|
365
|
-
artifact = kwargs.pop("artifact", None)
|
|
366
|
-
message = ToolMessageV1(content=content, artifact=artifact, **kwargs)
|
|
367
|
-
else:
|
|
368
|
-
msg = (
|
|
369
|
-
f"Unexpected message type: '{message_type}'. Use one of 'human',"
|
|
370
|
-
f" 'user', 'ai', 'assistant', 'function', 'tool', 'system', or 'developer'."
|
|
371
|
-
)
|
|
372
|
-
msg = create_message(message=msg, error_code=ErrorCode.MESSAGE_COERCION_FAILURE)
|
|
373
|
-
raise ValueError(msg)
|
|
374
|
-
return message
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
def convert_from_v1_message(message: MessageV1) -> BaseMessage:
|
|
378
|
-
"""Compatibility layer to convert v1 messages to current messages.
|
|
379
|
-
|
|
380
|
-
Args:
|
|
381
|
-
message: MessageV1 instance to convert.
|
|
382
|
-
|
|
383
|
-
Returns:
|
|
384
|
-
BaseMessage: Converted message instance.
|
|
385
|
-
"""
|
|
386
|
-
content = cast("Union[str, list[str | dict]]", message.content)
|
|
387
|
-
if isinstance(message, AIMessageV1):
|
|
388
|
-
return AIMessage(
|
|
389
|
-
content=content,
|
|
390
|
-
id=message.id,
|
|
391
|
-
name=message.name,
|
|
392
|
-
tool_calls=message.tool_calls,
|
|
393
|
-
response_metadata=cast("dict", message.response_metadata),
|
|
394
|
-
)
|
|
395
|
-
if isinstance(message, AIMessageChunkV1):
|
|
396
|
-
return AIMessageChunk(
|
|
397
|
-
content=content,
|
|
398
|
-
id=message.id,
|
|
399
|
-
name=message.name,
|
|
400
|
-
tool_call_chunks=message.tool_call_chunks,
|
|
401
|
-
response_metadata=cast("dict", message.response_metadata),
|
|
402
|
-
)
|
|
403
|
-
if isinstance(message, HumanMessageV1):
|
|
404
|
-
return HumanMessage(
|
|
405
|
-
content=content,
|
|
406
|
-
id=message.id,
|
|
407
|
-
name=message.name,
|
|
408
|
-
)
|
|
409
|
-
if isinstance(message, SystemMessageV1):
|
|
410
|
-
return SystemMessage(
|
|
411
|
-
content=content,
|
|
412
|
-
id=message.id,
|
|
413
|
-
)
|
|
414
|
-
if isinstance(message, ToolMessageV1):
|
|
415
|
-
return ToolMessage(
|
|
416
|
-
content=content,
|
|
417
|
-
id=message.id,
|
|
418
|
-
tool_call_id=message.tool_call_id,
|
|
419
|
-
artifact=message.artifact,
|
|
420
|
-
name=message.name,
|
|
421
|
-
status=message.status,
|
|
422
|
-
)
|
|
423
|
-
message = f"Unsupported message type: {type(message)}"
|
|
424
|
-
raise NotImplementedError(message)
|
|
425
|
-
|
|
426
|
-
|
|
427
302
|
def _convert_to_message(message: MessageLikeRepresentation) -> BaseMessage:
|
|
428
303
|
"""Instantiate a message from a variety of message formats.
|
|
429
304
|
|
|
@@ -471,66 +346,6 @@ def _convert_to_message(message: MessageLikeRepresentation) -> BaseMessage:
|
|
|
471
346
|
message_ = _create_message_from_message_type(
|
|
472
347
|
msg_type, msg_content, **msg_kwargs
|
|
473
348
|
)
|
|
474
|
-
elif isinstance(message, MessageV1Types):
|
|
475
|
-
message_ = convert_from_v1_message(message)
|
|
476
|
-
else:
|
|
477
|
-
msg = f"Unsupported message type: {type(message)}"
|
|
478
|
-
msg = create_message(message=msg, error_code=ErrorCode.MESSAGE_COERCION_FAILURE)
|
|
479
|
-
raise NotImplementedError(msg)
|
|
480
|
-
|
|
481
|
-
return message_
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
def _convert_to_message_v1(message: MessageLikeRepresentation) -> MessageV1:
|
|
485
|
-
"""Instantiate a message from a variety of message formats.
|
|
486
|
-
|
|
487
|
-
The message format can be one of the following:
|
|
488
|
-
|
|
489
|
-
- BaseMessagePromptTemplate
|
|
490
|
-
- BaseMessage
|
|
491
|
-
- 2-tuple of (role string, template); e.g., ("human", "{user_input}")
|
|
492
|
-
- dict: a message dict with role and content keys
|
|
493
|
-
- string: shorthand for ("human", template); e.g., "{user_input}"
|
|
494
|
-
|
|
495
|
-
Args:
|
|
496
|
-
message: a representation of a message in one of the supported formats.
|
|
497
|
-
|
|
498
|
-
Returns:
|
|
499
|
-
an instance of a message or a message template.
|
|
500
|
-
|
|
501
|
-
Raises:
|
|
502
|
-
NotImplementedError: if the message type is not supported.
|
|
503
|
-
ValueError: if the message dict does not contain the required keys.
|
|
504
|
-
"""
|
|
505
|
-
if isinstance(message, MessageV1Types):
|
|
506
|
-
if isinstance(message, AIMessageChunkV1):
|
|
507
|
-
message_: MessageV1 = message.to_message()
|
|
508
|
-
else:
|
|
509
|
-
message_ = message
|
|
510
|
-
elif isinstance(message, str):
|
|
511
|
-
message_ = _create_message_from_message_type_v1("human", message)
|
|
512
|
-
elif isinstance(message, Sequence) and len(message) == 2:
|
|
513
|
-
# mypy doesn't realise this can't be a string given the previous branch
|
|
514
|
-
message_type_str, template = message # type: ignore[misc]
|
|
515
|
-
message_ = _create_message_from_message_type_v1(message_type_str, template)
|
|
516
|
-
elif isinstance(message, dict):
|
|
517
|
-
msg_kwargs = message.copy()
|
|
518
|
-
try:
|
|
519
|
-
try:
|
|
520
|
-
msg_type = msg_kwargs.pop("role")
|
|
521
|
-
except KeyError:
|
|
522
|
-
msg_type = msg_kwargs.pop("type")
|
|
523
|
-
# None msg content is not allowed
|
|
524
|
-
msg_content = msg_kwargs.pop("content") or ""
|
|
525
|
-
except KeyError as e:
|
|
526
|
-
msg = f"Message dict must contain 'role' and 'content' keys, got {message}"
|
|
527
|
-
msg = create_message(
|
|
528
|
-
message=msg, error_code=ErrorCode.MESSAGE_COERCION_FAILURE
|
|
529
|
-
)
|
|
530
|
-
raise ValueError(msg) from e
|
|
531
|
-
message_ = _create_message_from_message_type_v1(
|
|
532
|
-
msg_type, msg_content, **msg_kwargs
|
|
533
|
-
)
|
|
534
349
|
else:
|
|
535
350
|
msg = f"Unsupported message type: {type(message)}"
|
|
536
351
|
msg = create_message(message=msg, error_code=ErrorCode.MESSAGE_COERCION_FAILURE)
|
|
@@ -558,25 +373,6 @@ def convert_to_messages(
|
|
|
558
373
|
return [_convert_to_message(m) for m in messages]
|
|
559
374
|
|
|
560
375
|
|
|
561
|
-
def convert_to_messages_v1(
|
|
562
|
-
messages: Union[Iterable[MessageLikeRepresentation], PromptValue],
|
|
563
|
-
) -> list[MessageV1]:
|
|
564
|
-
"""Convert a sequence of messages to a list of messages.
|
|
565
|
-
|
|
566
|
-
Args:
|
|
567
|
-
messages: Sequence of messages to convert.
|
|
568
|
-
|
|
569
|
-
Returns:
|
|
570
|
-
list of messages (BaseMessages).
|
|
571
|
-
"""
|
|
572
|
-
# Import here to avoid circular imports
|
|
573
|
-
from langchain_core.prompt_values import PromptValue
|
|
574
|
-
|
|
575
|
-
if isinstance(messages, PromptValue):
|
|
576
|
-
return messages.to_messages(message_version="v1")
|
|
577
|
-
return [_convert_to_message_v1(m) for m in messages]
|
|
578
|
-
|
|
579
|
-
|
|
580
376
|
def _runnable_support(func: Callable) -> Callable:
|
|
581
377
|
@overload
|
|
582
378
|
def wrapped(
|
|
@@ -865,22 +661,23 @@ def trim_messages(
|
|
|
865
661
|
properties:
|
|
866
662
|
|
|
867
663
|
1. The resulting chat history should be valid. Most chat models expect that chat
|
|
868
|
-
history starts with either (1) a
|
|
869
|
-
by a
|
|
870
|
-
In addition, generally a
|
|
664
|
+
history starts with either (1) a ``HumanMessage`` or (2) a ``SystemMessage`` followed
|
|
665
|
+
by a ``HumanMessage``. To achieve this, set ``start_on="human"``.
|
|
666
|
+
In addition, generally a ``ToolMessage`` can only appear after an ``AIMessage``
|
|
871
667
|
that involved a tool call.
|
|
872
668
|
Please see the following link for more information about messages:
|
|
873
669
|
https://python.langchain.com/docs/concepts/#messages
|
|
874
670
|
2. It includes recent messages and drops old messages in the chat history.
|
|
875
|
-
To achieve this set the
|
|
876
|
-
3. Usually, the new chat history should include the
|
|
877
|
-
was present in the original chat history since the
|
|
878
|
-
special instructions to the chat model. The
|
|
671
|
+
To achieve this set the ``strategy="last"``.
|
|
672
|
+
3. Usually, the new chat history should include the ``SystemMessage`` if it
|
|
673
|
+
was present in the original chat history since the ``SystemMessage`` includes
|
|
674
|
+
special instructions to the chat model. The ``SystemMessage`` is almost always
|
|
879
675
|
the first message in the history if present. To achieve this set the
|
|
880
|
-
|
|
676
|
+
``include_system=True``.
|
|
881
677
|
|
|
882
|
-
|
|
883
|
-
|
|
678
|
+
.. note::
|
|
679
|
+
The examples below show how to configure ``trim_messages`` to achieve a behavior
|
|
680
|
+
consistent with the above properties.
|
|
884
681
|
|
|
885
682
|
Args:
|
|
886
683
|
messages: Sequence of Message-like objects to trim.
|
|
@@ -1216,11 +1013,10 @@ def convert_to_openai_messages(
|
|
|
1216
1013
|
|
|
1217
1014
|
oai_messages: list = []
|
|
1218
1015
|
|
|
1219
|
-
if is_single := isinstance(messages, (BaseMessage, dict, str
|
|
1016
|
+
if is_single := isinstance(messages, (BaseMessage, dict, str)):
|
|
1220
1017
|
messages = [messages]
|
|
1221
1018
|
|
|
1222
|
-
|
|
1223
|
-
messages = convert_to_messages(messages) # type: ignore[arg-type]
|
|
1019
|
+
messages = convert_to_messages(messages)
|
|
1224
1020
|
|
|
1225
1021
|
for i, message in enumerate(messages):
|
|
1226
1022
|
oai_msg: dict = {"role": _get_message_openai_role(message)}
|
|
@@ -1707,11 +1503,15 @@ def _msg_to_chunk(message: BaseMessage) -> BaseMessageChunk:
|
|
|
1707
1503
|
def _chunk_to_msg(chunk: BaseMessageChunk) -> BaseMessage:
|
|
1708
1504
|
if chunk.__class__ in _CHUNK_MSG_MAP:
|
|
1709
1505
|
return _CHUNK_MSG_MAP[chunk.__class__](
|
|
1710
|
-
**chunk.model_dump(exclude={"type", "tool_call_chunks"})
|
|
1506
|
+
**chunk.model_dump(exclude={"type", "tool_call_chunks", "chunk_position"})
|
|
1711
1507
|
)
|
|
1712
1508
|
for chunk_cls, msg_cls in _CHUNK_MSG_MAP.items():
|
|
1713
1509
|
if isinstance(chunk, chunk_cls):
|
|
1714
|
-
return msg_cls(
|
|
1510
|
+
return msg_cls(
|
|
1511
|
+
**chunk.model_dump(
|
|
1512
|
+
exclude={"type", "tool_call_chunks", "chunk_position"}
|
|
1513
|
+
)
|
|
1514
|
+
)
|
|
1715
1515
|
|
|
1716
1516
|
msg = (
|
|
1717
1517
|
f"Unrecognized message chunk class {chunk.__class__}. Supported classes are "
|
|
@@ -1790,26 +1590,26 @@ def count_tokens_approximately(
|
|
|
1790
1590
|
chars_per_token: Number of characters per token to use for the approximation.
|
|
1791
1591
|
Default is 4 (one token corresponds to ~4 chars for common English text).
|
|
1792
1592
|
You can also specify float values for more fine-grained control.
|
|
1793
|
-
See more here
|
|
1593
|
+
`See more here. <https://platform.openai.com/tokenizer>`__
|
|
1794
1594
|
extra_tokens_per_message: Number of extra tokens to add per message.
|
|
1795
1595
|
Default is 3 (special tokens, including beginning/end of message).
|
|
1796
1596
|
You can also specify float values for more fine-grained control.
|
|
1797
|
-
See more here
|
|
1798
|
-
https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb
|
|
1597
|
+
`See more here. <https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb>`__
|
|
1799
1598
|
count_name: Whether to include message names in the count.
|
|
1800
1599
|
Enabled by default.
|
|
1801
1600
|
|
|
1802
1601
|
Returns:
|
|
1803
1602
|
Approximate number of tokens in the messages.
|
|
1804
1603
|
|
|
1805
|
-
|
|
1806
|
-
This is a simple approximation that may not match the exact token count
|
|
1807
|
-
|
|
1604
|
+
.. note::
|
|
1605
|
+
This is a simple approximation that may not match the exact token count used by
|
|
1606
|
+
specific models. For accurate counts, use model-specific tokenizers.
|
|
1808
1607
|
|
|
1809
1608
|
Warning:
|
|
1810
1609
|
This function does not currently support counting image tokens.
|
|
1811
1610
|
|
|
1812
1611
|
.. versionadded:: 0.3.46
|
|
1612
|
+
|
|
1813
1613
|
"""
|
|
1814
1614
|
token_count = 0.0
|
|
1815
1615
|
for message in convert_to_messages(messages):
|