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
coco_core/utils/aiter.py
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
"""Asynchronous iterator utilities.
|
|
2
|
+
|
|
3
|
+
Adapted from
|
|
4
|
+
https://github.com/maxfischer2781/asyncstdlib/blob/master/asyncstdlib/itertools.py
|
|
5
|
+
MIT License.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from collections import deque
|
|
9
|
+
from collections.abc import (
|
|
10
|
+
AsyncGenerator,
|
|
11
|
+
AsyncIterable,
|
|
12
|
+
AsyncIterator,
|
|
13
|
+
Awaitable,
|
|
14
|
+
Callable,
|
|
15
|
+
Iterator,
|
|
16
|
+
)
|
|
17
|
+
from contextlib import AbstractAsyncContextManager
|
|
18
|
+
from types import TracebackType
|
|
19
|
+
from typing import (
|
|
20
|
+
Any,
|
|
21
|
+
Generic,
|
|
22
|
+
TypeVar,
|
|
23
|
+
cast,
|
|
24
|
+
overload,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
from typing_extensions import override
|
|
28
|
+
|
|
29
|
+
from coco_core._api.deprecation import deprecated
|
|
30
|
+
|
|
31
|
+
T = TypeVar("T")
|
|
32
|
+
|
|
33
|
+
_no_default = object()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# https://github.com/python/cpython/blob/main/Lib/test/test_asyncgen.py#L54
|
|
37
|
+
@deprecated(since="1.1.2", removal="2.0.0")
|
|
38
|
+
def py_anext(
|
|
39
|
+
iterator: AsyncIterator[T], default: T | Any = _no_default
|
|
40
|
+
) -> Awaitable[T | Any | None]:
|
|
41
|
+
"""Pure-Python implementation of `anext()` for testing purposes.
|
|
42
|
+
|
|
43
|
+
Closely matches the builtin `anext()` C implementation.
|
|
44
|
+
|
|
45
|
+
Can be used to compare the built-in implementation of the inner coroutines machinery
|
|
46
|
+
to C-implementation of `__anext__()` and `send()` or `throw()` on the returned
|
|
47
|
+
generator.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
iterator: The async iterator to advance.
|
|
51
|
+
default: The value to return if the iterator is exhausted.
|
|
52
|
+
|
|
53
|
+
If not provided, a `StopAsyncIteration` exception is raised.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
The next value from the iterator, or the default value if the iterator is
|
|
57
|
+
exhausted.
|
|
58
|
+
|
|
59
|
+
Raises:
|
|
60
|
+
TypeError: If the iterator is not an async iterator.
|
|
61
|
+
"""
|
|
62
|
+
try:
|
|
63
|
+
__anext__ = cast(
|
|
64
|
+
"Callable[[AsyncIterator[T]], Awaitable[T]]", type(iterator).__anext__
|
|
65
|
+
)
|
|
66
|
+
except AttributeError as e:
|
|
67
|
+
msg = f"{iterator!r} is not an async iterator"
|
|
68
|
+
raise TypeError(msg) from e
|
|
69
|
+
|
|
70
|
+
if default is _no_default:
|
|
71
|
+
return __anext__(iterator)
|
|
72
|
+
|
|
73
|
+
async def anext_impl() -> T | Any:
|
|
74
|
+
try:
|
|
75
|
+
# The C code is way more low-level than this, as it implements
|
|
76
|
+
# all methods of the iterator protocol. In this implementation
|
|
77
|
+
# we're relying on higher-level coroutine concepts, but that's
|
|
78
|
+
# exactly what we want -- crosstest pure-Python high-level
|
|
79
|
+
# implementation and low-level C anext() iterators.
|
|
80
|
+
return await __anext__(iterator)
|
|
81
|
+
except StopAsyncIteration:
|
|
82
|
+
return default
|
|
83
|
+
|
|
84
|
+
return anext_impl()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class NoLock:
|
|
88
|
+
"""Dummy lock that provides the proper interface but no protection."""
|
|
89
|
+
|
|
90
|
+
async def __aenter__(self) -> None:
|
|
91
|
+
"""Do nothing."""
|
|
92
|
+
|
|
93
|
+
async def __aexit__(
|
|
94
|
+
self,
|
|
95
|
+
exc_type: type[BaseException] | None,
|
|
96
|
+
exc_val: BaseException | None,
|
|
97
|
+
exc_tb: TracebackType | None,
|
|
98
|
+
) -> bool:
|
|
99
|
+
"""Return False, exception not suppressed."""
|
|
100
|
+
return False
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
async def tee_peer(
|
|
104
|
+
iterator: AsyncIterator[T],
|
|
105
|
+
# the buffer specific to this peer
|
|
106
|
+
buffer: deque[T],
|
|
107
|
+
# the buffers of all peers, including our own
|
|
108
|
+
peers: list[deque[T]],
|
|
109
|
+
lock: AbstractAsyncContextManager[Any],
|
|
110
|
+
) -> AsyncGenerator[T, None]:
|
|
111
|
+
"""An individual iterator of a `tee`.
|
|
112
|
+
|
|
113
|
+
This function is a generator that yields items from the shared iterator
|
|
114
|
+
`iterator`. It buffers items until the least advanced iterator has yielded them as
|
|
115
|
+
well.
|
|
116
|
+
|
|
117
|
+
The buffer is shared with all other peers.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
iterator: The shared iterator.
|
|
121
|
+
buffer: The buffer for this peer.
|
|
122
|
+
peers: The buffers of all peers.
|
|
123
|
+
lock: The lock to synchronise access to the shared buffers.
|
|
124
|
+
|
|
125
|
+
Yields:
|
|
126
|
+
The next item from the shared iterator.
|
|
127
|
+
"""
|
|
128
|
+
try:
|
|
129
|
+
while True:
|
|
130
|
+
if not buffer:
|
|
131
|
+
async with lock:
|
|
132
|
+
# Another peer produced an item while we were waiting for the lock.
|
|
133
|
+
# Proceed with the next loop iteration to yield the item.
|
|
134
|
+
if buffer:
|
|
135
|
+
continue
|
|
136
|
+
try:
|
|
137
|
+
item = await anext(iterator)
|
|
138
|
+
except StopAsyncIteration:
|
|
139
|
+
break
|
|
140
|
+
else:
|
|
141
|
+
# Append to all buffers, including our own. We'll fetch our
|
|
142
|
+
# item from the buffer again, instead of yielding it directly.
|
|
143
|
+
# This ensures the proper item ordering if any of our peers
|
|
144
|
+
# are fetching items concurrently. They may have buffered their
|
|
145
|
+
# item already.
|
|
146
|
+
for peer_buffer in peers:
|
|
147
|
+
peer_buffer.append(item)
|
|
148
|
+
yield buffer.popleft()
|
|
149
|
+
finally:
|
|
150
|
+
async with lock:
|
|
151
|
+
# this peer is done - remove its buffer
|
|
152
|
+
for idx, peer_buffer in enumerate(peers): # pragma: no branch
|
|
153
|
+
if peer_buffer is buffer:
|
|
154
|
+
peers.pop(idx)
|
|
155
|
+
break
|
|
156
|
+
# if we are the last peer, try and close the iterator
|
|
157
|
+
if not peers and hasattr(iterator, "aclose"):
|
|
158
|
+
await iterator.aclose()
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class Tee(Generic[T]):
|
|
162
|
+
"""Create `n` separate asynchronous iterators over `iterable`.
|
|
163
|
+
|
|
164
|
+
This splits a single `iterable` into multiple iterators, each providing
|
|
165
|
+
the same items in the same order.
|
|
166
|
+
|
|
167
|
+
All child iterators may advance separately but share the same items from `iterable`
|
|
168
|
+
-- when the most advanced iterator retrieves an item, it is buffered until the least
|
|
169
|
+
advanced iterator has yielded it as well.
|
|
170
|
+
|
|
171
|
+
A `tee` works lazily and can handle an infinite `iterable`, provided
|
|
172
|
+
that all iterators advance.
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
async def derivative(sensor_data):
|
|
176
|
+
previous, current = a.tee(sensor_data, n=2)
|
|
177
|
+
await a.anext(previous) # advance one iterator
|
|
178
|
+
return a.map(operator.sub, previous, current)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Unlike `itertools.tee`, `.tee` returns a custom type instead of a `tuple`. Like a
|
|
182
|
+
tuple, it can be indexed, iterated and unpacked to get the child iterators. In
|
|
183
|
+
addition, its `.tee.aclose` method immediately closes all children, and it can be
|
|
184
|
+
used in an `async with` context for the same effect.
|
|
185
|
+
|
|
186
|
+
If `iterable` is an iterator and read elsewhere, `tee` will *not* provide these
|
|
187
|
+
items. Also, `tee` must internally buffer each item until the last iterator has
|
|
188
|
+
yielded it; if the most and least advanced iterator differ by most data, using a
|
|
189
|
+
`list` is more efficient (but not lazy).
|
|
190
|
+
|
|
191
|
+
If the underlying iterable is concurrency safe (`anext` may be awaited concurrently)
|
|
192
|
+
the resulting iterators are concurrency safe as well. Otherwise, the iterators are
|
|
193
|
+
safe if there is only ever one single "most advanced" iterator.
|
|
194
|
+
|
|
195
|
+
To enforce sequential use of `anext`, provide a `lock`
|
|
196
|
+
|
|
197
|
+
- e.g. an `asyncio.Lock` instance in an `asyncio` application - and access is
|
|
198
|
+
automatically synchronised.
|
|
199
|
+
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
def __init__(
|
|
203
|
+
self,
|
|
204
|
+
iterable: AsyncIterator[T],
|
|
205
|
+
n: int = 2,
|
|
206
|
+
*,
|
|
207
|
+
lock: AbstractAsyncContextManager[Any] | None = None,
|
|
208
|
+
):
|
|
209
|
+
"""Create a `tee`.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
iterable: The iterable to split.
|
|
213
|
+
n: The number of iterators to create.
|
|
214
|
+
lock: The lock to synchronise access to the shared buffers.
|
|
215
|
+
|
|
216
|
+
"""
|
|
217
|
+
self._iterator = aiter(iterable)
|
|
218
|
+
self._buffers: list[deque[T]] = [deque() for _ in range(n)]
|
|
219
|
+
self._children = tuple(
|
|
220
|
+
tee_peer(
|
|
221
|
+
iterator=self._iterator,
|
|
222
|
+
buffer=buffer,
|
|
223
|
+
peers=self._buffers,
|
|
224
|
+
lock=lock if lock is not None else NoLock(),
|
|
225
|
+
)
|
|
226
|
+
for buffer in self._buffers
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
def __len__(self) -> int:
|
|
230
|
+
"""Return the number of child iterators."""
|
|
231
|
+
return len(self._children)
|
|
232
|
+
|
|
233
|
+
@overload
|
|
234
|
+
def __getitem__(self, item: int) -> AsyncIterator[T]: ...
|
|
235
|
+
|
|
236
|
+
@overload
|
|
237
|
+
def __getitem__(self, item: slice) -> tuple[AsyncIterator[T], ...]: ...
|
|
238
|
+
|
|
239
|
+
def __getitem__(
|
|
240
|
+
self, item: int | slice
|
|
241
|
+
) -> AsyncIterator[T] | tuple[AsyncIterator[T], ...]:
|
|
242
|
+
"""Return the child iterator(s) for the given index or slice."""
|
|
243
|
+
return self._children[item]
|
|
244
|
+
|
|
245
|
+
def __iter__(self) -> Iterator[AsyncIterator[T]]:
|
|
246
|
+
"""Iterate over the child iterators.
|
|
247
|
+
|
|
248
|
+
Yields:
|
|
249
|
+
The child iterators.
|
|
250
|
+
"""
|
|
251
|
+
yield from self._children
|
|
252
|
+
|
|
253
|
+
async def __aenter__(self) -> "Tee[T]":
|
|
254
|
+
"""Return the tee instance."""
|
|
255
|
+
return self
|
|
256
|
+
|
|
257
|
+
async def __aexit__(
|
|
258
|
+
self,
|
|
259
|
+
exc_type: type[BaseException] | None,
|
|
260
|
+
exc_val: BaseException | None,
|
|
261
|
+
exc_tb: TracebackType | None,
|
|
262
|
+
) -> bool:
|
|
263
|
+
"""Close all child iterators.
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
`False`, exceptions not suppressed.
|
|
267
|
+
"""
|
|
268
|
+
await self.aclose()
|
|
269
|
+
return False
|
|
270
|
+
|
|
271
|
+
async def aclose(self) -> None:
|
|
272
|
+
"""Async close all child iterators."""
|
|
273
|
+
for child in self._children:
|
|
274
|
+
await child.aclose()
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
atee = Tee
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
class aclosing(AbstractAsyncContextManager[Any]): # noqa: N801
|
|
281
|
+
"""Async context manager to wrap an `AsyncGenerator` that has a `aclose()` method.
|
|
282
|
+
|
|
283
|
+
Code like this:
|
|
284
|
+
|
|
285
|
+
```python
|
|
286
|
+
async with aclosing(<module>.fetch(<arguments>)) as agen:
|
|
287
|
+
<block>
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
...is equivalent to this:
|
|
291
|
+
|
|
292
|
+
```python
|
|
293
|
+
agen = <module>.fetch(<arguments>)
|
|
294
|
+
try:
|
|
295
|
+
<block>
|
|
296
|
+
finally:
|
|
297
|
+
await agen.aclose()
|
|
298
|
+
|
|
299
|
+
```
|
|
300
|
+
"""
|
|
301
|
+
|
|
302
|
+
def __init__(self, thing: AsyncGenerator[Any, Any] | AsyncIterator[Any]) -> None:
|
|
303
|
+
"""Create the context manager.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
thing: The resource to wrap.
|
|
307
|
+
"""
|
|
308
|
+
self.thing = thing
|
|
309
|
+
|
|
310
|
+
@override
|
|
311
|
+
async def __aenter__(self) -> AsyncGenerator[Any, Any] | AsyncIterator[Any]:
|
|
312
|
+
return self.thing
|
|
313
|
+
|
|
314
|
+
@override
|
|
315
|
+
async def __aexit__(
|
|
316
|
+
self,
|
|
317
|
+
exc_type: type[BaseException] | None,
|
|
318
|
+
exc_value: BaseException | None,
|
|
319
|
+
traceback: TracebackType | None,
|
|
320
|
+
) -> None:
|
|
321
|
+
if hasattr(self.thing, "aclose"):
|
|
322
|
+
await self.thing.aclose()
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
async def abatch_iterate(
|
|
326
|
+
size: int | None, iterable: AsyncIterable[T]
|
|
327
|
+
) -> AsyncIterator[list[T]]:
|
|
328
|
+
"""Utility batching function for async iterables.
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
size: The size of the batch.
|
|
332
|
+
|
|
333
|
+
If `None`, returns a single batch.
|
|
334
|
+
iterable: The async iterable to batch.
|
|
335
|
+
|
|
336
|
+
Yields:
|
|
337
|
+
The batches.
|
|
338
|
+
|
|
339
|
+
Raises:
|
|
340
|
+
ValueError: If `size` is not `None` and is not a positive integer.
|
|
341
|
+
"""
|
|
342
|
+
if size is None:
|
|
343
|
+
single_batch = [el async for el in iterable]
|
|
344
|
+
if single_batch:
|
|
345
|
+
yield single_batch
|
|
346
|
+
return
|
|
347
|
+
if size <= 0:
|
|
348
|
+
msg = f"Batch size must be a positive integer, got {size}."
|
|
349
|
+
raise ValueError(msg)
|
|
350
|
+
batch: list[T] = []
|
|
351
|
+
async for element in iterable:
|
|
352
|
+
batch.append(element)
|
|
353
|
+
if len(batch) >= size:
|
|
354
|
+
yield batch
|
|
355
|
+
batch = []
|
|
356
|
+
if batch:
|
|
357
|
+
yield batch
|
coco_core/utils/env.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Utilities for environment variables."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def env_var_is_set(env_var: str) -> bool:
|
|
10
|
+
"""Check if an environment variable is set.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
env_var: The name of the environment variable.
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
`True` if the environment variable is set, `False` otherwise.
|
|
17
|
+
"""
|
|
18
|
+
return env_var in os.environ and os.environ[env_var] not in {
|
|
19
|
+
"",
|
|
20
|
+
"0",
|
|
21
|
+
"false",
|
|
22
|
+
"False",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_from_dict_or_env(
|
|
27
|
+
data: dict[str, Any],
|
|
28
|
+
key: str | list[str],
|
|
29
|
+
env_key: str,
|
|
30
|
+
default: str | None = None,
|
|
31
|
+
) -> str:
|
|
32
|
+
"""Get a value from a dictionary or an environment variable.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
data: The dictionary to look up the key in.
|
|
36
|
+
key: The key to look up in the dictionary.
|
|
37
|
+
|
|
38
|
+
This can be a list of keys to try in order.
|
|
39
|
+
env_key: The environment variable to look up if the key is not
|
|
40
|
+
in the dictionary.
|
|
41
|
+
default: The default value to return if the key is not in the dictionary
|
|
42
|
+
or the environment.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
The dict value or the environment variable value.
|
|
46
|
+
"""
|
|
47
|
+
if isinstance(key, (list, tuple)):
|
|
48
|
+
for k in key:
|
|
49
|
+
if value := data.get(k):
|
|
50
|
+
return str(value)
|
|
51
|
+
|
|
52
|
+
if isinstance(key, str) and key in data and data[key]:
|
|
53
|
+
return str(data[key])
|
|
54
|
+
|
|
55
|
+
key_for_err = key[0] if isinstance(key, (list, tuple)) else key
|
|
56
|
+
|
|
57
|
+
return get_from_env(key_for_err, env_key, default=default)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def get_from_env(key: str, env_key: str, default: str | None = None) -> str:
|
|
61
|
+
"""Get a value from a dictionary or an environment variable.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
key: The key to look up in the dictionary.
|
|
65
|
+
env_key: The environment variable to look up if the key is not
|
|
66
|
+
in the dictionary.
|
|
67
|
+
default: The default value to return if the key is not in the dictionary
|
|
68
|
+
or the environment.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
The value of the key.
|
|
72
|
+
|
|
73
|
+
Raises:
|
|
74
|
+
ValueError: If the key is not in the dictionary and no default value is
|
|
75
|
+
provided or if the environment variable is not set.
|
|
76
|
+
"""
|
|
77
|
+
if env_value := os.getenv(env_key):
|
|
78
|
+
return env_value
|
|
79
|
+
if default is not None:
|
|
80
|
+
return default
|
|
81
|
+
msg = (
|
|
82
|
+
f"Did not find {key}, please add an environment variable"
|
|
83
|
+
f" `{env_key}` which contains it, or pass"
|
|
84
|
+
f" `{key}` as a named parameter."
|
|
85
|
+
)
|
|
86
|
+
raise ValueError(msg)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Utilities for formatting strings."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping, Sequence
|
|
4
|
+
from string import Formatter
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class StrictFormatter(Formatter):
|
|
9
|
+
"""A string formatter that enforces keyword-only argument substitution.
|
|
10
|
+
|
|
11
|
+
This formatter extends Python's built-in `string.Formatter` to provide stricter
|
|
12
|
+
validation for prompt template formatting. It ensures that all variable
|
|
13
|
+
substitutions use keyword arguments rather than positional arguments, which improves
|
|
14
|
+
clarity and reduces errors when formatting prompt templates.
|
|
15
|
+
|
|
16
|
+
Example:
|
|
17
|
+
>>> fmt = StrictFormatter()
|
|
18
|
+
>>> fmt.format("Hello, {name}!", name="World")
|
|
19
|
+
'Hello, World!'
|
|
20
|
+
>>> fmt.format("Hello, {}!", "World") # Raises ValueError
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def vformat(
|
|
24
|
+
self, format_string: str, args: Sequence[Any], kwargs: Mapping[str, Any]
|
|
25
|
+
) -> str:
|
|
26
|
+
"""Format a string using only keyword arguments.
|
|
27
|
+
|
|
28
|
+
Overrides the base `vformat` to reject positional arguments, ensuring all
|
|
29
|
+
substitutions are explicit and named.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
format_string: A string containing replacement fields (e.g., `'{name}'`).
|
|
33
|
+
args: Positional arguments (must be empty).
|
|
34
|
+
kwargs: Keyword arguments for substitution into the format string.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
The formatted string with all replacement fields substituted.
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
ValueError: If any positional arguments are provided.
|
|
41
|
+
"""
|
|
42
|
+
if len(args) > 0:
|
|
43
|
+
msg = (
|
|
44
|
+
"No arguments should be provided, "
|
|
45
|
+
"everything should be passed as keyword arguments."
|
|
46
|
+
)
|
|
47
|
+
raise ValueError(msg)
|
|
48
|
+
return super().vformat(format_string, args, kwargs)
|
|
49
|
+
|
|
50
|
+
def validate_input_variables(
|
|
51
|
+
self, format_string: str, input_variables: list[str]
|
|
52
|
+
) -> None:
|
|
53
|
+
"""Validate that input variables match the placeholders in a format string.
|
|
54
|
+
|
|
55
|
+
Checks that the provided input variables can be used to format the given string
|
|
56
|
+
without missing or extra keys. This is useful for validating prompt templates
|
|
57
|
+
before runtime.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
format_string: A string containing replacement fields to validate
|
|
61
|
+
against (e.g., `'Hello, {name}!'`).
|
|
62
|
+
input_variables: List of variable names expected to fill the
|
|
63
|
+
replacement fields.
|
|
64
|
+
|
|
65
|
+
Raises:
|
|
66
|
+
KeyError: If the format string contains placeholders not present
|
|
67
|
+
in input_variables.
|
|
68
|
+
|
|
69
|
+
Example:
|
|
70
|
+
>>> fmt = StrictFormatter()
|
|
71
|
+
>>> fmt.validate_input_variables("Hello, {name}!", ["name"]) # OK
|
|
72
|
+
>>> fmt.validate_input_variables("Hello, {name}!", ["other"]) # Raises
|
|
73
|
+
"""
|
|
74
|
+
dummy_inputs = dict.fromkeys(input_variables, "foo")
|
|
75
|
+
super().format(format_string, **dummy_inputs)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
#: Default StrictFormatter instance for use throughout Coco.
|
|
79
|
+
#: Used internally for formatting prompt templates with named variables.
|
|
80
|
+
formatter = StrictFormatter()
|