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,698 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
import pickle
|
|
6
|
+
import random
|
|
7
|
+
import shutil
|
|
8
|
+
from collections import defaultdict
|
|
9
|
+
from collections.abc import AsyncIterator, Iterator, Mapping, Sequence
|
|
10
|
+
from contextlib import AbstractAsyncContextManager, AbstractContextManager, ExitStack
|
|
11
|
+
from types import TracebackType
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from coco_core.runnables import RunnableConfig
|
|
15
|
+
|
|
16
|
+
from coco_graph.checkpoint.base import (
|
|
17
|
+
WRITES_IDX_MAP,
|
|
18
|
+
BaseCheckpointSaver,
|
|
19
|
+
ChannelVersions,
|
|
20
|
+
Checkpoint,
|
|
21
|
+
CheckpointMetadata,
|
|
22
|
+
CheckpointTuple,
|
|
23
|
+
DeltaChannelHistory,
|
|
24
|
+
PendingWrite,
|
|
25
|
+
SerializerProtocol,
|
|
26
|
+
get_checkpoint_id,
|
|
27
|
+
get_checkpoint_metadata,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class InMemorySaver(
|
|
34
|
+
BaseCheckpointSaver[str], AbstractContextManager, AbstractAsyncContextManager
|
|
35
|
+
):
|
|
36
|
+
"""An in-memory checkpoint saver.
|
|
37
|
+
|
|
38
|
+
This checkpoint saver stores checkpoints in memory using a `defaultdict`.
|
|
39
|
+
|
|
40
|
+
Note:
|
|
41
|
+
Only use `InMemorySaver` for debugging or testing purposes.
|
|
42
|
+
For production use cases we recommend installing [trustcoco-graph-checkpoint-postgres](https://pypi.org/project/langgraph-checkpoint-postgres/) and using `PostgresSaver` / `AsyncPostgresSaver`.
|
|
43
|
+
|
|
44
|
+
If you are using LangSmith Deployment, no checkpointer needs to be specified. The correct managed checkpointer will be used automatically.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
serde: The serializer to use for serializing and deserializing checkpoints.
|
|
48
|
+
|
|
49
|
+
Example:
|
|
50
|
+
```python
|
|
51
|
+
import asyncio
|
|
52
|
+
|
|
53
|
+
from coco_graph.checkpoint.memory import InMemorySaver
|
|
54
|
+
from coco_graph.graph import StateGraph
|
|
55
|
+
|
|
56
|
+
builder = StateGraph(int)
|
|
57
|
+
builder.add_node("add_one", lambda x: x + 1)
|
|
58
|
+
builder.set_entry_point("add_one")
|
|
59
|
+
builder.set_finish_point("add_one")
|
|
60
|
+
|
|
61
|
+
memory = InMemorySaver()
|
|
62
|
+
graph = builder.compile(checkpointer=memory)
|
|
63
|
+
coro = graph.ainvoke(1, {"configurable": {"thread_id": "thread-1"}})
|
|
64
|
+
asyncio.run(coro) # Output: 2
|
|
65
|
+
```
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
# thread ID -> checkpoint NS -> checkpoint ID -> checkpoint mapping
|
|
69
|
+
storage: defaultdict[
|
|
70
|
+
str,
|
|
71
|
+
dict[str, dict[str, tuple[tuple[str, bytes], tuple[str, bytes], str | None]]],
|
|
72
|
+
]
|
|
73
|
+
# (thread ID, checkpoint NS, checkpoint ID) -> (task ID, write idx)
|
|
74
|
+
writes: defaultdict[
|
|
75
|
+
tuple[str, str, str],
|
|
76
|
+
dict[tuple[str, int], tuple[str, str, tuple[str, bytes], str]],
|
|
77
|
+
]
|
|
78
|
+
blobs: dict[
|
|
79
|
+
tuple[
|
|
80
|
+
str, str, str, str | int | float
|
|
81
|
+
], # thread id, checkpoint ns, channel, version
|
|
82
|
+
tuple[str, bytes],
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
def __init__(
|
|
86
|
+
self,
|
|
87
|
+
*,
|
|
88
|
+
serde: SerializerProtocol | None = None,
|
|
89
|
+
factory: type[defaultdict] = defaultdict,
|
|
90
|
+
) -> None:
|
|
91
|
+
super().__init__(serde=serde)
|
|
92
|
+
self.storage = factory(lambda: defaultdict(dict))
|
|
93
|
+
self.writes = factory(dict)
|
|
94
|
+
self.blobs = factory()
|
|
95
|
+
self.stack = ExitStack()
|
|
96
|
+
if factory is not defaultdict:
|
|
97
|
+
self.stack.enter_context(self.storage) # type: ignore[arg-type]
|
|
98
|
+
self.stack.enter_context(self.writes) # type: ignore[arg-type]
|
|
99
|
+
self.stack.enter_context(self.blobs) # type: ignore[arg-type]
|
|
100
|
+
|
|
101
|
+
def __enter__(self) -> InMemorySaver:
|
|
102
|
+
self.stack.__enter__()
|
|
103
|
+
return self
|
|
104
|
+
|
|
105
|
+
def __exit__(
|
|
106
|
+
self,
|
|
107
|
+
exc_type: type[BaseException] | None,
|
|
108
|
+
exc_value: BaseException | None,
|
|
109
|
+
traceback: TracebackType | None,
|
|
110
|
+
) -> bool | None:
|
|
111
|
+
return self.stack.__exit__(exc_type, exc_value, traceback)
|
|
112
|
+
|
|
113
|
+
async def __aenter__(self) -> InMemorySaver:
|
|
114
|
+
self.stack.__enter__()
|
|
115
|
+
return self
|
|
116
|
+
|
|
117
|
+
async def __aexit__(
|
|
118
|
+
self,
|
|
119
|
+
__exc_type: type[BaseException] | None,
|
|
120
|
+
__exc_value: BaseException | None,
|
|
121
|
+
__traceback: TracebackType | None,
|
|
122
|
+
) -> bool | None:
|
|
123
|
+
return self.stack.__exit__(__exc_type, __exc_value, __traceback)
|
|
124
|
+
|
|
125
|
+
def _load_blobs(
|
|
126
|
+
self,
|
|
127
|
+
thread_id: str,
|
|
128
|
+
checkpoint_ns: str,
|
|
129
|
+
versions: ChannelVersions,
|
|
130
|
+
) -> dict[str, Any]:
|
|
131
|
+
result: dict[str, Any] = {}
|
|
132
|
+
for k, ver in versions.items():
|
|
133
|
+
kk = (thread_id, checkpoint_ns, k, ver)
|
|
134
|
+
if kk not in self.blobs:
|
|
135
|
+
continue
|
|
136
|
+
vv = self.blobs[kk]
|
|
137
|
+
if vv[0] == "empty":
|
|
138
|
+
continue
|
|
139
|
+
result[k] = self.serde.loads_typed(vv)
|
|
140
|
+
return result
|
|
141
|
+
|
|
142
|
+
def get_delta_channel_history(
|
|
143
|
+
self, *, config: RunnableConfig, channels: Sequence[str]
|
|
144
|
+
) -> Mapping[str, DeltaChannelHistory]:
|
|
145
|
+
"""Override: walk the parent chain ONCE for all requested channels.
|
|
146
|
+
|
|
147
|
+
Each channel terminates independently at the nearest ancestor
|
|
148
|
+
whose stored blob is non-empty. Other channels keep walking until
|
|
149
|
+
they find their own terminator or hit the root.
|
|
150
|
+
|
|
151
|
+
A blob is the value AT its ancestor, prior to the writes stored
|
|
152
|
+
under that same ancestor (those writes produce its child, which
|
|
153
|
+
is on the path to the target). This holds for `_DeltaSnapshot`
|
|
154
|
+
blobs and for pre-delta plain values alike, so the seed
|
|
155
|
+
ancestor's own writes are always collected. Writes at ancestors
|
|
156
|
+
older than the seed are subsumed by the seed value and are never
|
|
157
|
+
reached — the walk terminates there.
|
|
158
|
+
"""
|
|
159
|
+
if not channels:
|
|
160
|
+
return {}
|
|
161
|
+
thread_id = config["configurable"]["thread_id"]
|
|
162
|
+
checkpoint_ns = config["configurable"].get("checkpoint_ns", "")
|
|
163
|
+
checkpoint_id = config["configurable"].get("checkpoint_id", "")
|
|
164
|
+
ns_storage = self.storage.get(thread_id, {}).get(checkpoint_ns, {})
|
|
165
|
+
|
|
166
|
+
chain: list[str] = []
|
|
167
|
+
target_entry = ns_storage.get(checkpoint_id)
|
|
168
|
+
current: str | None = target_entry[2] if target_entry is not None else None
|
|
169
|
+
while current is not None:
|
|
170
|
+
entry = ns_storage.get(current)
|
|
171
|
+
if entry is None:
|
|
172
|
+
break
|
|
173
|
+
chain.append(current)
|
|
174
|
+
_, _, parent = entry
|
|
175
|
+
current = parent
|
|
176
|
+
|
|
177
|
+
collected_by_ch: dict[str, list[PendingWrite]] = {c: [] for c in channels}
|
|
178
|
+
seed_by_ch: dict[str, Any] = {}
|
|
179
|
+
remaining: set[str] = set(channels)
|
|
180
|
+
|
|
181
|
+
for cp_id in chain:
|
|
182
|
+
if not remaining:
|
|
183
|
+
break
|
|
184
|
+
entry = ns_storage.get(cp_id)
|
|
185
|
+
ckpt = self.serde.loads_typed(entry[0]) if entry is not None else None
|
|
186
|
+
|
|
187
|
+
terminated_here: set[str] = set()
|
|
188
|
+
blob_value_by_ch: dict[str, Any] = {}
|
|
189
|
+
if ckpt is not None:
|
|
190
|
+
versions = ckpt.get("channel_versions", {})
|
|
191
|
+
for ch in remaining:
|
|
192
|
+
ver = versions.get(ch)
|
|
193
|
+
if ver is None:
|
|
194
|
+
continue
|
|
195
|
+
blob_entry = self.blobs.get((thread_id, checkpoint_ns, ch, ver))
|
|
196
|
+
if blob_entry is None or blob_entry[0] == "empty":
|
|
197
|
+
continue
|
|
198
|
+
blob_value_by_ch[ch] = self.serde.loads_typed(blob_entry)
|
|
199
|
+
terminated_here.add(ch)
|
|
200
|
+
|
|
201
|
+
step_writes = self.writes.get((thread_id, checkpoint_ns, cp_id), {})
|
|
202
|
+
for (_task_id, _idx), (tid, ch, serialized, _) in sorted(
|
|
203
|
+
step_writes.items(), reverse=True
|
|
204
|
+
):
|
|
205
|
+
if ch not in remaining:
|
|
206
|
+
continue
|
|
207
|
+
collected_by_ch[ch].append(
|
|
208
|
+
(tid, ch, self.serde.loads_typed(serialized))
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
for ch in terminated_here:
|
|
212
|
+
seed_by_ch[ch] = blob_value_by_ch[ch]
|
|
213
|
+
remaining.discard(ch)
|
|
214
|
+
|
|
215
|
+
result: dict[str, DeltaChannelHistory] = {}
|
|
216
|
+
for ch in channels:
|
|
217
|
+
entry_h: DeltaChannelHistory = {
|
|
218
|
+
"writes": list(reversed(collected_by_ch[ch]))
|
|
219
|
+
}
|
|
220
|
+
if ch in seed_by_ch:
|
|
221
|
+
entry_h["seed"] = seed_by_ch[ch]
|
|
222
|
+
result[ch] = entry_h
|
|
223
|
+
return result
|
|
224
|
+
|
|
225
|
+
async def aget_delta_channel_history(
|
|
226
|
+
self, *, config: RunnableConfig, channels: Sequence[str]
|
|
227
|
+
) -> Mapping[str, DeltaChannelHistory]:
|
|
228
|
+
return self.get_delta_channel_history(config=config, channels=channels)
|
|
229
|
+
|
|
230
|
+
def get_tuple(self, config: RunnableConfig) -> CheckpointTuple | None:
|
|
231
|
+
"""Get a checkpoint tuple from the in-memory storage.
|
|
232
|
+
|
|
233
|
+
This method retrieves a checkpoint tuple from the in-memory storage based on the
|
|
234
|
+
provided config. If the config contains a `checkpoint_id` key, the checkpoint with
|
|
235
|
+
the matching thread ID and timestamp is retrieved. Otherwise, the latest checkpoint
|
|
236
|
+
for the given thread ID is retrieved.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
config: The config to use for retrieving the checkpoint.
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
The retrieved checkpoint tuple, or None if no matching checkpoint was found.
|
|
243
|
+
"""
|
|
244
|
+
thread_id: str = config["configurable"]["thread_id"]
|
|
245
|
+
checkpoint_ns: str = config["configurable"].get("checkpoint_ns", "")
|
|
246
|
+
if checkpoint_id := get_checkpoint_id(config):
|
|
247
|
+
if saved := self.storage[thread_id][checkpoint_ns].get(checkpoint_id):
|
|
248
|
+
checkpoint, metadata, parent_checkpoint_id = saved
|
|
249
|
+
writes = self.writes[(thread_id, checkpoint_ns, checkpoint_id)].values()
|
|
250
|
+
checkpoint_: Checkpoint = self.serde.loads_typed(checkpoint)
|
|
251
|
+
return CheckpointTuple(
|
|
252
|
+
config=config,
|
|
253
|
+
checkpoint={
|
|
254
|
+
**checkpoint_,
|
|
255
|
+
"channel_values": self._load_blobs(
|
|
256
|
+
thread_id, checkpoint_ns, checkpoint_["channel_versions"]
|
|
257
|
+
),
|
|
258
|
+
},
|
|
259
|
+
metadata=self.serde.loads_typed(metadata),
|
|
260
|
+
pending_writes=[
|
|
261
|
+
(id, c, self.serde.loads_typed(v)) for id, c, v, _ in writes
|
|
262
|
+
],
|
|
263
|
+
parent_config=(
|
|
264
|
+
{
|
|
265
|
+
"configurable": {
|
|
266
|
+
"thread_id": thread_id,
|
|
267
|
+
"checkpoint_ns": checkpoint_ns,
|
|
268
|
+
"checkpoint_id": parent_checkpoint_id,
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if parent_checkpoint_id
|
|
272
|
+
else None
|
|
273
|
+
),
|
|
274
|
+
)
|
|
275
|
+
else:
|
|
276
|
+
if checkpoints := self.storage[thread_id][checkpoint_ns]:
|
|
277
|
+
checkpoint_id = max(checkpoints.keys())
|
|
278
|
+
checkpoint, metadata, parent_checkpoint_id = checkpoints[checkpoint_id]
|
|
279
|
+
writes = self.writes[(thread_id, checkpoint_ns, checkpoint_id)].values()
|
|
280
|
+
checkpoint_ = self.serde.loads_typed(checkpoint)
|
|
281
|
+
return CheckpointTuple(
|
|
282
|
+
config={
|
|
283
|
+
"configurable": {
|
|
284
|
+
"thread_id": thread_id,
|
|
285
|
+
"checkpoint_ns": checkpoint_ns,
|
|
286
|
+
"checkpoint_id": checkpoint_id,
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
checkpoint={
|
|
290
|
+
**checkpoint_,
|
|
291
|
+
"channel_values": self._load_blobs(
|
|
292
|
+
thread_id, checkpoint_ns, checkpoint_["channel_versions"]
|
|
293
|
+
),
|
|
294
|
+
},
|
|
295
|
+
metadata=self.serde.loads_typed(metadata),
|
|
296
|
+
pending_writes=[
|
|
297
|
+
(id, c, self.serde.loads_typed(v)) for id, c, v, _ in writes
|
|
298
|
+
],
|
|
299
|
+
parent_config=(
|
|
300
|
+
{
|
|
301
|
+
"configurable": {
|
|
302
|
+
"thread_id": thread_id,
|
|
303
|
+
"checkpoint_ns": checkpoint_ns,
|
|
304
|
+
"checkpoint_id": parent_checkpoint_id,
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if parent_checkpoint_id
|
|
308
|
+
else None
|
|
309
|
+
),
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
def list(
|
|
313
|
+
self,
|
|
314
|
+
config: RunnableConfig | None,
|
|
315
|
+
*,
|
|
316
|
+
filter: dict[str, Any] | None = None,
|
|
317
|
+
before: RunnableConfig | None = None,
|
|
318
|
+
limit: int | None = None,
|
|
319
|
+
) -> Iterator[CheckpointTuple]:
|
|
320
|
+
"""List checkpoints from the in-memory storage.
|
|
321
|
+
|
|
322
|
+
This method retrieves a list of checkpoint tuples from the in-memory storage based
|
|
323
|
+
on the provided criteria.
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
config: Base configuration for filtering checkpoints.
|
|
327
|
+
filter: Additional filtering criteria for metadata.
|
|
328
|
+
before: List checkpoints created before this configuration.
|
|
329
|
+
limit: Maximum number of checkpoints to return.
|
|
330
|
+
|
|
331
|
+
Yields:
|
|
332
|
+
An iterator of matching checkpoint tuples.
|
|
333
|
+
"""
|
|
334
|
+
thread_ids = (config["configurable"]["thread_id"],) if config else self.storage
|
|
335
|
+
config_checkpoint_ns = (
|
|
336
|
+
config["configurable"].get("checkpoint_ns") if config else None
|
|
337
|
+
)
|
|
338
|
+
config_checkpoint_id = get_checkpoint_id(config) if config else None
|
|
339
|
+
for thread_id in thread_ids:
|
|
340
|
+
for checkpoint_ns in self.storage[thread_id].keys():
|
|
341
|
+
if (
|
|
342
|
+
config_checkpoint_ns is not None
|
|
343
|
+
and checkpoint_ns != config_checkpoint_ns
|
|
344
|
+
):
|
|
345
|
+
continue
|
|
346
|
+
|
|
347
|
+
for checkpoint_id, (
|
|
348
|
+
checkpoint,
|
|
349
|
+
metadata_b,
|
|
350
|
+
parent_checkpoint_id,
|
|
351
|
+
) in sorted(
|
|
352
|
+
self.storage[thread_id][checkpoint_ns].items(),
|
|
353
|
+
key=lambda x: x[0],
|
|
354
|
+
reverse=True,
|
|
355
|
+
):
|
|
356
|
+
# filter by checkpoint ID from config
|
|
357
|
+
if config_checkpoint_id and checkpoint_id != config_checkpoint_id:
|
|
358
|
+
continue
|
|
359
|
+
|
|
360
|
+
# filter by checkpoint ID from `before` config
|
|
361
|
+
if (
|
|
362
|
+
before
|
|
363
|
+
and (before_checkpoint_id := get_checkpoint_id(before))
|
|
364
|
+
and checkpoint_id >= before_checkpoint_id
|
|
365
|
+
):
|
|
366
|
+
continue
|
|
367
|
+
|
|
368
|
+
# filter by metadata
|
|
369
|
+
metadata = self.serde.loads_typed(metadata_b)
|
|
370
|
+
if filter and not all(
|
|
371
|
+
query_value == metadata.get(query_key)
|
|
372
|
+
for query_key, query_value in filter.items()
|
|
373
|
+
):
|
|
374
|
+
continue
|
|
375
|
+
|
|
376
|
+
# limit search results
|
|
377
|
+
if limit is not None and limit <= 0:
|
|
378
|
+
break
|
|
379
|
+
elif limit is not None:
|
|
380
|
+
limit -= 1
|
|
381
|
+
|
|
382
|
+
writes = self.writes[
|
|
383
|
+
(thread_id, checkpoint_ns, checkpoint_id)
|
|
384
|
+
].values()
|
|
385
|
+
|
|
386
|
+
checkpoint_: Checkpoint = self.serde.loads_typed(checkpoint)
|
|
387
|
+
|
|
388
|
+
yield CheckpointTuple(
|
|
389
|
+
config={
|
|
390
|
+
"configurable": {
|
|
391
|
+
"thread_id": thread_id,
|
|
392
|
+
"checkpoint_ns": checkpoint_ns,
|
|
393
|
+
"checkpoint_id": checkpoint_id,
|
|
394
|
+
}
|
|
395
|
+
},
|
|
396
|
+
checkpoint={
|
|
397
|
+
**checkpoint_,
|
|
398
|
+
"channel_values": self._load_blobs(
|
|
399
|
+
thread_id,
|
|
400
|
+
checkpoint_ns,
|
|
401
|
+
checkpoint_["channel_versions"],
|
|
402
|
+
),
|
|
403
|
+
},
|
|
404
|
+
metadata=metadata,
|
|
405
|
+
parent_config=(
|
|
406
|
+
{
|
|
407
|
+
"configurable": {
|
|
408
|
+
"thread_id": thread_id,
|
|
409
|
+
"checkpoint_ns": checkpoint_ns,
|
|
410
|
+
"checkpoint_id": parent_checkpoint_id,
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
if parent_checkpoint_id
|
|
414
|
+
else None
|
|
415
|
+
),
|
|
416
|
+
pending_writes=[
|
|
417
|
+
(id, c, self.serde.loads_typed(v)) for id, c, v, _ in writes
|
|
418
|
+
],
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
def put(
|
|
422
|
+
self,
|
|
423
|
+
config: RunnableConfig,
|
|
424
|
+
checkpoint: Checkpoint,
|
|
425
|
+
metadata: CheckpointMetadata,
|
|
426
|
+
new_versions: ChannelVersions,
|
|
427
|
+
) -> RunnableConfig:
|
|
428
|
+
"""Save a checkpoint to the in-memory storage.
|
|
429
|
+
|
|
430
|
+
This method saves a checkpoint to the in-memory storage. The checkpoint is associated
|
|
431
|
+
with the provided config.
|
|
432
|
+
|
|
433
|
+
Args:
|
|
434
|
+
config: The config to associate with the checkpoint.
|
|
435
|
+
checkpoint: The checkpoint to save.
|
|
436
|
+
metadata: Additional metadata to save with the checkpoint.
|
|
437
|
+
new_versions: New versions as of this write
|
|
438
|
+
|
|
439
|
+
Returns:
|
|
440
|
+
RunnableConfig: The updated config containing the saved checkpoint's timestamp.
|
|
441
|
+
"""
|
|
442
|
+
c = checkpoint.copy()
|
|
443
|
+
thread_id = config["configurable"]["thread_id"]
|
|
444
|
+
checkpoint_ns = config["configurable"]["checkpoint_ns"]
|
|
445
|
+
values: dict[str, Any] = c.pop("channel_values") # type: ignore[misc]
|
|
446
|
+
for k, v in new_versions.items():
|
|
447
|
+
self.blobs[(thread_id, checkpoint_ns, k, v)] = (
|
|
448
|
+
self.serde.dumps_typed(values[k]) if k in values else ("empty", b"")
|
|
449
|
+
)
|
|
450
|
+
self.storage[thread_id][checkpoint_ns].update(
|
|
451
|
+
{
|
|
452
|
+
checkpoint["id"]: (
|
|
453
|
+
self.serde.dumps_typed(c),
|
|
454
|
+
self.serde.dumps_typed(get_checkpoint_metadata(config, metadata)),
|
|
455
|
+
config["configurable"].get("checkpoint_id"), # parent
|
|
456
|
+
)
|
|
457
|
+
}
|
|
458
|
+
)
|
|
459
|
+
return {
|
|
460
|
+
"configurable": {
|
|
461
|
+
"thread_id": thread_id,
|
|
462
|
+
"checkpoint_ns": checkpoint_ns,
|
|
463
|
+
"checkpoint_id": checkpoint["id"],
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
def put_writes(
|
|
468
|
+
self,
|
|
469
|
+
config: RunnableConfig,
|
|
470
|
+
writes: Sequence[tuple[str, Any]],
|
|
471
|
+
task_id: str,
|
|
472
|
+
task_path: str = "",
|
|
473
|
+
) -> None:
|
|
474
|
+
"""Save a list of writes to the in-memory storage.
|
|
475
|
+
|
|
476
|
+
This method saves a list of writes to the in-memory storage. The writes are associated
|
|
477
|
+
with the provided config.
|
|
478
|
+
|
|
479
|
+
Args:
|
|
480
|
+
config: The config to associate with the writes.
|
|
481
|
+
writes: The writes to save.
|
|
482
|
+
task_id: Identifier for the task creating the writes.
|
|
483
|
+
task_path: Path of the task creating the writes.
|
|
484
|
+
|
|
485
|
+
Returns:
|
|
486
|
+
RunnableConfig: The updated config containing the saved writes' timestamp.
|
|
487
|
+
"""
|
|
488
|
+
thread_id = config["configurable"]["thread_id"]
|
|
489
|
+
checkpoint_ns = config["configurable"].get("checkpoint_ns", "")
|
|
490
|
+
checkpoint_id = config["configurable"]["checkpoint_id"]
|
|
491
|
+
outer_key = (thread_id, checkpoint_ns, checkpoint_id)
|
|
492
|
+
outer_writes_ = self.writes.get(outer_key)
|
|
493
|
+
for idx, (c, v) in enumerate(writes):
|
|
494
|
+
inner_key = (task_id, WRITES_IDX_MAP.get(c, idx))
|
|
495
|
+
if inner_key[1] >= 0 and outer_writes_ and inner_key in outer_writes_:
|
|
496
|
+
continue
|
|
497
|
+
|
|
498
|
+
self.writes[outer_key][inner_key] = (
|
|
499
|
+
task_id,
|
|
500
|
+
c,
|
|
501
|
+
self.serde.dumps_typed(v),
|
|
502
|
+
task_path,
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
def delete_thread(self, thread_id: str) -> None:
|
|
506
|
+
"""Delete all checkpoints and writes associated with a thread ID.
|
|
507
|
+
|
|
508
|
+
Args:
|
|
509
|
+
thread_id: The thread ID to delete.
|
|
510
|
+
|
|
511
|
+
Returns:
|
|
512
|
+
None
|
|
513
|
+
"""
|
|
514
|
+
if thread_id in self.storage:
|
|
515
|
+
del self.storage[thread_id]
|
|
516
|
+
for k in list(self.writes.keys()):
|
|
517
|
+
if k[0] == thread_id:
|
|
518
|
+
del self.writes[k]
|
|
519
|
+
for k in list(self.blobs.keys()):
|
|
520
|
+
if k[0] == thread_id:
|
|
521
|
+
del self.blobs[k]
|
|
522
|
+
|
|
523
|
+
async def aget_tuple(self, config: RunnableConfig) -> CheckpointTuple | None:
|
|
524
|
+
"""Asynchronous version of `get_tuple`.
|
|
525
|
+
|
|
526
|
+
This method is an asynchronous wrapper around `get_tuple` that runs the synchronous
|
|
527
|
+
method in a separate thread using asyncio.
|
|
528
|
+
|
|
529
|
+
Args:
|
|
530
|
+
config: The config to use for retrieving the checkpoint.
|
|
531
|
+
|
|
532
|
+
Returns:
|
|
533
|
+
The retrieved checkpoint tuple, or None if no matching checkpoint was found.
|
|
534
|
+
"""
|
|
535
|
+
return self.get_tuple(config)
|
|
536
|
+
|
|
537
|
+
async def alist(
|
|
538
|
+
self,
|
|
539
|
+
config: RunnableConfig | None,
|
|
540
|
+
*,
|
|
541
|
+
filter: dict[str, Any] | None = None,
|
|
542
|
+
before: RunnableConfig | None = None,
|
|
543
|
+
limit: int | None = None,
|
|
544
|
+
) -> AsyncIterator[CheckpointTuple]:
|
|
545
|
+
"""Asynchronous version of `list`.
|
|
546
|
+
|
|
547
|
+
This method is an asynchronous wrapper around `list` that runs the synchronous
|
|
548
|
+
method in a separate thread using asyncio.
|
|
549
|
+
|
|
550
|
+
Args:
|
|
551
|
+
config: The config to use for listing the checkpoints.
|
|
552
|
+
|
|
553
|
+
Yields:
|
|
554
|
+
An asynchronous iterator of checkpoint tuples.
|
|
555
|
+
"""
|
|
556
|
+
for item in self.list(config, filter=filter, before=before, limit=limit):
|
|
557
|
+
yield item
|
|
558
|
+
|
|
559
|
+
async def aput(
|
|
560
|
+
self,
|
|
561
|
+
config: RunnableConfig,
|
|
562
|
+
checkpoint: Checkpoint,
|
|
563
|
+
metadata: CheckpointMetadata,
|
|
564
|
+
new_versions: ChannelVersions,
|
|
565
|
+
) -> RunnableConfig:
|
|
566
|
+
"""Asynchronous version of `put`.
|
|
567
|
+
|
|
568
|
+
Args:
|
|
569
|
+
config: The config to associate with the checkpoint.
|
|
570
|
+
checkpoint: The checkpoint to save.
|
|
571
|
+
metadata: Additional metadata to save with the checkpoint.
|
|
572
|
+
new_versions: New versions as of this write
|
|
573
|
+
|
|
574
|
+
Returns:
|
|
575
|
+
RunnableConfig: The updated config containing the saved checkpoint's timestamp.
|
|
576
|
+
"""
|
|
577
|
+
return self.put(config, checkpoint, metadata, new_versions)
|
|
578
|
+
|
|
579
|
+
async def aput_writes(
|
|
580
|
+
self,
|
|
581
|
+
config: RunnableConfig,
|
|
582
|
+
writes: Sequence[tuple[str, Any]],
|
|
583
|
+
task_id: str,
|
|
584
|
+
task_path: str = "",
|
|
585
|
+
) -> None:
|
|
586
|
+
"""Asynchronous version of `put_writes`.
|
|
587
|
+
|
|
588
|
+
This method is an asynchronous wrapper around `put_writes` that runs the synchronous
|
|
589
|
+
method in a separate thread using asyncio.
|
|
590
|
+
|
|
591
|
+
Args:
|
|
592
|
+
config: The config to associate with the writes.
|
|
593
|
+
writes: The writes to save, each as a (channel, value) pair.
|
|
594
|
+
task_id: Identifier for the task creating the writes.
|
|
595
|
+
task_path: Path of the task creating the writes.
|
|
596
|
+
|
|
597
|
+
Returns:
|
|
598
|
+
None
|
|
599
|
+
"""
|
|
600
|
+
return self.put_writes(config, writes, task_id, task_path)
|
|
601
|
+
|
|
602
|
+
async def adelete_thread(self, thread_id: str) -> None:
|
|
603
|
+
"""Delete all checkpoints and writes associated with a thread ID.
|
|
604
|
+
|
|
605
|
+
Args:
|
|
606
|
+
thread_id: The thread ID to delete.
|
|
607
|
+
|
|
608
|
+
Returns:
|
|
609
|
+
None
|
|
610
|
+
"""
|
|
611
|
+
return self.delete_thread(thread_id)
|
|
612
|
+
|
|
613
|
+
def get_next_version(self, current: str | None, channel: None) -> str:
|
|
614
|
+
if current is None:
|
|
615
|
+
current_v = 0
|
|
616
|
+
elif isinstance(current, int):
|
|
617
|
+
current_v = current
|
|
618
|
+
else:
|
|
619
|
+
current_v = int(current.split(".")[0])
|
|
620
|
+
next_v = current_v + 1
|
|
621
|
+
next_h = random.random()
|
|
622
|
+
return f"{next_v:032}.{next_h:016}"
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
MemorySaver = InMemorySaver # Kept for backwards compatibility
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
class PersistentDict(defaultdict):
|
|
629
|
+
"""Persistent dictionary with an API compatible with shelve and anydbm.
|
|
630
|
+
|
|
631
|
+
The dict is kept in memory, so the dictionary operations run as fast as
|
|
632
|
+
a regular dictionary.
|
|
633
|
+
|
|
634
|
+
Write to disk is delayed until close or sync (similar to gdbm's fast mode).
|
|
635
|
+
|
|
636
|
+
Input file format is automatically discovered.
|
|
637
|
+
Output file format is selectable between pickle, json, and csv.
|
|
638
|
+
All three serialization formats are backed by fast C implementations.
|
|
639
|
+
|
|
640
|
+
Adapted from https://code.activestate.com/recipes/576642-persistent-dict-with-multiple-standard-file-format/
|
|
641
|
+
|
|
642
|
+
"""
|
|
643
|
+
|
|
644
|
+
def __init__(self, *args: Any, filename: str, **kwds: Any) -> None:
|
|
645
|
+
self.flag = "c" # r=readonly, c=create, or n=new
|
|
646
|
+
self.mode = None # None or an octal triple like 0644
|
|
647
|
+
self.format = "pickle" # 'csv', 'json', or 'pickle'
|
|
648
|
+
self.filename = filename
|
|
649
|
+
super().__init__(*args, **kwds)
|
|
650
|
+
|
|
651
|
+
def sync(self) -> None:
|
|
652
|
+
"Write dict to disk"
|
|
653
|
+
if self.flag == "r":
|
|
654
|
+
return
|
|
655
|
+
tempname = self.filename + ".tmp"
|
|
656
|
+
fileobj = open(tempname, "wb" if self.format == "pickle" else "w")
|
|
657
|
+
try:
|
|
658
|
+
self.dump(fileobj)
|
|
659
|
+
except Exception:
|
|
660
|
+
os.remove(tempname)
|
|
661
|
+
raise
|
|
662
|
+
finally:
|
|
663
|
+
fileobj.close()
|
|
664
|
+
shutil.move(tempname, self.filename) # atomic commit
|
|
665
|
+
if self.mode is not None:
|
|
666
|
+
os.chmod(self.filename, self.mode)
|
|
667
|
+
|
|
668
|
+
def close(self) -> None:
|
|
669
|
+
self.sync()
|
|
670
|
+
self.clear()
|
|
671
|
+
|
|
672
|
+
def __enter__(self) -> PersistentDict:
|
|
673
|
+
return self
|
|
674
|
+
|
|
675
|
+
def __exit__(self, *exc_info: Any) -> None:
|
|
676
|
+
self.close()
|
|
677
|
+
|
|
678
|
+
def dump(self, fileobj: Any) -> None:
|
|
679
|
+
if self.format == "pickle":
|
|
680
|
+
pickle.dump(dict(self), fileobj, 2)
|
|
681
|
+
else:
|
|
682
|
+
raise NotImplementedError("Unknown format: " + repr(self.format))
|
|
683
|
+
|
|
684
|
+
def load(self) -> None:
|
|
685
|
+
# try formats from most restrictive to least restrictive
|
|
686
|
+
if self.flag == "n":
|
|
687
|
+
return
|
|
688
|
+
with open(self.filename, "rb" if self.format == "pickle" else "r") as fileobj:
|
|
689
|
+
for loader in (pickle.load,):
|
|
690
|
+
fileobj.seek(0)
|
|
691
|
+
try:
|
|
692
|
+
return self.update(loader(fileobj))
|
|
693
|
+
except EOFError:
|
|
694
|
+
return
|
|
695
|
+
except Exception:
|
|
696
|
+
logger.error(f"Failed to load file: {fileobj.name}")
|
|
697
|
+
raise
|
|
698
|
+
raise ValueError("File not in a supported format")
|
|
File without changes
|