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,437 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import uuid
|
|
4
|
+
import warnings
|
|
5
|
+
from collections.abc import Callable, Sequence
|
|
6
|
+
from functools import partial
|
|
7
|
+
from typing import (
|
|
8
|
+
Annotated,
|
|
9
|
+
Any,
|
|
10
|
+
Literal,
|
|
11
|
+
cast,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
from coco_core.messages import (
|
|
15
|
+
AnyMessage,
|
|
16
|
+
BaseMessage,
|
|
17
|
+
BaseMessageChunk,
|
|
18
|
+
MessageLikeRepresentation,
|
|
19
|
+
RemoveMessage,
|
|
20
|
+
convert_to_messages,
|
|
21
|
+
message_chunk_to_message,
|
|
22
|
+
)
|
|
23
|
+
from typing_extensions import TypedDict, deprecated
|
|
24
|
+
|
|
25
|
+
from coco_graph._internal._constants import CONF, CONFIG_KEY_SEND, NS_SEP
|
|
26
|
+
from coco_graph.graph.state import StateGraph
|
|
27
|
+
from coco_graph.warnings import CocoGraphDeprecatedSinceV10
|
|
28
|
+
|
|
29
|
+
__all__ = (
|
|
30
|
+
"add_messages",
|
|
31
|
+
"MessagesState",
|
|
32
|
+
"MessageGraph",
|
|
33
|
+
"REMOVE_ALL_MESSAGES",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
Messages = list[MessageLikeRepresentation] | MessageLikeRepresentation
|
|
37
|
+
|
|
38
|
+
REMOVE_ALL_MESSAGES = "__remove_all__"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _add_messages_wrapper(func: Callable) -> Callable[[Messages, Messages], Messages]:
|
|
42
|
+
def _add_messages(
|
|
43
|
+
left: Messages | None = None, right: Messages | None = None, **kwargs: Any
|
|
44
|
+
) -> Messages | Callable[[Messages, Messages], Messages]:
|
|
45
|
+
if left is not None and right is not None:
|
|
46
|
+
return func(left, right, **kwargs)
|
|
47
|
+
elif left is not None or right is not None:
|
|
48
|
+
msg = (
|
|
49
|
+
f"Must specify non-null arguments for both 'left' and 'right'. Only "
|
|
50
|
+
f"received: '{'left' if left else 'right'}'."
|
|
51
|
+
)
|
|
52
|
+
raise ValueError(msg)
|
|
53
|
+
else:
|
|
54
|
+
return partial(func, **kwargs)
|
|
55
|
+
|
|
56
|
+
_add_messages.__doc__ = func.__doc__
|
|
57
|
+
return cast(Callable[[Messages, Messages], Messages], _add_messages)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@_add_messages_wrapper
|
|
61
|
+
def add_messages(
|
|
62
|
+
left: Messages,
|
|
63
|
+
right: Messages,
|
|
64
|
+
*,
|
|
65
|
+
format: Literal["trustcoco-openai"] | None = None,
|
|
66
|
+
) -> Messages:
|
|
67
|
+
"""Merges two lists of messages, updating existing messages by ID.
|
|
68
|
+
|
|
69
|
+
By default, this ensures the state is "append-only", unless the
|
|
70
|
+
new message has the same ID as an existing message.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
left: The base list of `Messages`.
|
|
74
|
+
right: The list of `Messages` (or single `Message`) to merge
|
|
75
|
+
into the base list.
|
|
76
|
+
format: The format to return messages in. If `None` then `Messages` will be
|
|
77
|
+
returned as is. If `trustcoco-openai` then `Messages` will be returned as
|
|
78
|
+
`BaseMessage` objects with their contents formatted to match OpenAI message
|
|
79
|
+
format, meaning contents can be string, `'text'` blocks, or `'image_url'` blocks
|
|
80
|
+
and tool responses are returned as their own `ToolMessage` objects.
|
|
81
|
+
|
|
82
|
+
!!! important "Requirement"
|
|
83
|
+
|
|
84
|
+
Must have `trustcoco-core>=0.3.11` installed to use this feature.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
A new list of messages with the messages from `right` merged into `left`.
|
|
88
|
+
If a message in `right` has the same ID as a message in `left`, the
|
|
89
|
+
message from `right` will replace the message from `left`.
|
|
90
|
+
|
|
91
|
+
Example: Basic usage
|
|
92
|
+
```python
|
|
93
|
+
from coco_core.messages import AIMessage, HumanMessage
|
|
94
|
+
|
|
95
|
+
msgs1 = [HumanMessage(content="Hello", id="1")]
|
|
96
|
+
msgs2 = [AIMessage(content="Hi there!", id="2")]
|
|
97
|
+
add_messages(msgs1, msgs2)
|
|
98
|
+
# [HumanMessage(content='Hello', id='1'), AIMessage(content='Hi there!', id='2')]
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Example: Overwrite existing message
|
|
102
|
+
```python
|
|
103
|
+
msgs1 = [HumanMessage(content="Hello", id="1")]
|
|
104
|
+
msgs2 = [HumanMessage(content="Hello again", id="1")]
|
|
105
|
+
add_messages(msgs1, msgs2)
|
|
106
|
+
# [HumanMessage(content='Hello again', id='1')]
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Example: Use in a StateGraph
|
|
110
|
+
```python
|
|
111
|
+
from typing import Annotated
|
|
112
|
+
from typing_extensions import TypedDict
|
|
113
|
+
from coco_graph.graph import StateGraph
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class State(TypedDict):
|
|
117
|
+
messages: Annotated[list, add_messages]
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
builder = StateGraph(State)
|
|
121
|
+
builder.add_node("chatbot", lambda state: {"messages": [("assistant", "Hello")]})
|
|
122
|
+
builder.set_entry_point("chatbot")
|
|
123
|
+
builder.set_finish_point("chatbot")
|
|
124
|
+
graph = builder.compile()
|
|
125
|
+
graph.invoke({})
|
|
126
|
+
# {'messages': [AIMessage(content='Hello', id=...)]}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Example: Use OpenAI message format
|
|
130
|
+
```python
|
|
131
|
+
from typing import Annotated
|
|
132
|
+
from typing_extensions import TypedDict
|
|
133
|
+
from coco_graph.graph import StateGraph, add_messages
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class State(TypedDict):
|
|
137
|
+
messages: Annotated[list, add_messages(format="trustcoco-openai")]
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def chatbot_node(state: State) -> list:
|
|
141
|
+
return {
|
|
142
|
+
"messages": [
|
|
143
|
+
{
|
|
144
|
+
"role": "user",
|
|
145
|
+
"content": [
|
|
146
|
+
{
|
|
147
|
+
"type": "text",
|
|
148
|
+
"text": "Here's an image:",
|
|
149
|
+
"cache_control": {"type": "ephemeral"},
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
"type": "image",
|
|
153
|
+
"source": {
|
|
154
|
+
"type": "base64",
|
|
155
|
+
"media_type": "image/jpeg",
|
|
156
|
+
"data": "1234",
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
},
|
|
161
|
+
]
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
builder = StateGraph(State)
|
|
166
|
+
builder.add_node("chatbot", chatbot_node)
|
|
167
|
+
builder.set_entry_point("chatbot")
|
|
168
|
+
builder.set_finish_point("chatbot")
|
|
169
|
+
graph = builder.compile()
|
|
170
|
+
graph.invoke({"messages": []})
|
|
171
|
+
# {
|
|
172
|
+
# 'messages': [
|
|
173
|
+
# HumanMessage(
|
|
174
|
+
# content=[
|
|
175
|
+
# {"type": "text", "text": "Here's an image:"},
|
|
176
|
+
# {
|
|
177
|
+
# "type": "image_url",
|
|
178
|
+
# "image_url": {"url": "data:image/jpeg;base64,1234"},
|
|
179
|
+
# },
|
|
180
|
+
# ],
|
|
181
|
+
# ),
|
|
182
|
+
# ]
|
|
183
|
+
# }
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
"""
|
|
187
|
+
remove_all_idx = None
|
|
188
|
+
# coerce to list
|
|
189
|
+
if not isinstance(left, list):
|
|
190
|
+
left = [left] # type: ignore[assignment]
|
|
191
|
+
if not isinstance(right, list):
|
|
192
|
+
right = [right] # type: ignore[assignment]
|
|
193
|
+
# coerce to message
|
|
194
|
+
left = [
|
|
195
|
+
message_chunk_to_message(cast(BaseMessageChunk, m))
|
|
196
|
+
for m in convert_to_messages(left)
|
|
197
|
+
]
|
|
198
|
+
right = [
|
|
199
|
+
message_chunk_to_message(cast(BaseMessageChunk, m))
|
|
200
|
+
for m in convert_to_messages(right)
|
|
201
|
+
]
|
|
202
|
+
# assign missing ids
|
|
203
|
+
for m in left:
|
|
204
|
+
if m.id is None:
|
|
205
|
+
m.id = str(uuid.uuid4())
|
|
206
|
+
for idx, m in enumerate(right):
|
|
207
|
+
if m.id is None:
|
|
208
|
+
m.id = str(uuid.uuid4())
|
|
209
|
+
if isinstance(m, RemoveMessage) and m.id == REMOVE_ALL_MESSAGES:
|
|
210
|
+
remove_all_idx = idx
|
|
211
|
+
|
|
212
|
+
if remove_all_idx is not None:
|
|
213
|
+
return right[remove_all_idx + 1 :]
|
|
214
|
+
|
|
215
|
+
# merge
|
|
216
|
+
merged = left.copy()
|
|
217
|
+
merged_by_id = {m.id: i for i, m in enumerate(merged)}
|
|
218
|
+
ids_to_remove = set()
|
|
219
|
+
for m in right:
|
|
220
|
+
if (existing_idx := merged_by_id.get(m.id)) is not None:
|
|
221
|
+
if isinstance(m, RemoveMessage):
|
|
222
|
+
ids_to_remove.add(m.id)
|
|
223
|
+
else:
|
|
224
|
+
ids_to_remove.discard(m.id)
|
|
225
|
+
merged[existing_idx] = m
|
|
226
|
+
else:
|
|
227
|
+
if isinstance(m, RemoveMessage):
|
|
228
|
+
raise ValueError(
|
|
229
|
+
f"Attempting to delete a message with an ID that doesn't exist ('{m.id}')"
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
merged_by_id[m.id] = len(merged)
|
|
233
|
+
merged.append(m)
|
|
234
|
+
merged = [m for m in merged if m.id not in ids_to_remove]
|
|
235
|
+
|
|
236
|
+
if format == "trustcoco-openai":
|
|
237
|
+
merged = _format_messages(merged)
|
|
238
|
+
elif format:
|
|
239
|
+
msg = f"Unrecognized {format=}. Expected one of 'trustcoco-openai', None."
|
|
240
|
+
raise ValueError(msg)
|
|
241
|
+
else:
|
|
242
|
+
pass
|
|
243
|
+
|
|
244
|
+
return merged
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _messages_delta_reducer(
|
|
248
|
+
state: list[AnyMessage], writes: list[list[AnyMessage]]
|
|
249
|
+
) -> list[AnyMessage]:
|
|
250
|
+
"""**Experimental.** Batch reducer for use with `DeltaChannel`.
|
|
251
|
+
|
|
252
|
+
Processes all writes in one pass — dedup by ID, `RemoveMessage`
|
|
253
|
+
tombstoning — without calling `add_messages`.
|
|
254
|
+
|
|
255
|
+
This reducer is batching-invariant, as required by `DeltaChannel`:
|
|
256
|
+
`reducer(reducer(state, xs), ys) == reducer(state, xs + ys)`.
|
|
257
|
+
|
|
258
|
+
Raw dict / string / tuple inputs are coerced to typed `BaseMessage`
|
|
259
|
+
objects so that HTTP-driven graphs work without a separate coercion
|
|
260
|
+
step. This is not full `add_messages` parity — `REMOVE_ALL_MESSAGES`,
|
|
261
|
+
unknown-id `RemoveMessage` errors, missing-id UUID assignment, and
|
|
262
|
+
`BaseMessageChunk` conversion are not handled here.
|
|
263
|
+
|
|
264
|
+
Example::
|
|
265
|
+
|
|
266
|
+
from typing import Annotated
|
|
267
|
+
from coco_graph.channels.delta import DeltaChannel
|
|
268
|
+
from coco_graph.graph.message import _messages_delta_reducer
|
|
269
|
+
|
|
270
|
+
class State(TypedDict):
|
|
271
|
+
messages: Annotated[list, DeltaChannel(_messages_delta_reducer)]
|
|
272
|
+
"""
|
|
273
|
+
|
|
274
|
+
# Each write is either a list of message-likes or a single message-like
|
|
275
|
+
# (BaseMessage / dict / str / tuple). Only lists flatten; everything
|
|
276
|
+
# else is one message.
|
|
277
|
+
flat: list[Any] = []
|
|
278
|
+
for w in writes:
|
|
279
|
+
if isinstance(w, list):
|
|
280
|
+
flat.extend(w)
|
|
281
|
+
else:
|
|
282
|
+
flat.append(w)
|
|
283
|
+
# Steady state: the reducer's own output is already typed, so skip
|
|
284
|
+
# `convert_to_messages` on state when the first element is a BaseMessage.
|
|
285
|
+
# Only raw input (initial dicts, deserialized blobs) hits the slow path.
|
|
286
|
+
if state and isinstance(state[0], BaseMessage):
|
|
287
|
+
state_msgs = state
|
|
288
|
+
else:
|
|
289
|
+
state_msgs = cast("list[AnyMessage]", convert_to_messages(state))
|
|
290
|
+
msgs = cast("list[AnyMessage]", convert_to_messages(flat))
|
|
291
|
+
|
|
292
|
+
index: dict[str, int] = {
|
|
293
|
+
m.id: i for i, m in enumerate(state_msgs) if m.id is not None
|
|
294
|
+
}
|
|
295
|
+
result: list[AnyMessage | None] = list(state_msgs)
|
|
296
|
+
for msg in msgs:
|
|
297
|
+
mid = msg.id
|
|
298
|
+
if mid is None:
|
|
299
|
+
result.append(msg)
|
|
300
|
+
elif isinstance(msg, RemoveMessage):
|
|
301
|
+
if mid in index:
|
|
302
|
+
result[index[mid]] = None
|
|
303
|
+
del index[mid]
|
|
304
|
+
elif mid in index:
|
|
305
|
+
result[index[mid]] = msg
|
|
306
|
+
else:
|
|
307
|
+
index[mid] = len(result)
|
|
308
|
+
result.append(msg)
|
|
309
|
+
return [m for m in result if m is not None]
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
@deprecated(
|
|
313
|
+
"MessageGraph is deprecated in coco_graph 1.0.0, to be removed in 2.0.0. Please use StateGraph with a `messages` key instead.",
|
|
314
|
+
category=None,
|
|
315
|
+
)
|
|
316
|
+
class MessageGraph(StateGraph):
|
|
317
|
+
"""A StateGraph where every node receives a list of messages as input and returns one or more messages as output.
|
|
318
|
+
|
|
319
|
+
MessageGraph is a subclass of StateGraph whose entire state is a single, append-only* list of messages.
|
|
320
|
+
Each node in a MessageGraph takes a list of messages as input and returns zero or more
|
|
321
|
+
messages as output. The `add_messages` function is used to merge the output messages from each node
|
|
322
|
+
into the existing list of messages in the graph's state.
|
|
323
|
+
|
|
324
|
+
Examples:
|
|
325
|
+
```pycon
|
|
326
|
+
>>> from coco_graph.graph.message import MessageGraph
|
|
327
|
+
...
|
|
328
|
+
>>> builder = MessageGraph()
|
|
329
|
+
>>> builder.add_node("chatbot", lambda state: [("assistant", "Hello!")])
|
|
330
|
+
>>> builder.set_entry_point("chatbot")
|
|
331
|
+
>>> builder.set_finish_point("chatbot")
|
|
332
|
+
>>> builder.compile().invoke([("user", "Hi there.")])
|
|
333
|
+
[HumanMessage(content="Hi there.", id='...'), AIMessage(content="Hello!", id='...')]
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
```pycon
|
|
337
|
+
>>> from coco_core.messages import AIMessage, HumanMessage, ToolMessage
|
|
338
|
+
>>> from coco_graph.graph.message import MessageGraph
|
|
339
|
+
...
|
|
340
|
+
>>> builder = MessageGraph()
|
|
341
|
+
>>> builder.add_node(
|
|
342
|
+
... "chatbot",
|
|
343
|
+
... lambda state: [
|
|
344
|
+
... AIMessage(
|
|
345
|
+
... content="Hello!",
|
|
346
|
+
... tool_calls=[{"name": "search", "id": "123", "args": {"query": "X"}}],
|
|
347
|
+
... )
|
|
348
|
+
... ],
|
|
349
|
+
... )
|
|
350
|
+
>>> builder.add_node(
|
|
351
|
+
... "search", lambda state: [ToolMessage(content="Searching...", tool_call_id="123")]
|
|
352
|
+
... )
|
|
353
|
+
>>> builder.set_entry_point("chatbot")
|
|
354
|
+
>>> builder.add_edge("chatbot", "search")
|
|
355
|
+
>>> builder.set_finish_point("search")
|
|
356
|
+
>>> builder.compile().invoke([HumanMessage(content="Hi there. Can you search for X?")])
|
|
357
|
+
{'messages': [HumanMessage(content="Hi there. Can you search for X?", id='b8b7d8f4-7f4d-4f4d-9c1d-f8b8d8f4d9c1'),
|
|
358
|
+
AIMessage(content="Hello!", id='f4d9c1d8-8d8f-4d9c-b8b7-d8f4f4d9c1d8'),
|
|
359
|
+
ToolMessage(content="Searching...", id='d8f4f4d9-c1d8-4f4d-b8b7-d8f4f4d9c1d8', tool_call_id="123")]}
|
|
360
|
+
```
|
|
361
|
+
"""
|
|
362
|
+
|
|
363
|
+
def __init__(self) -> None:
|
|
364
|
+
warnings.warn(
|
|
365
|
+
"MessageGraph is deprecated in CocoGraph v1.0.0, to be removed in v2.0.0. Please use StateGraph with a `messages` key instead.",
|
|
366
|
+
category=CocoGraphDeprecatedSinceV10,
|
|
367
|
+
stacklevel=2,
|
|
368
|
+
)
|
|
369
|
+
super().__init__(Annotated[list[AnyMessage], add_messages]) # type: ignore[arg-type]
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
class MessagesState(TypedDict):
|
|
373
|
+
messages: Annotated[list[AnyMessage], add_messages]
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def _format_messages(messages: Sequence[BaseMessage]) -> list[BaseMessage]:
|
|
377
|
+
try:
|
|
378
|
+
from coco_core.messages import convert_to_openai_messages
|
|
379
|
+
except ImportError:
|
|
380
|
+
msg = (
|
|
381
|
+
"Must have trustcoco-core>=0.3.11 installed to use automatic message "
|
|
382
|
+
"formatting (format='trustcoco-openai'). Please update your trustcoco-core "
|
|
383
|
+
"version or remove the 'format' flag. Returning un-formatted "
|
|
384
|
+
"messages."
|
|
385
|
+
)
|
|
386
|
+
warnings.warn(msg)
|
|
387
|
+
return list(messages)
|
|
388
|
+
else:
|
|
389
|
+
return convert_to_messages(convert_to_openai_messages(messages))
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def push_message(
|
|
393
|
+
message: MessageLikeRepresentation | BaseMessageChunk,
|
|
394
|
+
*,
|
|
395
|
+
state_key: str | None = "messages",
|
|
396
|
+
) -> AnyMessage:
|
|
397
|
+
"""Write a message manually to the `messages` / `messages-tuple` stream mode.
|
|
398
|
+
|
|
399
|
+
Will automatically write to the channel specified in the `state_key` unless `state_key` is `None`.
|
|
400
|
+
"""
|
|
401
|
+
|
|
402
|
+
from coco_core.callbacks.base import (
|
|
403
|
+
BaseCallbackHandler,
|
|
404
|
+
BaseCallbackManager,
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
from coco_graph.config import get_config
|
|
408
|
+
from coco_graph.pregel._messages import StreamMessagesHandler
|
|
409
|
+
|
|
410
|
+
config = get_config()
|
|
411
|
+
message = next(x for x in convert_to_messages([message]))
|
|
412
|
+
|
|
413
|
+
if message.id is None:
|
|
414
|
+
raise ValueError("Message ID is required")
|
|
415
|
+
|
|
416
|
+
if isinstance(config["callbacks"], BaseCallbackManager):
|
|
417
|
+
manager = config["callbacks"]
|
|
418
|
+
handlers = manager.handlers
|
|
419
|
+
elif isinstance(config["callbacks"], list) and all(
|
|
420
|
+
isinstance(x, BaseCallbackHandler) for x in config["callbacks"]
|
|
421
|
+
):
|
|
422
|
+
handlers = config["callbacks"]
|
|
423
|
+
|
|
424
|
+
if stream_handler := next(
|
|
425
|
+
(x for x in handlers if isinstance(x, StreamMessagesHandler)), None
|
|
426
|
+
):
|
|
427
|
+
metadata = config["metadata"]
|
|
428
|
+
message_meta = (
|
|
429
|
+
tuple(cast(str, metadata["coco_graph_checkpoint_ns"]).split(NS_SEP)),
|
|
430
|
+
metadata,
|
|
431
|
+
)
|
|
432
|
+
stream_handler._emit(message_meta, message, dedupe=False)
|
|
433
|
+
|
|
434
|
+
if state_key:
|
|
435
|
+
config[CONF][CONFIG_KEY_SEND]([(state_key, message)])
|
|
436
|
+
|
|
437
|
+
return message
|