coco-framework 0.1.0__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.
- coco/__init__.py +3 -0
- coco/agents/__init__.py +9 -0
- coco/agents/_subagent_transformer.py +307 -0
- coco/agents/factory.py +2164 -0
- coco/agents/middleware/__init__.py +100 -0
- coco/agents/middleware/_execution.py +385 -0
- coco/agents/middleware/_redaction.py +454 -0
- coco/agents/middleware/_retry.py +134 -0
- coco/agents/middleware/_trace_policy.py +61 -0
- coco/agents/middleware/context_editing.py +306 -0
- coco/agents/middleware/file_search.py +433 -0
- coco/agents/middleware/human_in_the_loop.py +680 -0
- coco/agents/middleware/internal_call_transformer.py +108 -0
- coco/agents/middleware/model_call_limit.py +267 -0
- coco/agents/middleware/model_fallback.py +417 -0
- coco/agents/middleware/model_retry.py +327 -0
- coco/agents/middleware/pii.py +878 -0
- coco/agents/middleware/provider_tool_search.py +307 -0
- coco/agents/middleware/shell_tool.py +949 -0
- coco/agents/middleware/summarization.py +910 -0
- coco/agents/middleware/todo.py +357 -0
- coco/agents/middleware/tool_call_limit.py +495 -0
- coco/agents/middleware/tool_emulator.py +251 -0
- coco/agents/middleware/tool_error.py +209 -0
- coco/agents/middleware/tool_retry.py +421 -0
- coco/agents/middleware/tool_selection.py +515 -0
- coco/agents/middleware/types.py +2201 -0
- coco/agents/structured_output.py +472 -0
- coco/chat_models/__init__.py +7 -0
- coco/chat_models/base.py +1082 -0
- coco/embeddings/__init__.py +18 -0
- coco/embeddings/base.py +279 -0
- coco/mcp/__init__.py +36 -0
- coco/mcp/adapter.py +245 -0
- coco/mcp/elicitation.py +394 -0
- coco/mcp/tools.py +307 -0
- coco/messages/__init__.py +73 -0
- coco/py.typed +0 -0
- coco/rate_limiters/__init__.py +13 -0
- coco/tools/__init__.py +22 -0
- coco/tools/tool_node.py +20 -0
- coco_anthropic/__init__.py +15 -0
- coco_anthropic/_client_utils.py +81 -0
- coco_anthropic/_compat.py +228 -0
- coco_anthropic/_sdk_compat.py +124 -0
- coco_anthropic/_version.py +3 -0
- coco_anthropic/chat_models.py +3349 -0
- coco_anthropic/data/__init__.py +1 -0
- coco_anthropic/data/_profiles.py +491 -0
- coco_anthropic/data/profile_augmentations.toml +69 -0
- coco_anthropic/experimental.py +140 -0
- coco_anthropic/llms.py +428 -0
- coco_anthropic/middleware/__init__.py +25 -0
- coco_anthropic/middleware/anthropic_tools.py +1197 -0
- coco_anthropic/middleware/bash.py +84 -0
- coco_anthropic/middleware/file_search.py +360 -0
- coco_anthropic/middleware/prompt_caching.py +276 -0
- coco_anthropic/output_parsers.py +93 -0
- coco_anthropic/py.typed +0 -0
- coco_core/__init__.py +20 -0
- coco_core/_api/__init__.py +87 -0
- coco_core/_api/beta_decorator.py +266 -0
- coco_core/_api/deprecation.py +637 -0
- coco_core/_api/internal.py +23 -0
- coco_core/_api/path.py +50 -0
- coco_core/_import_utils.py +41 -0
- coco_core/_protocol.py +625 -0
- coco_core/_security/__init__.py +36 -0
- coco_core/_security/_exceptions.py +9 -0
- coco_core/_security/_policy.py +313 -0
- coco_core/_security/_ssrf_protection.py +155 -0
- coco_core/_security/_transport.py +254 -0
- coco_core/agents.py +258 -0
- coco_core/caches.py +272 -0
- coco_core/callbacks/__init__.py +132 -0
- coco_core/callbacks/base.py +1229 -0
- coco_core/callbacks/file.py +266 -0
- coco_core/callbacks/manager.py +2826 -0
- coco_core/callbacks/stdout.py +123 -0
- coco_core/callbacks/streaming_stdout.py +154 -0
- coco_core/callbacks/usage.py +124 -0
- coco_core/chat_history.py +267 -0
- coco_core/chat_loaders.py +26 -0
- coco_core/chat_sessions.py +19 -0
- coco_core/cross_encoders.py +18 -0
- coco_core/document_loaders/__init__.py +36 -0
- coco_core/document_loaders/base.py +155 -0
- coco_core/document_loaders/blob_loaders.py +38 -0
- coco_core/documents/__init__.py +55 -0
- coco_core/documents/base.py +347 -0
- coco_core/documents/compressor.py +74 -0
- coco_core/documents/transformers.py +79 -0
- coco_core/embeddings/__init__.py +31 -0
- coco_core/embeddings/embeddings.py +78 -0
- coco_core/embeddings/fake.py +129 -0
- coco_core/env.py +22 -0
- coco_core/example_selectors/__init__.py +47 -0
- coco_core/example_selectors/base.py +60 -0
- coco_core/example_selectors/length_based.py +131 -0
- coco_core/example_selectors/semantic_similarity.py +366 -0
- coco_core/exceptions.py +166 -0
- coco_core/globals.py +72 -0
- coco_core/indexing/__init__.py +53 -0
- coco_core/indexing/api.py +954 -0
- coco_core/indexing/base.py +661 -0
- coco_core/indexing/in_memory.py +104 -0
- coco_core/language_models/__init__.py +116 -0
- coco_core/language_models/_compat_bridge.py +844 -0
- coco_core/language_models/_utils.py +343 -0
- coco_core/language_models/base.py +500 -0
- coco_core/language_models/chat_model_stream.py +1525 -0
- coco_core/language_models/chat_models.py +2748 -0
- coco_core/language_models/fake.py +137 -0
- coco_core/language_models/fake_chat_models.py +396 -0
- coco_core/language_models/llms.py +1569 -0
- coco_core/language_models/model_profile.py +183 -0
- coco_core/load/__init__.py +44 -0
- coco_core/load/_validation.py +191 -0
- coco_core/load/dump.py +120 -0
- coco_core/load/load.py +828 -0
- coco_core/load/mapping.py +1097 -0
- coco_core/load/serializable.py +406 -0
- coco_core/messages/__init__.py +198 -0
- coco_core/messages/ai.py +854 -0
- coco_core/messages/base.py +518 -0
- coco_core/messages/block_translators/__init__.py +112 -0
- coco_core/messages/block_translators/anthropic.py +534 -0
- coco_core/messages/block_translators/bedrock.py +108 -0
- coco_core/messages/block_translators/bedrock_converse.py +329 -0
- coco_core/messages/block_translators/coco_v0.py +303 -0
- coco_core/messages/block_translators/google_genai.py +610 -0
- coco_core/messages/block_translators/google_vertexai.py +21 -0
- coco_core/messages/block_translators/groq.py +157 -0
- coco_core/messages/block_translators/openai.py +1089 -0
- coco_core/messages/chat.py +64 -0
- coco_core/messages/content.py +1488 -0
- coco_core/messages/function.py +62 -0
- coco_core/messages/human.py +70 -0
- coco_core/messages/modifier.py +33 -0
- coco_core/messages/system.py +70 -0
- coco_core/messages/tool.py +418 -0
- coco_core/messages/utils.py +2442 -0
- coco_core/output_parsers/__init__.py +101 -0
- coco_core/output_parsers/base.py +361 -0
- coco_core/output_parsers/format_instructions.py +16 -0
- coco_core/output_parsers/json.py +139 -0
- coco_core/output_parsers/list.py +249 -0
- coco_core/output_parsers/openai_functions.py +324 -0
- coco_core/output_parsers/openai_tools.py +383 -0
- coco_core/output_parsers/pydantic.py +141 -0
- coco_core/output_parsers/string.py +63 -0
- coco_core/output_parsers/transform.py +175 -0
- coco_core/output_parsers/xml.py +301 -0
- coco_core/outputs/__init__.py +66 -0
- coco_core/outputs/chat_generation.py +147 -0
- coco_core/outputs/chat_result.py +39 -0
- coco_core/outputs/generation.py +80 -0
- coco_core/outputs/llm_result.py +112 -0
- coco_core/outputs/run_info.py +22 -0
- coco_core/prompt_values.py +161 -0
- coco_core/prompts/__init__.py +101 -0
- coco_core/prompts/base.py +508 -0
- coco_core/prompts/chat.py +1495 -0
- coco_core/prompts/dict.py +177 -0
- coco_core/prompts/few_shot.py +483 -0
- coco_core/prompts/few_shot_with_templates.py +239 -0
- coco_core/prompts/image.py +177 -0
- coco_core/prompts/loading.py +296 -0
- coco_core/prompts/message.py +97 -0
- coco_core/prompts/prompt.py +312 -0
- coco_core/prompts/string.py +399 -0
- coco_core/prompts/structured.py +214 -0
- coco_core/py.typed +0 -0
- coco_core/rate_limiters.py +254 -0
- coco_core/retrievers.py +328 -0
- coco_core/runnables/__init__.py +136 -0
- coco_core/runnables/base.py +6725 -0
- coco_core/runnables/branch.py +457 -0
- coco_core/runnables/config.py +712 -0
- coco_core/runnables/configurable.py +710 -0
- coco_core/runnables/fallbacks.py +663 -0
- coco_core/runnables/graph.py +741 -0
- coco_core/runnables/graph_ascii.py +366 -0
- coco_core/runnables/graph_mermaid.py +503 -0
- coco_core/runnables/graph_png.py +215 -0
- coco_core/runnables/history.py +631 -0
- coco_core/runnables/passthrough.py +849 -0
- coco_core/runnables/retry.py +379 -0
- coco_core/runnables/router.py +239 -0
- coco_core/runnables/schema.py +188 -0
- coco_core/runnables/utils.py +798 -0
- coco_core/stores.py +291 -0
- coco_core/structured_query.py +203 -0
- coco_core/sys_info.py +154 -0
- coco_core/tools/__init__.py +95 -0
- coco_core/tools/base.py +1948 -0
- coco_core/tools/convert.py +497 -0
- coco_core/tools/render.py +67 -0
- coco_core/tools/retriever.py +94 -0
- coco_core/tools/simple.py +208 -0
- coco_core/tools/structured.py +322 -0
- coco_core/tracers/__init__.py +47 -0
- coco_core/tracers/_compat.py +95 -0
- coco_core/tracers/_context.py +337 -0
- coco_core/tracers/_streaming.py +53 -0
- coco_core/tracers/base.py +955 -0
- coco_core/tracers/coco.py +279 -0
- coco_core/tracers/context.py +205 -0
- coco_core/tracers/core.py +766 -0
- coco_core/tracers/event_stream.py +1101 -0
- coco_core/tracers/log_stream.py +765 -0
- coco_core/tracers/memory_stream.py +200 -0
- coco_core/tracers/root_listeners.py +130 -0
- coco_core/tracers/run_collector.py +40 -0
- coco_core/tracers/schemas.py +14 -0
- coco_core/tracers/stdout.py +205 -0
- coco_core/utils/__init__.py +111 -0
- coco_core/utils/_gateway.py +274 -0
- coco_core/utils/_merge.py +218 -0
- coco_core/utils/aiter.py +357 -0
- coco_core/utils/env.py +86 -0
- coco_core/utils/formatting.py +80 -0
- coco_core/utils/function_calling.py +869 -0
- coco_core/utils/html.py +132 -0
- coco_core/utils/image.py +15 -0
- coco_core/utils/input.py +82 -0
- coco_core/utils/interactive_env.py +12 -0
- coco_core/utils/iter.py +229 -0
- coco_core/utils/json.py +230 -0
- coco_core/utils/json_schema.py +273 -0
- coco_core/utils/mustache.py +706 -0
- coco_core/utils/pydantic.py +631 -0
- coco_core/utils/strings.py +69 -0
- coco_core/utils/usage.py +61 -0
- coco_core/utils/utils.py +528 -0
- coco_core/utils/uuid.py +57 -0
- coco_core/vectorstores/__init__.py +53 -0
- coco_core/vectorstores/base.py +1111 -0
- coco_core/vectorstores/in_memory.py +546 -0
- coco_core/vectorstores/utils.py +163 -0
- coco_core/version.py +3 -0
- coco_framework-0.1.0.dist-info/METADATA +184 -0
- coco_framework-0.1.0.dist-info/RECORD +400 -0
- coco_framework-0.1.0.dist-info/WHEEL +4 -0
- coco_framework-0.1.0.dist-info/licenses/LICENSE +24 -0
- coco_framework-0.1.0.dist-info/licenses/NOTICE +15 -0
- coco_graph/_internal/__init__.py +4 -0
- coco_graph/_internal/_cache.py +31 -0
- coco_graph/_internal/_config.py +474 -0
- coco_graph/_internal/_constants.py +140 -0
- coco_graph/_internal/_fields.py +213 -0
- coco_graph/_internal/_future.py +220 -0
- coco_graph/_internal/_pydantic.py +275 -0
- coco_graph/_internal/_queue.py +124 -0
- coco_graph/_internal/_replay.py +90 -0
- coco_graph/_internal/_retry.py +29 -0
- coco_graph/_internal/_runnable.py +969 -0
- coco_graph/_internal/_scratchpad.py +19 -0
- coco_graph/_internal/_serde.py +253 -0
- coco_graph/_internal/_timeout.py +25 -0
- coco_graph/_internal/_typing.py +54 -0
- coco_graph/cache/base/__init__.py +48 -0
- coco_graph/cache/base/py.typed +0 -0
- coco_graph/cache/memory/__init__.py +73 -0
- coco_graph/cache/redis/__init__.py +144 -0
- coco_graph/cache/sqlite/__init__.py +120 -0
- coco_graph/callbacks.py +394 -0
- coco_graph/channels/__init__.py +29 -0
- coco_graph/channels/any_value.py +72 -0
- coco_graph/channels/base.py +121 -0
- coco_graph/channels/binop.py +155 -0
- coco_graph/channels/delta.py +202 -0
- coco_graph/channels/ephemeral_value.py +79 -0
- coco_graph/channels/last_value.py +151 -0
- coco_graph/channels/named_barrier_value.py +167 -0
- coco_graph/channels/topic.py +94 -0
- coco_graph/channels/untracked_value.py +73 -0
- coco_graph/checkpoint/base/__init__.py +861 -0
- coco_graph/checkpoint/base/id.py +109 -0
- coco_graph/checkpoint/base/py.typed +0 -0
- coco_graph/checkpoint/memory/__init__.py +698 -0
- coco_graph/checkpoint/memory/py.typed +0 -0
- coco_graph/checkpoint/postgres/__init__.py +602 -0
- coco_graph/checkpoint/postgres/_ainternal.py +23 -0
- coco_graph/checkpoint/postgres/_internal.py +21 -0
- coco_graph/checkpoint/postgres/aio.py +691 -0
- coco_graph/checkpoint/postgres/base.py +666 -0
- coco_graph/checkpoint/postgres/py.typed +0 -0
- coco_graph/checkpoint/postgres/shallow.py +967 -0
- coco_graph/checkpoint/serde/__init__.py +0 -0
- coco_graph/checkpoint/serde/_msgpack.py +97 -0
- coco_graph/checkpoint/serde/base.py +64 -0
- coco_graph/checkpoint/serde/encrypted.py +80 -0
- coco_graph/checkpoint/serde/event_hooks.py +52 -0
- coco_graph/checkpoint/serde/jsonplus.py +883 -0
- coco_graph/checkpoint/serde/py.typed +0 -0
- coco_graph/checkpoint/serde/types.py +68 -0
- coco_graph/checkpoint/sqlite/__init__.py +645 -0
- coco_graph/checkpoint/sqlite/_delta.py +172 -0
- coco_graph/checkpoint/sqlite/aio.py +738 -0
- coco_graph/checkpoint/sqlite/py.typed +0 -0
- coco_graph/checkpoint/sqlite/utils.py +116 -0
- coco_graph/config.py +196 -0
- coco_graph/constants.py +64 -0
- coco_graph/errors.py +241 -0
- coco_graph/func/__init__.py +620 -0
- coco_graph/graph/__init__.py +12 -0
- coco_graph/graph/_branch.py +225 -0
- coco_graph/graph/_node.py +103 -0
- coco_graph/graph/message.py +437 -0
- coco_graph/graph/state.py +1978 -0
- coco_graph/graph/ui.py +227 -0
- coco_graph/managed/__init__.py +3 -0
- coco_graph/managed/base.py +31 -0
- coco_graph/managed/is_last_step.py +24 -0
- coco_graph/prebuilt/__init__.py +23 -0
- coco_graph/prebuilt/_tool_call_stream.py +117 -0
- coco_graph/prebuilt/_tool_call_transformer.py +165 -0
- coco_graph/prebuilt/chat_agent_executor.py +1015 -0
- coco_graph/prebuilt/interrupt.py +105 -0
- coco_graph/prebuilt/py.typed +0 -0
- coco_graph/prebuilt/tool_node.py +2030 -0
- coco_graph/prebuilt/tool_validator.py +221 -0
- coco_graph/pregel/__init__.py +3 -0
- coco_graph/pregel/_algo.py +1460 -0
- coco_graph/pregel/_call.py +298 -0
- coco_graph/pregel/_checkpoint.py +331 -0
- coco_graph/pregel/_config.py +0 -0
- coco_graph/pregel/_draw.py +294 -0
- coco_graph/pregel/_executor.py +223 -0
- coco_graph/pregel/_io.py +174 -0
- coco_graph/pregel/_log.py +3 -0
- coco_graph/pregel/_loop.py +1988 -0
- coco_graph/pregel/_messages.py +461 -0
- coco_graph/pregel/_read.py +312 -0
- coco_graph/pregel/_retry.py +854 -0
- coco_graph/pregel/_runner.py +941 -0
- coco_graph/pregel/_tools.py +268 -0
- coco_graph/pregel/_utils.py +221 -0
- coco_graph/pregel/_validate.py +120 -0
- coco_graph/pregel/_write.py +192 -0
- coco_graph/pregel/debug.py +302 -0
- coco_graph/pregel/main.py +4364 -0
- coco_graph/pregel/protocol.py +288 -0
- coco_graph/pregel/types.py +38 -0
- coco_graph/py.typed +0 -0
- coco_graph/runtime.py +350 -0
- coco_graph/store/base/__init__.py +1322 -0
- coco_graph/store/base/batch.py +371 -0
- coco_graph/store/base/embed.py +436 -0
- coco_graph/store/base/py.typed +0 -0
- coco_graph/store/memory/__init__.py +592 -0
- coco_graph/store/memory/py.typed +0 -0
- coco_graph/store/postgres/__init__.py +4 -0
- coco_graph/store/postgres/aio.py +592 -0
- coco_graph/store/postgres/base.py +1480 -0
- coco_graph/store/postgres/py.typed +0 -0
- coco_graph/store/sqlite/__init__.py +4 -0
- coco_graph/store/sqlite/aio.py +632 -0
- coco_graph/store/sqlite/base.py +1514 -0
- coco_graph/stream/__init__.py +45 -0
- coco_graph/stream/_convert.py +32 -0
- coco_graph/stream/_mux.py +523 -0
- coco_graph/stream/_types.py +330 -0
- coco_graph/stream/run_stream.py +735 -0
- coco_graph/stream/stream_channel.py +341 -0
- coco_graph/stream/transformers.py +1039 -0
- coco_graph/types.py +1079 -0
- coco_graph/typing.py +48 -0
- coco_graph/utils/__init__.py +1 -0
- coco_graph/utils/config.py +4 -0
- coco_graph/utils/runnable.py +3 -0
- coco_graph/version.py +13 -0
- coco_graph/warnings.py +69 -0
- coco_openai/__init__.py +20 -0
- coco_openai/_compat.py +45 -0
- coco_openai/_version.py +3 -0
- coco_openai/chat_models/__init__.py +6 -0
- coco_openai/chat_models/_client_utils.py +684 -0
- coco_openai/chat_models/_compat.py +525 -0
- coco_openai/chat_models/azure.py +1227 -0
- coco_openai/chat_models/base.py +5654 -0
- coco_openai/chat_models/codex.py +557 -0
- coco_openai/chatgpt_oauth.py +1063 -0
- coco_openai/data/__init__.py +1 -0
- coco_openai/data/_profiles.py +1655 -0
- coco_openai/data/profile_augmentations.toml +127 -0
- coco_openai/embeddings/__init__.py +6 -0
- coco_openai/embeddings/azure.py +240 -0
- coco_openai/embeddings/base.py +820 -0
- coco_openai/llms/__init__.py +6 -0
- coco_openai/llms/azure.py +249 -0
- coco_openai/llms/base.py +919 -0
- coco_openai/middleware/__init__.py +11 -0
- coco_openai/middleware/openai_moderation.py +484 -0
- coco_openai/output_parsers/__init__.py +9 -0
- coco_openai/output_parsers/tools.py +9 -0
- coco_openai/py.typed +0 -0
- coco_openai/tools/__init__.py +5 -0
- coco_openai/tools/custom_tool.py +111 -0
|
@@ -0,0 +1,631 @@
|
|
|
1
|
+
"""`Runnable` that manages chat message history for another `Runnable`."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import inspect
|
|
6
|
+
from collections.abc import Callable, Sequence
|
|
7
|
+
from types import GenericAlias
|
|
8
|
+
from typing import (
|
|
9
|
+
TYPE_CHECKING,
|
|
10
|
+
Any,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from pydantic import BaseModel
|
|
14
|
+
from typing_extensions import override
|
|
15
|
+
|
|
16
|
+
from coco_core._api.deprecation import warn_deprecated
|
|
17
|
+
from coco_core.chat_history import BaseChatMessageHistory
|
|
18
|
+
from coco_core.load.load import load
|
|
19
|
+
from coco_core.messages import AIMessage, BaseMessage, HumanMessage
|
|
20
|
+
from coco_core.runnables.base import Runnable, RunnableBindingBase, RunnableLambda
|
|
21
|
+
from coco_core.runnables.passthrough import RunnablePassthrough
|
|
22
|
+
from coco_core.runnables.utils import (
|
|
23
|
+
ConfigurableFieldSpec,
|
|
24
|
+
Output,
|
|
25
|
+
get_unique_config_specs,
|
|
26
|
+
)
|
|
27
|
+
from coco_core.utils.pydantic import create_model_v2
|
|
28
|
+
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
from coco_core.language_models.base import LanguageModelLike
|
|
31
|
+
from coco_core.runnables.config import RunnableConfig
|
|
32
|
+
from coco_core.tracers.schemas import Run
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
MessagesOrDictWithMessages = Sequence["BaseMessage"] | dict[str, Any]
|
|
36
|
+
GetSessionHistoryCallable = Callable[..., BaseChatMessageHistory]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class RunnableWithMessageHistory(RunnableBindingBase[Any, Any]): # type: ignore[no-redef]
|
|
40
|
+
"""`Runnable` that manages chat message history for another `Runnable`.
|
|
41
|
+
|
|
42
|
+
A chat message history is a sequence of messages that represent a conversation.
|
|
43
|
+
|
|
44
|
+
`RunnableWithMessageHistory` wraps another `Runnable` and manages the chat message
|
|
45
|
+
history for it; it is responsible for reading and updating the chat message
|
|
46
|
+
history.
|
|
47
|
+
|
|
48
|
+
The formats supported for the inputs and outputs of the wrapped `Runnable`
|
|
49
|
+
are described below.
|
|
50
|
+
|
|
51
|
+
`RunnableWithMessageHistory` must always be called with a config that contains
|
|
52
|
+
the appropriate parameters for the chat message history factory.
|
|
53
|
+
|
|
54
|
+
By default, the `Runnable` is expected to take a single configuration parameter
|
|
55
|
+
called `session_id` which is a string. This parameter is used to create a new
|
|
56
|
+
or look up an existing chat message history that matches the given `session_id`.
|
|
57
|
+
|
|
58
|
+
In this case, the invocation would look like this:
|
|
59
|
+
|
|
60
|
+
`with_history.invoke(..., config={"configurable": {"session_id": "bar"}})`
|
|
61
|
+
; e.g., `{"configurable": {"session_id": "<SESSION_ID>"}}`.
|
|
62
|
+
|
|
63
|
+
The configuration can be customized by passing in a list of
|
|
64
|
+
`ConfigurableFieldSpec` objects to the `history_factory_config` parameter (see
|
|
65
|
+
example below).
|
|
66
|
+
|
|
67
|
+
In the examples, we will use a chat message history with an in-memory
|
|
68
|
+
implementation to make it easy to experiment and see the results.
|
|
69
|
+
|
|
70
|
+
For production use cases, you will want to use a persistent implementation
|
|
71
|
+
of chat message history, such as `RedisChatMessageHistory`.
|
|
72
|
+
|
|
73
|
+
Example: Chat message history with an in-memory implementation for testing.
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from operator import itemgetter
|
|
77
|
+
|
|
78
|
+
from coco_openai.chat_models import ChatOpenAI
|
|
79
|
+
|
|
80
|
+
from coco_core.chat_history import BaseChatMessageHistory
|
|
81
|
+
from coco_core.documents import Document
|
|
82
|
+
from coco_core.messages import BaseMessage, AIMessage
|
|
83
|
+
from coco_core.prompts import ChatPromptTemplate, MessagesPlaceholder
|
|
84
|
+
from pydantic import BaseModel, Field
|
|
85
|
+
from coco_core.runnables import (
|
|
86
|
+
RunnableLambda,
|
|
87
|
+
ConfigurableFieldSpec,
|
|
88
|
+
RunnablePassthrough,
|
|
89
|
+
)
|
|
90
|
+
from coco_core.runnables.history import RunnableWithMessageHistory
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class InMemoryHistory(BaseChatMessageHistory, BaseModel):
|
|
94
|
+
\"\"\"In memory implementation of chat message history.\"\"\"
|
|
95
|
+
|
|
96
|
+
messages: list[BaseMessage] = Field(default_factory=list)
|
|
97
|
+
|
|
98
|
+
def add_messages(self, messages: list[BaseMessage]) -> None:
|
|
99
|
+
\"\"\"Add a list of messages to the store\"\"\"
|
|
100
|
+
self.messages.extend(messages)
|
|
101
|
+
|
|
102
|
+
def clear(self) -> None:
|
|
103
|
+
self.messages = []
|
|
104
|
+
|
|
105
|
+
# Here we use a global variable to store the chat message history.
|
|
106
|
+
# This will make it easier to inspect it to see the underlying results.
|
|
107
|
+
store = {}
|
|
108
|
+
|
|
109
|
+
def get_by_session_id(session_id: str) -> BaseChatMessageHistory:
|
|
110
|
+
if session_id not in store:
|
|
111
|
+
store[session_id] = InMemoryHistory()
|
|
112
|
+
return store[session_id]
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
history = get_by_session_id("1")
|
|
116
|
+
history.add_message(AIMessage(content="hello"))
|
|
117
|
+
print(store) # noqa: T201
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Example where the wrapped `Runnable` takes a dictionary input:
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from typing import Optional
|
|
125
|
+
|
|
126
|
+
from coco_anthropic import ChatAnthropic
|
|
127
|
+
from coco_core.prompts import ChatPromptTemplate, MessagesPlaceholder
|
|
128
|
+
from coco_core.runnables.history import RunnableWithMessageHistory
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
prompt = ChatPromptTemplate.from_messages(
|
|
132
|
+
[
|
|
133
|
+
("system", "You're an assistant who's good at {ability}"),
|
|
134
|
+
MessagesPlaceholder(variable_name="history"),
|
|
135
|
+
("human", "{question}"),
|
|
136
|
+
]
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
chain = prompt | ChatAnthropic(model="claude-sonnet-5")
|
|
140
|
+
|
|
141
|
+
chain_with_history = RunnableWithMessageHistory(
|
|
142
|
+
chain,
|
|
143
|
+
# Uses the get_by_session_id function defined in the example
|
|
144
|
+
# above.
|
|
145
|
+
get_by_session_id,
|
|
146
|
+
input_messages_key="question",
|
|
147
|
+
history_messages_key="history",
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
print(
|
|
151
|
+
chain_with_history.invoke( # noqa: T201
|
|
152
|
+
{"ability": "math", "question": "What does cosine mean?"},
|
|
153
|
+
config={"configurable": {"session_id": "foo"}},
|
|
154
|
+
)
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# Uses the store defined in the example above.
|
|
158
|
+
print(store) # noqa: T201
|
|
159
|
+
|
|
160
|
+
print(
|
|
161
|
+
chain_with_history.invoke( # noqa: T201
|
|
162
|
+
{"ability": "math", "question": "What's its inverse"},
|
|
163
|
+
config={"configurable": {"session_id": "foo"}},
|
|
164
|
+
)
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
print(store) # noqa: T201
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Example where the session factory takes two keys (`user_id` and `conversation_id`):
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
store = {}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def get_session_history(
|
|
177
|
+
user_id: str, conversation_id: str
|
|
178
|
+
) -> BaseChatMessageHistory:
|
|
179
|
+
if (user_id, conversation_id) not in store:
|
|
180
|
+
store[(user_id, conversation_id)] = InMemoryHistory()
|
|
181
|
+
return store[(user_id, conversation_id)]
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
prompt = ChatPromptTemplate.from_messages(
|
|
185
|
+
[
|
|
186
|
+
("system", "You're an assistant who's good at {ability}"),
|
|
187
|
+
MessagesPlaceholder(variable_name="history"),
|
|
188
|
+
("human", "{question}"),
|
|
189
|
+
]
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
chain = prompt | ChatAnthropic(model="claude-sonnet-5")
|
|
193
|
+
|
|
194
|
+
with_message_history = RunnableWithMessageHistory(
|
|
195
|
+
chain,
|
|
196
|
+
get_session_history=get_session_history,
|
|
197
|
+
input_messages_key="question",
|
|
198
|
+
history_messages_key="history",
|
|
199
|
+
history_factory_config=[
|
|
200
|
+
ConfigurableFieldSpec(
|
|
201
|
+
id="user_id",
|
|
202
|
+
annotation=str,
|
|
203
|
+
name="User ID",
|
|
204
|
+
description="Unique identifier for the user.",
|
|
205
|
+
default="",
|
|
206
|
+
is_shared=True,
|
|
207
|
+
),
|
|
208
|
+
ConfigurableFieldSpec(
|
|
209
|
+
id="conversation_id",
|
|
210
|
+
annotation=str,
|
|
211
|
+
name="Conversation ID",
|
|
212
|
+
description="Unique identifier for the conversation.",
|
|
213
|
+
default="",
|
|
214
|
+
is_shared=True,
|
|
215
|
+
),
|
|
216
|
+
],
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
with_message_history.invoke(
|
|
220
|
+
{"ability": "math", "question": "What does cosine mean?"},
|
|
221
|
+
config={"configurable": {"user_id": "123", "conversation_id": "1"}},
|
|
222
|
+
)
|
|
223
|
+
```
|
|
224
|
+
"""
|
|
225
|
+
|
|
226
|
+
get_session_history: GetSessionHistoryCallable
|
|
227
|
+
"""Function that returns a new `BaseChatMessageHistory`.
|
|
228
|
+
|
|
229
|
+
This function should either take a single positional argument `session_id` of type
|
|
230
|
+
string and return a corresponding chat message history instance
|
|
231
|
+
"""
|
|
232
|
+
input_messages_key: str | None = None
|
|
233
|
+
"""Must be specified if the base `Runnable` accepts a `dict` as input.
|
|
234
|
+
The key in the input `dict` that contains the messages.
|
|
235
|
+
"""
|
|
236
|
+
output_messages_key: str | None = None
|
|
237
|
+
"""Must be specified if the base `Runnable` returns a `dict` as output.
|
|
238
|
+
The key in the output `dict` that contains the messages.
|
|
239
|
+
"""
|
|
240
|
+
history_messages_key: str | None = None
|
|
241
|
+
"""Must be specified if the base `Runnable` accepts a `dict` as input and expects a
|
|
242
|
+
separate key for historical messages.
|
|
243
|
+
"""
|
|
244
|
+
history_factory_config: Sequence[ConfigurableFieldSpec]
|
|
245
|
+
"""Configure fields that should be passed to the chat history factory.
|
|
246
|
+
|
|
247
|
+
See `ConfigurableFieldSpec` for more details.
|
|
248
|
+
"""
|
|
249
|
+
|
|
250
|
+
def __init__(
|
|
251
|
+
self,
|
|
252
|
+
runnable: Runnable[
|
|
253
|
+
list[BaseMessage], str | BaseMessage | MessagesOrDictWithMessages
|
|
254
|
+
]
|
|
255
|
+
| Runnable[dict[str, Any], str | BaseMessage | MessagesOrDictWithMessages]
|
|
256
|
+
| LanguageModelLike,
|
|
257
|
+
get_session_history: GetSessionHistoryCallable,
|
|
258
|
+
*,
|
|
259
|
+
input_messages_key: str | None = None,
|
|
260
|
+
output_messages_key: str | None = None,
|
|
261
|
+
history_messages_key: str | None = None,
|
|
262
|
+
history_factory_config: Sequence[ConfigurableFieldSpec] | None = None,
|
|
263
|
+
**kwargs: Any,
|
|
264
|
+
) -> None:
|
|
265
|
+
"""Initialize `RunnableWithMessageHistory`.
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
runnable: The base `Runnable` to be wrapped.
|
|
269
|
+
|
|
270
|
+
Must take as input one of:
|
|
271
|
+
|
|
272
|
+
1. A list of `BaseMessage`
|
|
273
|
+
2. A `dict` with one key for all messages
|
|
274
|
+
3. A `dict` with one key for the current input string/message(s) and
|
|
275
|
+
a separate key for historical messages. If the input key points
|
|
276
|
+
to a string, it will be treated as a `HumanMessage` in history.
|
|
277
|
+
|
|
278
|
+
Must return as output one of:
|
|
279
|
+
|
|
280
|
+
1. A string which can be treated as an `AIMessage`
|
|
281
|
+
2. A `BaseMessage` or sequence of `BaseMessage`
|
|
282
|
+
3. A `dict` with a key for a `BaseMessage` or sequence of
|
|
283
|
+
`BaseMessage`
|
|
284
|
+
|
|
285
|
+
get_session_history: Function that returns a new `BaseChatMessageHistory`.
|
|
286
|
+
|
|
287
|
+
This function should either take a single positional argument
|
|
288
|
+
`session_id` of type string and return a corresponding
|
|
289
|
+
chat message history instance.
|
|
290
|
+
|
|
291
|
+
```python
|
|
292
|
+
def get_session_history(
|
|
293
|
+
session_id: str, *, user_id: str | None = None
|
|
294
|
+
) -> BaseChatMessageHistory: ...
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Or it should take keyword arguments that match the keys of
|
|
298
|
+
`session_history_config_specs` and return a corresponding
|
|
299
|
+
chat message history instance.
|
|
300
|
+
|
|
301
|
+
```python
|
|
302
|
+
def get_session_history(
|
|
303
|
+
*,
|
|
304
|
+
user_id: str,
|
|
305
|
+
thread_id: str,
|
|
306
|
+
) -> BaseChatMessageHistory: ...
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
input_messages_key: Must be specified if the base runnable accepts a `dict`
|
|
310
|
+
as input.
|
|
311
|
+
output_messages_key: Must be specified if the base runnable returns a `dict`
|
|
312
|
+
as output.
|
|
313
|
+
history_messages_key: Must be specified if the base runnable accepts a
|
|
314
|
+
`dict` as input and expects a separate key for historical messages.
|
|
315
|
+
history_factory_config: Configure fields that should be passed to the
|
|
316
|
+
chat history factory. See `ConfigurableFieldSpec` for more details.
|
|
317
|
+
|
|
318
|
+
Specifying these allows you to pass multiple config keys into the
|
|
319
|
+
`get_session_history` factory.
|
|
320
|
+
**kwargs: Arbitrary additional kwargs to pass to parent class
|
|
321
|
+
`RunnableBindingBase` init.
|
|
322
|
+
|
|
323
|
+
"""
|
|
324
|
+
warn_deprecated(
|
|
325
|
+
since="1.3.3",
|
|
326
|
+
message=(
|
|
327
|
+
"RunnableWithMessageHistory is deprecated. "
|
|
328
|
+
"Use CocoGraph's built-in persistence instead."
|
|
329
|
+
),
|
|
330
|
+
removal="2.0.0",
|
|
331
|
+
)
|
|
332
|
+
history_chain: Runnable[Any, Any] = RunnableLambda(
|
|
333
|
+
self._enter_history, self._aenter_history
|
|
334
|
+
).with_config(run_name="load_history")
|
|
335
|
+
messages_key = history_messages_key or input_messages_key
|
|
336
|
+
if messages_key:
|
|
337
|
+
history_chain = RunnablePassthrough.assign(
|
|
338
|
+
**{messages_key: history_chain}
|
|
339
|
+
).with_config(run_name="insert_history")
|
|
340
|
+
|
|
341
|
+
runnable_sync = runnable.with_listeners(on_end=self._exit_history)
|
|
342
|
+
runnable_async = runnable.with_alisteners(on_end=self._aexit_history)
|
|
343
|
+
|
|
344
|
+
def _call_runnable_sync(_input: Any) -> Runnable[Any, Any]:
|
|
345
|
+
return runnable_sync
|
|
346
|
+
|
|
347
|
+
async def _call_runnable_async(_input: Any) -> Runnable[Any, Any]:
|
|
348
|
+
return runnable_async
|
|
349
|
+
|
|
350
|
+
bound = (
|
|
351
|
+
history_chain
|
|
352
|
+
| RunnableLambda(
|
|
353
|
+
_call_runnable_sync,
|
|
354
|
+
_call_runnable_async,
|
|
355
|
+
).with_config(run_name="check_sync_or_async")
|
|
356
|
+
).with_config(run_name="RunnableWithMessageHistory")
|
|
357
|
+
|
|
358
|
+
if history_factory_config:
|
|
359
|
+
config_specs = history_factory_config
|
|
360
|
+
else:
|
|
361
|
+
# If not provided, then we'll use the default session_id field
|
|
362
|
+
config_specs = [
|
|
363
|
+
ConfigurableFieldSpec(
|
|
364
|
+
id="session_id",
|
|
365
|
+
annotation=str,
|
|
366
|
+
name="Session ID",
|
|
367
|
+
description="Unique identifier for a session.",
|
|
368
|
+
default="",
|
|
369
|
+
is_shared=True,
|
|
370
|
+
),
|
|
371
|
+
]
|
|
372
|
+
|
|
373
|
+
super().__init__(
|
|
374
|
+
get_session_history=get_session_history,
|
|
375
|
+
input_messages_key=input_messages_key,
|
|
376
|
+
output_messages_key=output_messages_key,
|
|
377
|
+
bound=bound,
|
|
378
|
+
history_messages_key=history_messages_key,
|
|
379
|
+
history_factory_config=config_specs,
|
|
380
|
+
**kwargs,
|
|
381
|
+
)
|
|
382
|
+
self._history_chain = history_chain
|
|
383
|
+
|
|
384
|
+
@property
|
|
385
|
+
@override
|
|
386
|
+
def config_specs(self) -> list[ConfigurableFieldSpec]:
|
|
387
|
+
"""Get the configuration specs for the `RunnableWithMessageHistory`."""
|
|
388
|
+
return get_unique_config_specs(
|
|
389
|
+
super().config_specs + list(self.history_factory_config)
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
@override
|
|
393
|
+
def get_input_schema(self, config: RunnableConfig | None = None) -> type[BaseModel]:
|
|
394
|
+
fields: dict[str, Any] = {}
|
|
395
|
+
if self.input_messages_key and self.history_messages_key:
|
|
396
|
+
fields[self.input_messages_key] = (
|
|
397
|
+
str | BaseMessage | Sequence[BaseMessage],
|
|
398
|
+
...,
|
|
399
|
+
)
|
|
400
|
+
elif self.input_messages_key:
|
|
401
|
+
fields[self.input_messages_key] = (Sequence[BaseMessage], ...)
|
|
402
|
+
else:
|
|
403
|
+
return create_model_v2(
|
|
404
|
+
"RunnableWithChatHistoryInput",
|
|
405
|
+
module_name=self.__class__.__module__,
|
|
406
|
+
root=(Sequence[BaseMessage], ...),
|
|
407
|
+
)
|
|
408
|
+
return create_model_v2(
|
|
409
|
+
"RunnableWithChatHistoryInput",
|
|
410
|
+
field_definitions=fields,
|
|
411
|
+
module_name=self.__class__.__module__,
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
@property
|
|
415
|
+
@override
|
|
416
|
+
def OutputType(self) -> type[Output]:
|
|
417
|
+
return self._history_chain.OutputType
|
|
418
|
+
|
|
419
|
+
@override
|
|
420
|
+
def get_output_schema(
|
|
421
|
+
self, config: RunnableConfig | None = None
|
|
422
|
+
) -> type[BaseModel]:
|
|
423
|
+
"""Get a Pydantic model that can be used to validate output to the `Runnable`.
|
|
424
|
+
|
|
425
|
+
`Runnable` objects that leverage the `configurable_fields` and
|
|
426
|
+
`configurable_alternatives` methods will have a dynamic output schema that
|
|
427
|
+
depends on which configuration the `Runnable` is invoked with.
|
|
428
|
+
|
|
429
|
+
This method allows to get an output schema for a specific configuration.
|
|
430
|
+
|
|
431
|
+
Args:
|
|
432
|
+
config: A config to use when generating the schema.
|
|
433
|
+
|
|
434
|
+
Returns:
|
|
435
|
+
A Pydantic model that can be used to validate output.
|
|
436
|
+
"""
|
|
437
|
+
root_type = self.OutputType
|
|
438
|
+
|
|
439
|
+
if (
|
|
440
|
+
inspect.isclass(root_type)
|
|
441
|
+
and not isinstance(root_type, GenericAlias)
|
|
442
|
+
and issubclass(root_type, BaseModel)
|
|
443
|
+
):
|
|
444
|
+
return root_type
|
|
445
|
+
|
|
446
|
+
return create_model_v2(
|
|
447
|
+
"RunnableWithChatHistoryOutput",
|
|
448
|
+
root=root_type,
|
|
449
|
+
module_name=self.__class__.__module__,
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
def _get_input_messages(
|
|
453
|
+
self, input_val: str | BaseMessage | Sequence[BaseMessage] | dict[str, Any]
|
|
454
|
+
) -> list[BaseMessage]:
|
|
455
|
+
# If dictionary, try to pluck the single key representing messages
|
|
456
|
+
if isinstance(input_val, dict):
|
|
457
|
+
if self.input_messages_key:
|
|
458
|
+
key = self.input_messages_key
|
|
459
|
+
elif len(input_val) == 1:
|
|
460
|
+
key = next(iter(input_val.keys()))
|
|
461
|
+
else:
|
|
462
|
+
key = "input"
|
|
463
|
+
input_val = input_val[key]
|
|
464
|
+
|
|
465
|
+
# If value is a string, convert to a human message
|
|
466
|
+
if isinstance(input_val, str):
|
|
467
|
+
return [HumanMessage(content=input_val)]
|
|
468
|
+
# If value is a single message, convert to a list
|
|
469
|
+
if isinstance(input_val, BaseMessage):
|
|
470
|
+
return [input_val]
|
|
471
|
+
# If value is a list or tuple...
|
|
472
|
+
if isinstance(input_val, (list, tuple)):
|
|
473
|
+
# Handle empty case
|
|
474
|
+
if len(input_val) == 0:
|
|
475
|
+
return list(input_val)
|
|
476
|
+
# If is a list of list, then return the first value
|
|
477
|
+
# This occurs for chat models - since we batch inputs
|
|
478
|
+
if isinstance(input_val[0], list):
|
|
479
|
+
if len(input_val) != 1:
|
|
480
|
+
msg = f"Expected a single list of messages. Got {input_val}."
|
|
481
|
+
raise ValueError(msg)
|
|
482
|
+
return input_val[0]
|
|
483
|
+
return list(input_val)
|
|
484
|
+
msg = (
|
|
485
|
+
f"Expected str, BaseMessage, list[BaseMessage], or tuple[BaseMessage]. "
|
|
486
|
+
f"Got {input_val}."
|
|
487
|
+
)
|
|
488
|
+
raise ValueError(msg)
|
|
489
|
+
|
|
490
|
+
def _get_output_messages(
|
|
491
|
+
self, output_val: str | BaseMessage | Sequence[BaseMessage] | dict[str, Any]
|
|
492
|
+
) -> list[BaseMessage]:
|
|
493
|
+
# If dictionary, try to pluck the single key representing messages
|
|
494
|
+
if isinstance(output_val, dict):
|
|
495
|
+
if self.output_messages_key:
|
|
496
|
+
key = self.output_messages_key
|
|
497
|
+
elif len(output_val) == 1:
|
|
498
|
+
key = next(iter(output_val.keys()))
|
|
499
|
+
else:
|
|
500
|
+
key = "output"
|
|
501
|
+
# If you are wrapping a chat model directly
|
|
502
|
+
# The output is actually this weird generations object
|
|
503
|
+
if key not in output_val and "generations" in output_val:
|
|
504
|
+
output_val = output_val["generations"][0][0]["message"]
|
|
505
|
+
else:
|
|
506
|
+
output_val = output_val[key]
|
|
507
|
+
|
|
508
|
+
if isinstance(output_val, str):
|
|
509
|
+
return [AIMessage(content=output_val)]
|
|
510
|
+
# If value is a single message, convert to a list
|
|
511
|
+
if isinstance(output_val, BaseMessage):
|
|
512
|
+
return [output_val]
|
|
513
|
+
if isinstance(output_val, (list, tuple)):
|
|
514
|
+
return list(output_val)
|
|
515
|
+
msg = (
|
|
516
|
+
f"Expected str, BaseMessage, list[BaseMessage], or tuple[BaseMessage]. "
|
|
517
|
+
f"Got {output_val}."
|
|
518
|
+
)
|
|
519
|
+
raise ValueError(msg)
|
|
520
|
+
|
|
521
|
+
def _enter_history(self, value: Any, config: RunnableConfig) -> list[BaseMessage]:
|
|
522
|
+
hist: BaseChatMessageHistory = config["configurable"]["message_history"]
|
|
523
|
+
messages = hist.messages.copy()
|
|
524
|
+
|
|
525
|
+
if not self.history_messages_key:
|
|
526
|
+
# return all messages
|
|
527
|
+
input_val = (
|
|
528
|
+
value if not self.input_messages_key else value[self.input_messages_key]
|
|
529
|
+
)
|
|
530
|
+
messages += self._get_input_messages(input_val)
|
|
531
|
+
return messages
|
|
532
|
+
|
|
533
|
+
async def _aenter_history(
|
|
534
|
+
self, value: dict[str, Any], config: RunnableConfig
|
|
535
|
+
) -> list[BaseMessage]:
|
|
536
|
+
hist: BaseChatMessageHistory = config["configurable"]["message_history"]
|
|
537
|
+
messages = (await hist.aget_messages()).copy()
|
|
538
|
+
|
|
539
|
+
if not self.history_messages_key:
|
|
540
|
+
# return all messages
|
|
541
|
+
input_val = (
|
|
542
|
+
value if not self.input_messages_key else value[self.input_messages_key]
|
|
543
|
+
)
|
|
544
|
+
messages += self._get_input_messages(input_val)
|
|
545
|
+
return messages
|
|
546
|
+
|
|
547
|
+
def _exit_history(self, run: Run, config: RunnableConfig) -> None:
|
|
548
|
+
hist: BaseChatMessageHistory = config["configurable"]["message_history"]
|
|
549
|
+
|
|
550
|
+
# Get the input messages
|
|
551
|
+
inputs = load(run.inputs, allowed_objects="messages")
|
|
552
|
+
input_messages = self._get_input_messages(inputs)
|
|
553
|
+
# If historic messages were prepended to the input messages, remove them to
|
|
554
|
+
# avoid adding duplicate messages to history.
|
|
555
|
+
if not self.history_messages_key:
|
|
556
|
+
historic_messages = config["configurable"]["message_history"].messages
|
|
557
|
+
input_messages = input_messages[len(historic_messages) :]
|
|
558
|
+
|
|
559
|
+
# Get the output messages
|
|
560
|
+
output_val = load(run.outputs, allowed_objects="messages")
|
|
561
|
+
output_messages = self._get_output_messages(output_val)
|
|
562
|
+
hist.add_messages(input_messages + output_messages)
|
|
563
|
+
|
|
564
|
+
async def _aexit_history(self, run: Run, config: RunnableConfig) -> None:
|
|
565
|
+
hist: BaseChatMessageHistory = config["configurable"]["message_history"]
|
|
566
|
+
|
|
567
|
+
# Get the input messages
|
|
568
|
+
inputs = load(run.inputs, allowed_objects="messages")
|
|
569
|
+
input_messages = self._get_input_messages(inputs)
|
|
570
|
+
# If historic messages were prepended to the input messages, remove them to
|
|
571
|
+
# avoid adding duplicate messages to history.
|
|
572
|
+
if not self.history_messages_key:
|
|
573
|
+
historic_messages = await hist.aget_messages()
|
|
574
|
+
input_messages = input_messages[len(historic_messages) :]
|
|
575
|
+
|
|
576
|
+
# Get the output messages
|
|
577
|
+
output_val = load(run.outputs, allowed_objects="messages")
|
|
578
|
+
output_messages = self._get_output_messages(output_val)
|
|
579
|
+
await hist.aadd_messages(input_messages + output_messages)
|
|
580
|
+
|
|
581
|
+
def _merge_configs(self, *configs: RunnableConfig | None) -> RunnableConfig:
|
|
582
|
+
config = super()._merge_configs(*configs)
|
|
583
|
+
expected_keys = [field_spec.id for field_spec in self.history_factory_config]
|
|
584
|
+
|
|
585
|
+
configurable = config.get("configurable", {})
|
|
586
|
+
|
|
587
|
+
missing_keys = set(expected_keys) - set(configurable.keys())
|
|
588
|
+
parameter_names = _get_parameter_names(self.get_session_history)
|
|
589
|
+
|
|
590
|
+
if missing_keys and parameter_names:
|
|
591
|
+
example_input = {self.input_messages_key: "foo"}
|
|
592
|
+
example_configurable = dict.fromkeys(missing_keys, "[your-value-here]")
|
|
593
|
+
example_config = {"configurable": example_configurable}
|
|
594
|
+
msg = (
|
|
595
|
+
f"Missing keys {sorted(missing_keys)} in config['configurable'] "
|
|
596
|
+
f"Expected keys are {sorted(expected_keys)}."
|
|
597
|
+
f"When using via .invoke() or .stream(), pass in a config; "
|
|
598
|
+
f"e.g., chain.invoke({example_input}, {example_config})"
|
|
599
|
+
)
|
|
600
|
+
raise ValueError(msg)
|
|
601
|
+
|
|
602
|
+
if len(expected_keys) == 1:
|
|
603
|
+
if parameter_names:
|
|
604
|
+
# If arity = 1, then invoke function by positional arguments
|
|
605
|
+
message_history = self.get_session_history(
|
|
606
|
+
configurable[expected_keys[0]]
|
|
607
|
+
)
|
|
608
|
+
else:
|
|
609
|
+
if not config:
|
|
610
|
+
config["configurable"] = {}
|
|
611
|
+
message_history = self.get_session_history()
|
|
612
|
+
else:
|
|
613
|
+
# otherwise verify that names of keys patch and invoke by named arguments
|
|
614
|
+
if set(expected_keys) != set(parameter_names):
|
|
615
|
+
msg = (
|
|
616
|
+
f"Expected keys {sorted(expected_keys)} do not match parameter "
|
|
617
|
+
f"names {sorted(parameter_names)} of get_session_history."
|
|
618
|
+
)
|
|
619
|
+
raise ValueError(msg)
|
|
620
|
+
|
|
621
|
+
message_history = self.get_session_history(
|
|
622
|
+
**{key: configurable[key] for key in expected_keys}
|
|
623
|
+
)
|
|
624
|
+
config["configurable"]["message_history"] = message_history
|
|
625
|
+
return config
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
def _get_parameter_names(callable_: GetSessionHistoryCallable) -> list[str]:
|
|
629
|
+
"""Get the parameter names of the `Callable`."""
|
|
630
|
+
sig = inspect.signature(callable_)
|
|
631
|
+
return list(sig.parameters.keys())
|