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,249 @@
|
|
|
1
|
+
"""Parsers for list output."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import csv
|
|
6
|
+
import re
|
|
7
|
+
from abc import abstractmethod
|
|
8
|
+
from collections import deque
|
|
9
|
+
from io import StringIO
|
|
10
|
+
from typing import TYPE_CHECKING, TypeVar
|
|
11
|
+
|
|
12
|
+
from typing_extensions import override
|
|
13
|
+
|
|
14
|
+
from coco_core.messages import BaseMessage
|
|
15
|
+
from coco_core.output_parsers.transform import BaseTransformOutputParser
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from collections.abc import AsyncIterator, Iterator
|
|
19
|
+
|
|
20
|
+
T = TypeVar("T")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def droplastn(
|
|
24
|
+
iter: Iterator[T], # noqa: A002
|
|
25
|
+
n: int,
|
|
26
|
+
) -> Iterator[T]:
|
|
27
|
+
"""Drop the last `n` elements of an iterator.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
iter: The iterator to drop elements from.
|
|
31
|
+
n: The number of elements to drop.
|
|
32
|
+
|
|
33
|
+
Yields:
|
|
34
|
+
The elements of the iterator, except the last n elements.
|
|
35
|
+
"""
|
|
36
|
+
buffer: deque[T] = deque()
|
|
37
|
+
for item in iter:
|
|
38
|
+
buffer.append(item)
|
|
39
|
+
if len(buffer) > n:
|
|
40
|
+
yield buffer.popleft()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ListOutputParser(BaseTransformOutputParser[list[str]]):
|
|
44
|
+
"""Parse the output of a model to a list."""
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def _type(self) -> str:
|
|
48
|
+
return "list"
|
|
49
|
+
|
|
50
|
+
@abstractmethod
|
|
51
|
+
def parse(self, text: str) -> list[str]:
|
|
52
|
+
"""Parse the output of an LLM call.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
text: The output of an LLM call.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
A list of strings.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def parse_iter(self, text: str) -> Iterator[re.Match[str]]:
|
|
62
|
+
"""Parse the output of an LLM call.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
text: The output of an LLM call.
|
|
66
|
+
|
|
67
|
+
Yields:
|
|
68
|
+
A match object for each part of the output.
|
|
69
|
+
"""
|
|
70
|
+
raise NotImplementedError
|
|
71
|
+
|
|
72
|
+
@override
|
|
73
|
+
def _transform(self, input: Iterator[str | BaseMessage]) -> Iterator[list[str]]:
|
|
74
|
+
buffer = ""
|
|
75
|
+
for chunk in input:
|
|
76
|
+
if isinstance(chunk, BaseMessage):
|
|
77
|
+
# Extract text
|
|
78
|
+
chunk_content = chunk.content
|
|
79
|
+
if not isinstance(chunk_content, str):
|
|
80
|
+
continue
|
|
81
|
+
buffer += chunk_content
|
|
82
|
+
else:
|
|
83
|
+
# Add current chunk to buffer
|
|
84
|
+
buffer += chunk
|
|
85
|
+
# Parse buffer into a list of parts
|
|
86
|
+
try:
|
|
87
|
+
done_idx = 0
|
|
88
|
+
# Yield only complete parts
|
|
89
|
+
for m in droplastn(self.parse_iter(buffer), 1):
|
|
90
|
+
done_idx = m.end()
|
|
91
|
+
yield [m.group(1)]
|
|
92
|
+
buffer = buffer[done_idx:]
|
|
93
|
+
except NotImplementedError:
|
|
94
|
+
parts = self.parse(buffer)
|
|
95
|
+
# Yield only complete parts
|
|
96
|
+
if len(parts) > 1:
|
|
97
|
+
for part in parts[:-1]:
|
|
98
|
+
yield [part]
|
|
99
|
+
buffer = parts[-1]
|
|
100
|
+
# Yield the last part
|
|
101
|
+
for part in self.parse(buffer):
|
|
102
|
+
yield [part]
|
|
103
|
+
|
|
104
|
+
@override
|
|
105
|
+
async def _atransform(
|
|
106
|
+
self, input: AsyncIterator[str | BaseMessage]
|
|
107
|
+
) -> AsyncIterator[list[str]]:
|
|
108
|
+
buffer = ""
|
|
109
|
+
async for chunk in input:
|
|
110
|
+
if isinstance(chunk, BaseMessage):
|
|
111
|
+
# Extract text
|
|
112
|
+
chunk_content = chunk.content
|
|
113
|
+
if not isinstance(chunk_content, str):
|
|
114
|
+
continue
|
|
115
|
+
buffer += chunk_content
|
|
116
|
+
else:
|
|
117
|
+
# Add current chunk to buffer
|
|
118
|
+
buffer += chunk
|
|
119
|
+
# Parse buffer into a list of parts
|
|
120
|
+
try:
|
|
121
|
+
done_idx = 0
|
|
122
|
+
# Yield only complete parts
|
|
123
|
+
for m in droplastn(self.parse_iter(buffer), 1):
|
|
124
|
+
done_idx = m.end()
|
|
125
|
+
yield [m.group(1)]
|
|
126
|
+
buffer = buffer[done_idx:]
|
|
127
|
+
except NotImplementedError:
|
|
128
|
+
parts = self.parse(buffer)
|
|
129
|
+
# Yield only complete parts
|
|
130
|
+
if len(parts) > 1:
|
|
131
|
+
for part in parts[:-1]:
|
|
132
|
+
yield [part]
|
|
133
|
+
buffer = parts[-1]
|
|
134
|
+
# Yield the last part
|
|
135
|
+
for part in self.parse(buffer):
|
|
136
|
+
yield [part]
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class CommaSeparatedListOutputParser(ListOutputParser):
|
|
140
|
+
"""Parse the output of a model to a comma-separated list."""
|
|
141
|
+
|
|
142
|
+
@classmethod
|
|
143
|
+
def is_lc_serializable(cls) -> bool:
|
|
144
|
+
"""Return `True` as this class is serializable."""
|
|
145
|
+
return True
|
|
146
|
+
|
|
147
|
+
@classmethod
|
|
148
|
+
def get_lc_namespace(cls) -> list[str]:
|
|
149
|
+
"""Get the namespace of the Coco object.
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
`["coco", "output_parsers", "list"]`
|
|
153
|
+
"""
|
|
154
|
+
return ["coco", "output_parsers", "list"]
|
|
155
|
+
|
|
156
|
+
@override
|
|
157
|
+
def get_format_instructions(self) -> str:
|
|
158
|
+
"""Return the format instructions for the comma-separated list output."""
|
|
159
|
+
return (
|
|
160
|
+
"Your response should be a list of comma separated values, "
|
|
161
|
+
"eg: `foo, bar, baz` or `foo,bar,baz`"
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
@override
|
|
165
|
+
def parse(self, text: str) -> list[str]:
|
|
166
|
+
"""Parse the output of an LLM call.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
text: The output of an LLM call.
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
A list of strings.
|
|
173
|
+
"""
|
|
174
|
+
try:
|
|
175
|
+
reader = csv.reader(
|
|
176
|
+
StringIO(text), quotechar='"', delimiter=",", skipinitialspace=True
|
|
177
|
+
)
|
|
178
|
+
return [item for sublist in reader for item in sublist]
|
|
179
|
+
except csv.Error:
|
|
180
|
+
# Keep old logic for backup
|
|
181
|
+
return [part.strip() for part in text.split(",")]
|
|
182
|
+
|
|
183
|
+
@property
|
|
184
|
+
def _type(self) -> str:
|
|
185
|
+
return "comma-separated-list"
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class NumberedListOutputParser(ListOutputParser):
|
|
189
|
+
"""Parse a numbered list."""
|
|
190
|
+
|
|
191
|
+
pattern: str = r"\d+\.\s([^\n]+)"
|
|
192
|
+
"""The pattern to match a numbered list item."""
|
|
193
|
+
|
|
194
|
+
@override
|
|
195
|
+
def get_format_instructions(self) -> str:
|
|
196
|
+
return (
|
|
197
|
+
"Your response should be a numbered list with each item on a new line. "
|
|
198
|
+
"For example: \n\n1. foo\n\n2. bar\n\n3. baz"
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
def parse(self, text: str) -> list[str]:
|
|
202
|
+
"""Parse the output of an LLM call.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
text: The output of an LLM call.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
A list of strings.
|
|
209
|
+
"""
|
|
210
|
+
return re.findall(self.pattern, text)
|
|
211
|
+
|
|
212
|
+
@override
|
|
213
|
+
def parse_iter(self, text: str) -> Iterator[re.Match[str]]:
|
|
214
|
+
return re.finditer(self.pattern, text)
|
|
215
|
+
|
|
216
|
+
@property
|
|
217
|
+
def _type(self) -> str:
|
|
218
|
+
return "numbered-list"
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class MarkdownListOutputParser(ListOutputParser):
|
|
222
|
+
"""Parse a Markdown list."""
|
|
223
|
+
|
|
224
|
+
pattern: str = r"^\s*[-*]\s([^\n]+)$"
|
|
225
|
+
"""The pattern to match a Markdown list item."""
|
|
226
|
+
|
|
227
|
+
@override
|
|
228
|
+
def get_format_instructions(self) -> str:
|
|
229
|
+
"""Return the format instructions for the Markdown list output."""
|
|
230
|
+
return "Your response should be a markdown list, eg: `- foo\n- bar\n- baz`"
|
|
231
|
+
|
|
232
|
+
def parse(self, text: str) -> list[str]:
|
|
233
|
+
"""Parse the output of an LLM call.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
text: The output of an LLM call.
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
A list of strings.
|
|
240
|
+
"""
|
|
241
|
+
return re.findall(self.pattern, text, re.MULTILINE)
|
|
242
|
+
|
|
243
|
+
@override
|
|
244
|
+
def parse_iter(self, text: str) -> Iterator[re.Match[str]]:
|
|
245
|
+
return re.finditer(self.pattern, text, re.MULTILINE)
|
|
246
|
+
|
|
247
|
+
@property
|
|
248
|
+
def _type(self) -> str:
|
|
249
|
+
return "markdown-list"
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
"""Parsers for OpenAI functions output."""
|
|
2
|
+
|
|
3
|
+
import copy
|
|
4
|
+
import json
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import jsonpatch # type: ignore[import-untyped]
|
|
8
|
+
from pydantic import BaseModel, model_validator
|
|
9
|
+
from pydantic.v1 import BaseModel as BaseModelV1
|
|
10
|
+
from typing_extensions import override
|
|
11
|
+
|
|
12
|
+
from coco_core.exceptions import OutputParserException
|
|
13
|
+
from coco_core.output_parsers import (
|
|
14
|
+
BaseCumulativeTransformOutputParser,
|
|
15
|
+
BaseGenerationOutputParser,
|
|
16
|
+
)
|
|
17
|
+
from coco_core.output_parsers.json import parse_partial_json
|
|
18
|
+
from coco_core.outputs import ChatGeneration, Generation
|
|
19
|
+
from coco_core.utils.pydantic import PydanticBaseModel, TypeBaseModel
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class OutputFunctionsParser(BaseGenerationOutputParser[Any]):
|
|
23
|
+
"""Parse an output that is one of sets of values."""
|
|
24
|
+
|
|
25
|
+
args_only: bool = True
|
|
26
|
+
"""Whether to only return the arguments to the function call."""
|
|
27
|
+
|
|
28
|
+
@override
|
|
29
|
+
def parse_result(self, result: list[Generation], *, partial: bool = False) -> Any:
|
|
30
|
+
"""Parse the result of an LLM call to a JSON object.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
result: The result of the LLM call.
|
|
34
|
+
partial: Whether to parse partial JSON objects.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
The parsed JSON object.
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
OutputParserException: If the output is not valid JSON.
|
|
41
|
+
"""
|
|
42
|
+
generation = result[0]
|
|
43
|
+
if not isinstance(generation, ChatGeneration):
|
|
44
|
+
msg = "This output parser can only be used with a chat generation."
|
|
45
|
+
raise OutputParserException(msg)
|
|
46
|
+
message = generation.message
|
|
47
|
+
try:
|
|
48
|
+
func_call = copy.deepcopy(message.additional_kwargs["function_call"])
|
|
49
|
+
except KeyError as exc:
|
|
50
|
+
msg = f"Could not parse function call: {exc}"
|
|
51
|
+
raise OutputParserException(msg) from exc
|
|
52
|
+
|
|
53
|
+
if self.args_only:
|
|
54
|
+
return func_call["arguments"]
|
|
55
|
+
return func_call
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class JsonOutputFunctionsParser(BaseCumulativeTransformOutputParser[Any]):
|
|
59
|
+
"""Parse an output as the JSON object."""
|
|
60
|
+
|
|
61
|
+
strict: bool = False
|
|
62
|
+
"""Whether to allow non-JSON-compliant strings.
|
|
63
|
+
|
|
64
|
+
See: https://docs.python.org/3/library/json.html#encoders-and-decoders
|
|
65
|
+
|
|
66
|
+
Useful when the parsed output may include unicode characters or new lines.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
args_only: bool = True
|
|
70
|
+
"""Whether to only return the arguments to the function call."""
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def _type(self) -> str:
|
|
74
|
+
return "json_functions"
|
|
75
|
+
|
|
76
|
+
@override
|
|
77
|
+
def _diff(self, prev: Any | None, next: Any) -> Any:
|
|
78
|
+
return jsonpatch.make_patch(prev, next).patch
|
|
79
|
+
|
|
80
|
+
def parse_result(self, result: list[Generation], *, partial: bool = False) -> Any:
|
|
81
|
+
"""Parse the result of an LLM call to a JSON object.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
result: The result of the LLM call.
|
|
85
|
+
partial: Whether to parse partial JSON objects.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
The parsed JSON object.
|
|
89
|
+
|
|
90
|
+
Raises:
|
|
91
|
+
OutputParserException: If the output is not valid JSON.
|
|
92
|
+
"""
|
|
93
|
+
if len(result) != 1:
|
|
94
|
+
msg = f"Expected exactly one result, but got {len(result)}"
|
|
95
|
+
raise OutputParserException(msg)
|
|
96
|
+
generation = result[0]
|
|
97
|
+
if not isinstance(generation, ChatGeneration):
|
|
98
|
+
msg = "This output parser can only be used with a chat generation."
|
|
99
|
+
raise OutputParserException(msg)
|
|
100
|
+
message = generation.message
|
|
101
|
+
try:
|
|
102
|
+
function_call = message.additional_kwargs["function_call"]
|
|
103
|
+
except KeyError as exc:
|
|
104
|
+
if partial:
|
|
105
|
+
return None
|
|
106
|
+
msg = f"Could not parse function call: {exc}"
|
|
107
|
+
raise OutputParserException(msg) from exc
|
|
108
|
+
try:
|
|
109
|
+
if partial:
|
|
110
|
+
try:
|
|
111
|
+
if self.args_only:
|
|
112
|
+
return parse_partial_json(
|
|
113
|
+
function_call["arguments"], strict=self.strict
|
|
114
|
+
)
|
|
115
|
+
return {
|
|
116
|
+
**function_call,
|
|
117
|
+
"arguments": parse_partial_json(
|
|
118
|
+
function_call["arguments"], strict=self.strict
|
|
119
|
+
),
|
|
120
|
+
}
|
|
121
|
+
except json.JSONDecodeError:
|
|
122
|
+
return None
|
|
123
|
+
elif self.args_only:
|
|
124
|
+
try:
|
|
125
|
+
return json.loads(function_call["arguments"], strict=self.strict)
|
|
126
|
+
except (json.JSONDecodeError, TypeError) as exc:
|
|
127
|
+
msg = f"Could not parse function call data: {exc}"
|
|
128
|
+
raise OutputParserException(msg) from exc
|
|
129
|
+
else:
|
|
130
|
+
try:
|
|
131
|
+
return {
|
|
132
|
+
**function_call,
|
|
133
|
+
"arguments": json.loads(
|
|
134
|
+
function_call["arguments"], strict=self.strict
|
|
135
|
+
),
|
|
136
|
+
}
|
|
137
|
+
except (json.JSONDecodeError, TypeError) as exc:
|
|
138
|
+
msg = f"Could not parse function call data: {exc}"
|
|
139
|
+
raise OutputParserException(msg) from exc
|
|
140
|
+
except KeyError:
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
# This method would be called by the default implementation of `parse_result`
|
|
144
|
+
# but we're overriding that method so it's not needed.
|
|
145
|
+
def parse(self, text: str) -> Any:
|
|
146
|
+
"""Parse the output of an LLM call to a JSON object.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
text: The output of the LLM call.
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
The parsed JSON object.
|
|
153
|
+
"""
|
|
154
|
+
raise NotImplementedError
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class JsonKeyOutputFunctionsParser(JsonOutputFunctionsParser):
|
|
158
|
+
"""Parse an output as the element of the JSON object."""
|
|
159
|
+
|
|
160
|
+
key_name: str
|
|
161
|
+
"""The name of the key to return."""
|
|
162
|
+
|
|
163
|
+
def parse_result(self, result: list[Generation], *, partial: bool = False) -> Any:
|
|
164
|
+
"""Parse the result of an LLM call to a JSON object.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
result: The result of the LLM call.
|
|
168
|
+
partial: Whether to parse partial JSON objects.
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
The parsed JSON object.
|
|
172
|
+
"""
|
|
173
|
+
res = super().parse_result(result, partial=partial)
|
|
174
|
+
if partial and res is None:
|
|
175
|
+
return None
|
|
176
|
+
return res.get(self.key_name) if partial else res[self.key_name]
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class PydanticOutputFunctionsParser(OutputFunctionsParser):
|
|
180
|
+
"""Parse an output as a Pydantic object.
|
|
181
|
+
|
|
182
|
+
This parser is used to parse the output of a chat model that uses OpenAI function
|
|
183
|
+
format to invoke functions.
|
|
184
|
+
|
|
185
|
+
The parser extracts the function call invocation and matches them to the Pydantic
|
|
186
|
+
schema provided.
|
|
187
|
+
|
|
188
|
+
An exception will be raised if the function call does not match the provided schema.
|
|
189
|
+
|
|
190
|
+
Example:
|
|
191
|
+
```python
|
|
192
|
+
message = AIMessage(
|
|
193
|
+
content="This is a test message",
|
|
194
|
+
additional_kwargs={
|
|
195
|
+
"function_call": {
|
|
196
|
+
"name": "cookie",
|
|
197
|
+
"arguments": json.dumps({"name": "value", "age": 10}),
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
)
|
|
201
|
+
chat_generation = ChatGeneration(message=message)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class Cookie(BaseModel):
|
|
205
|
+
name: str
|
|
206
|
+
age: int
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class Dog(BaseModel):
|
|
210
|
+
species: str
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
# Full output
|
|
214
|
+
parser = PydanticOutputFunctionsParser(
|
|
215
|
+
pydantic_schema={"cookie": Cookie, "dog": Dog}
|
|
216
|
+
)
|
|
217
|
+
result = parser.parse_result([chat_generation])
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
"""
|
|
221
|
+
|
|
222
|
+
pydantic_schema: TypeBaseModel | dict[str, TypeBaseModel]
|
|
223
|
+
"""The Pydantic schema to parse the output with.
|
|
224
|
+
|
|
225
|
+
If multiple schemas are provided, then the function name will be used to
|
|
226
|
+
determine which schema to use.
|
|
227
|
+
"""
|
|
228
|
+
|
|
229
|
+
@model_validator(mode="before")
|
|
230
|
+
@classmethod
|
|
231
|
+
def validate_schema(cls, values: dict[str, Any]) -> Any:
|
|
232
|
+
"""Validate the Pydantic schema.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
values: The values to validate.
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
The validated values.
|
|
239
|
+
|
|
240
|
+
Raises:
|
|
241
|
+
ValueError: If the schema is not a Pydantic schema.
|
|
242
|
+
"""
|
|
243
|
+
schema = values["pydantic_schema"]
|
|
244
|
+
if "args_only" not in values:
|
|
245
|
+
values["args_only"] = isinstance(schema, type) and issubclass(
|
|
246
|
+
schema, BaseModel
|
|
247
|
+
)
|
|
248
|
+
elif values["args_only"] and isinstance(schema, dict):
|
|
249
|
+
msg = (
|
|
250
|
+
"If multiple pydantic schemas are provided then args_only should be"
|
|
251
|
+
" False."
|
|
252
|
+
)
|
|
253
|
+
raise ValueError(msg)
|
|
254
|
+
return values
|
|
255
|
+
|
|
256
|
+
@override
|
|
257
|
+
def parse_result(self, result: list[Generation], *, partial: bool = False) -> Any:
|
|
258
|
+
"""Parse the result of an LLM call to a JSON object.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
result: The result of the LLM call.
|
|
262
|
+
partial: Whether to parse partial JSON objects.
|
|
263
|
+
|
|
264
|
+
Raises:
|
|
265
|
+
ValueError: If the Pydantic schema is not valid.
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
The parsed JSON object.
|
|
269
|
+
"""
|
|
270
|
+
result_ = super().parse_result(result)
|
|
271
|
+
pydantic_args: PydanticBaseModel
|
|
272
|
+
if self.args_only:
|
|
273
|
+
if isinstance(self.pydantic_schema, dict):
|
|
274
|
+
msg = (
|
|
275
|
+
"Dict Pydantic schema unsupported with args_only: "
|
|
276
|
+
f"{self.pydantic_schema}"
|
|
277
|
+
)
|
|
278
|
+
raise ValueError(msg)
|
|
279
|
+
if issubclass(self.pydantic_schema, BaseModel):
|
|
280
|
+
pydantic_args = self.pydantic_schema.model_validate_json(result_)
|
|
281
|
+
elif issubclass(self.pydantic_schema, BaseModelV1):
|
|
282
|
+
pydantic_args = self.pydantic_schema.parse_raw(result_)
|
|
283
|
+
else:
|
|
284
|
+
msg = ( # type: ignore[unreachable]
|
|
285
|
+
"Unsupported Pydantic schema with args_only: "
|
|
286
|
+
f"{self.pydantic_schema}"
|
|
287
|
+
)
|
|
288
|
+
raise ValueError(msg)
|
|
289
|
+
else:
|
|
290
|
+
fn_name = result_["name"]
|
|
291
|
+
args = result_["arguments"]
|
|
292
|
+
if isinstance(self.pydantic_schema, dict):
|
|
293
|
+
pydantic_schema = self.pydantic_schema[fn_name]
|
|
294
|
+
else:
|
|
295
|
+
pydantic_schema = self.pydantic_schema
|
|
296
|
+
if issubclass(pydantic_schema, BaseModel):
|
|
297
|
+
pydantic_args = pydantic_schema.model_validate_json(args)
|
|
298
|
+
elif issubclass(pydantic_schema, BaseModelV1):
|
|
299
|
+
pydantic_args = pydantic_schema.parse_raw(args)
|
|
300
|
+
else:
|
|
301
|
+
msg = f"Unsupported Pydantic schema: {pydantic_schema}" # type: ignore[unreachable]
|
|
302
|
+
raise ValueError(msg)
|
|
303
|
+
return pydantic_args
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
class PydanticAttrOutputFunctionsParser(PydanticOutputFunctionsParser):
|
|
307
|
+
"""Parse an output as an attribute of a Pydantic object."""
|
|
308
|
+
|
|
309
|
+
attr_name: str
|
|
310
|
+
"""The name of the attribute to return."""
|
|
311
|
+
|
|
312
|
+
@override
|
|
313
|
+
def parse_result(self, result: list[Generation], *, partial: bool = False) -> Any:
|
|
314
|
+
"""Parse the result of an LLM call to a JSON object.
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
result: The result of the LLM call.
|
|
318
|
+
partial: Whether to parse partial JSON objects.
|
|
319
|
+
|
|
320
|
+
Returns:
|
|
321
|
+
The parsed JSON object.
|
|
322
|
+
"""
|
|
323
|
+
result = super().parse_result(result)
|
|
324
|
+
return getattr(result, self.attr_name)
|