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,484 @@
|
|
|
1
|
+
"""Agent middleware that integrates OpenAI's moderation endpoint."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from collections.abc import Sequence
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Literal, cast
|
|
8
|
+
|
|
9
|
+
from coco.agents.middleware.types import AgentMiddleware, AgentState, hook_config
|
|
10
|
+
from coco_core.messages import AIMessage, BaseMessage, HumanMessage, ToolMessage
|
|
11
|
+
from openai import AsyncOpenAI, OpenAI
|
|
12
|
+
from openai.types import Moderation, ModerationModel
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
15
|
+
from coco_graph.runtime import Runtime
|
|
16
|
+
|
|
17
|
+
ViolationStage = Literal["input", "output", "tool"]
|
|
18
|
+
|
|
19
|
+
DEFAULT_VIOLATION_TEMPLATE = (
|
|
20
|
+
"I'm sorry, but I can't comply with that request. It was flagged for {categories}."
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class OpenAIModerationError(RuntimeError):
|
|
25
|
+
"""Raised when OpenAI flags content and `exit_behavior` is set to `"error"`."""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
*,
|
|
30
|
+
content: str,
|
|
31
|
+
stage: ViolationStage,
|
|
32
|
+
result: Moderation,
|
|
33
|
+
message: str,
|
|
34
|
+
) -> None:
|
|
35
|
+
"""Initialize the error with violation details.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
content: The content that was flagged.
|
|
39
|
+
stage: The stage where the violation occurred.
|
|
40
|
+
result: The moderation result from OpenAI.
|
|
41
|
+
message: The error message.
|
|
42
|
+
"""
|
|
43
|
+
super().__init__(message)
|
|
44
|
+
self.content = content
|
|
45
|
+
self.stage = stage
|
|
46
|
+
self.result = result
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class OpenAIModerationMiddleware(AgentMiddleware[AgentState[Any], Any]):
|
|
50
|
+
"""Moderate agent traffic using OpenAI's moderation endpoint."""
|
|
51
|
+
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
*,
|
|
55
|
+
model: ModerationModel = "omni-moderation-latest",
|
|
56
|
+
check_input: bool = True,
|
|
57
|
+
check_output: bool = True,
|
|
58
|
+
check_tool_results: bool = False,
|
|
59
|
+
exit_behavior: Literal["error", "end", "replace"] = "end",
|
|
60
|
+
violation_message: str | None = None,
|
|
61
|
+
client: OpenAI | None = None,
|
|
62
|
+
async_client: AsyncOpenAI | None = None,
|
|
63
|
+
) -> None:
|
|
64
|
+
"""Create the middleware instance.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
model: OpenAI moderation model to use.
|
|
68
|
+
check_input: Whether to check user input messages.
|
|
69
|
+
check_output: Whether to check model output messages.
|
|
70
|
+
check_tool_results: Whether to check tool result messages.
|
|
71
|
+
exit_behavior: How to handle violations
|
|
72
|
+
(`'error'`, `'end'`, or `'replace'`).
|
|
73
|
+
violation_message: Custom template for violation messages.
|
|
74
|
+
client: Optional pre-configured OpenAI client to reuse.
|
|
75
|
+
If not provided, a new client will be created.
|
|
76
|
+
async_client: Optional pre-configured AsyncOpenAI client to reuse.
|
|
77
|
+
If not provided, a new async client will be created.
|
|
78
|
+
"""
|
|
79
|
+
super().__init__()
|
|
80
|
+
self.model = model
|
|
81
|
+
self.check_input = check_input
|
|
82
|
+
self.check_output = check_output
|
|
83
|
+
self.check_tool_results = check_tool_results
|
|
84
|
+
self.exit_behavior = exit_behavior
|
|
85
|
+
self.violation_message = violation_message
|
|
86
|
+
|
|
87
|
+
self._client = client
|
|
88
|
+
self._async_client = async_client
|
|
89
|
+
|
|
90
|
+
@hook_config(can_jump_to=["end"])
|
|
91
|
+
def before_model(
|
|
92
|
+
self, state: AgentState[Any], runtime: Runtime[Any]
|
|
93
|
+
) -> dict[str, Any] | None: # type: ignore[override]
|
|
94
|
+
"""Moderate user input and tool results before the model is called.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
state: Current agent state containing messages.
|
|
98
|
+
runtime: Agent runtime context.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
Updated state with moderated messages, or `None` if no changes.
|
|
102
|
+
"""
|
|
103
|
+
if not self.check_input and not self.check_tool_results:
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
messages = list(state.get("messages", []))
|
|
107
|
+
if not messages:
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
return self._moderate_inputs(messages)
|
|
111
|
+
|
|
112
|
+
@hook_config(can_jump_to=["end"])
|
|
113
|
+
def after_model(
|
|
114
|
+
self, state: AgentState[Any], runtime: Runtime[Any]
|
|
115
|
+
) -> dict[str, Any] | None: # type: ignore[override]
|
|
116
|
+
"""Moderate model output after the model is called.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
state: Current agent state containing messages.
|
|
120
|
+
runtime: Agent runtime context.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Updated state with moderated messages, or `None` if no changes.
|
|
124
|
+
"""
|
|
125
|
+
if not self.check_output:
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
messages = list(state.get("messages", []))
|
|
129
|
+
if not messages:
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
return self._moderate_output(messages)
|
|
133
|
+
|
|
134
|
+
@hook_config(can_jump_to=["end"])
|
|
135
|
+
async def abefore_model(
|
|
136
|
+
self, state: AgentState[Any], runtime: Runtime[Any]
|
|
137
|
+
) -> dict[str, Any] | None: # type: ignore[override]
|
|
138
|
+
"""Async version of before_model.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
state: Current agent state containing messages.
|
|
142
|
+
runtime: Agent runtime context.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Updated state with moderated messages, or `None` if no changes.
|
|
146
|
+
"""
|
|
147
|
+
if not self.check_input and not self.check_tool_results:
|
|
148
|
+
return None
|
|
149
|
+
|
|
150
|
+
messages = list(state.get("messages", []))
|
|
151
|
+
if not messages:
|
|
152
|
+
return None
|
|
153
|
+
|
|
154
|
+
return await self._amoderate_inputs(messages)
|
|
155
|
+
|
|
156
|
+
@hook_config(can_jump_to=["end"])
|
|
157
|
+
async def aafter_model(
|
|
158
|
+
self, state: AgentState[Any], runtime: Runtime[Any]
|
|
159
|
+
) -> dict[str, Any] | None: # type: ignore[override]
|
|
160
|
+
"""Async version of after_model.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
state: Current agent state containing messages.
|
|
164
|
+
runtime: Agent runtime context.
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
Updated state with moderated messages, or `None` if no changes.
|
|
168
|
+
"""
|
|
169
|
+
if not self.check_output:
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
messages = list(state.get("messages", []))
|
|
173
|
+
if not messages:
|
|
174
|
+
return None
|
|
175
|
+
|
|
176
|
+
return await self._amoderate_output(messages)
|
|
177
|
+
|
|
178
|
+
def _moderate_inputs(
|
|
179
|
+
self, messages: Sequence[BaseMessage]
|
|
180
|
+
) -> dict[str, Any] | None:
|
|
181
|
+
working = list(messages)
|
|
182
|
+
modified = False
|
|
183
|
+
|
|
184
|
+
if self.check_tool_results:
|
|
185
|
+
action = self._moderate_tool_messages(working)
|
|
186
|
+
if action:
|
|
187
|
+
if "jump_to" in action:
|
|
188
|
+
return action
|
|
189
|
+
working = cast("list[BaseMessage]", action["messages"])
|
|
190
|
+
modified = True
|
|
191
|
+
|
|
192
|
+
if self.check_input:
|
|
193
|
+
action = self._moderate_user_message(working)
|
|
194
|
+
if action:
|
|
195
|
+
if "jump_to" in action:
|
|
196
|
+
return action
|
|
197
|
+
working = cast("list[BaseMessage]", action["messages"])
|
|
198
|
+
modified = True
|
|
199
|
+
|
|
200
|
+
if modified:
|
|
201
|
+
return {"messages": working}
|
|
202
|
+
|
|
203
|
+
return None
|
|
204
|
+
|
|
205
|
+
async def _amoderate_inputs(
|
|
206
|
+
self, messages: Sequence[BaseMessage]
|
|
207
|
+
) -> dict[str, Any] | None:
|
|
208
|
+
working = list(messages)
|
|
209
|
+
modified = False
|
|
210
|
+
|
|
211
|
+
if self.check_tool_results:
|
|
212
|
+
action = await self._amoderate_tool_messages(working)
|
|
213
|
+
if action:
|
|
214
|
+
if "jump_to" in action:
|
|
215
|
+
return action
|
|
216
|
+
working = cast("list[BaseMessage]", action["messages"])
|
|
217
|
+
modified = True
|
|
218
|
+
|
|
219
|
+
if self.check_input:
|
|
220
|
+
action = await self._amoderate_user_message(working)
|
|
221
|
+
if action:
|
|
222
|
+
if "jump_to" in action:
|
|
223
|
+
return action
|
|
224
|
+
working = cast("list[BaseMessage]", action["messages"])
|
|
225
|
+
modified = True
|
|
226
|
+
|
|
227
|
+
if modified:
|
|
228
|
+
return {"messages": working}
|
|
229
|
+
|
|
230
|
+
return None
|
|
231
|
+
|
|
232
|
+
def _moderate_output(
|
|
233
|
+
self, messages: Sequence[BaseMessage]
|
|
234
|
+
) -> dict[str, Any] | None:
|
|
235
|
+
last_ai_idx = self._find_last_index(messages, AIMessage)
|
|
236
|
+
if last_ai_idx is None:
|
|
237
|
+
return None
|
|
238
|
+
|
|
239
|
+
ai_message = messages[last_ai_idx]
|
|
240
|
+
text = self._extract_text(ai_message)
|
|
241
|
+
if not text:
|
|
242
|
+
return None
|
|
243
|
+
|
|
244
|
+
result = self._moderate(text)
|
|
245
|
+
if not result.flagged:
|
|
246
|
+
return None
|
|
247
|
+
|
|
248
|
+
return self._apply_violation(
|
|
249
|
+
messages, index=last_ai_idx, stage="output", content=text, result=result
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
async def _amoderate_output(
|
|
253
|
+
self, messages: Sequence[BaseMessage]
|
|
254
|
+
) -> dict[str, Any] | None:
|
|
255
|
+
last_ai_idx = self._find_last_index(messages, AIMessage)
|
|
256
|
+
if last_ai_idx is None:
|
|
257
|
+
return None
|
|
258
|
+
|
|
259
|
+
ai_message = messages[last_ai_idx]
|
|
260
|
+
text = self._extract_text(ai_message)
|
|
261
|
+
if not text:
|
|
262
|
+
return None
|
|
263
|
+
|
|
264
|
+
result = await self._amoderate(text)
|
|
265
|
+
if not result.flagged:
|
|
266
|
+
return None
|
|
267
|
+
|
|
268
|
+
return self._apply_violation(
|
|
269
|
+
messages, index=last_ai_idx, stage="output", content=text, result=result
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
def _moderate_tool_messages(
|
|
273
|
+
self, messages: Sequence[BaseMessage]
|
|
274
|
+
) -> dict[str, Any] | None:
|
|
275
|
+
last_ai_idx = self._find_last_index(messages, AIMessage)
|
|
276
|
+
if last_ai_idx is None:
|
|
277
|
+
return None
|
|
278
|
+
|
|
279
|
+
working = list(messages)
|
|
280
|
+
modified = False
|
|
281
|
+
|
|
282
|
+
for idx in range(last_ai_idx + 1, len(working)):
|
|
283
|
+
msg = working[idx]
|
|
284
|
+
if not isinstance(msg, ToolMessage):
|
|
285
|
+
continue
|
|
286
|
+
|
|
287
|
+
text = self._extract_text(msg)
|
|
288
|
+
if not text:
|
|
289
|
+
continue
|
|
290
|
+
|
|
291
|
+
result = self._moderate(text)
|
|
292
|
+
if not result.flagged:
|
|
293
|
+
continue
|
|
294
|
+
|
|
295
|
+
action = self._apply_violation(
|
|
296
|
+
working, index=idx, stage="tool", content=text, result=result
|
|
297
|
+
)
|
|
298
|
+
if action:
|
|
299
|
+
if "jump_to" in action:
|
|
300
|
+
return action
|
|
301
|
+
working = cast("list[BaseMessage]", action["messages"])
|
|
302
|
+
modified = True
|
|
303
|
+
|
|
304
|
+
if modified:
|
|
305
|
+
return {"messages": working}
|
|
306
|
+
|
|
307
|
+
return None
|
|
308
|
+
|
|
309
|
+
async def _amoderate_tool_messages(
|
|
310
|
+
self, messages: Sequence[BaseMessage]
|
|
311
|
+
) -> dict[str, Any] | None:
|
|
312
|
+
last_ai_idx = self._find_last_index(messages, AIMessage)
|
|
313
|
+
if last_ai_idx is None:
|
|
314
|
+
return None
|
|
315
|
+
|
|
316
|
+
working = list(messages)
|
|
317
|
+
modified = False
|
|
318
|
+
|
|
319
|
+
for idx in range(last_ai_idx + 1, len(working)):
|
|
320
|
+
msg = working[idx]
|
|
321
|
+
if not isinstance(msg, ToolMessage):
|
|
322
|
+
continue
|
|
323
|
+
|
|
324
|
+
text = self._extract_text(msg)
|
|
325
|
+
if not text:
|
|
326
|
+
continue
|
|
327
|
+
|
|
328
|
+
result = await self._amoderate(text)
|
|
329
|
+
if not result.flagged:
|
|
330
|
+
continue
|
|
331
|
+
|
|
332
|
+
action = self._apply_violation(
|
|
333
|
+
working, index=idx, stage="tool", content=text, result=result
|
|
334
|
+
)
|
|
335
|
+
if action:
|
|
336
|
+
if "jump_to" in action:
|
|
337
|
+
return action
|
|
338
|
+
working = cast("list[BaseMessage]", action["messages"])
|
|
339
|
+
modified = True
|
|
340
|
+
|
|
341
|
+
if modified:
|
|
342
|
+
return {"messages": working}
|
|
343
|
+
|
|
344
|
+
return None
|
|
345
|
+
|
|
346
|
+
def _moderate_user_message(
|
|
347
|
+
self, messages: Sequence[BaseMessage]
|
|
348
|
+
) -> dict[str, Any] | None:
|
|
349
|
+
idx = self._find_last_index(messages, HumanMessage)
|
|
350
|
+
if idx is None:
|
|
351
|
+
return None
|
|
352
|
+
|
|
353
|
+
message = messages[idx]
|
|
354
|
+
text = self._extract_text(message)
|
|
355
|
+
if not text:
|
|
356
|
+
return None
|
|
357
|
+
|
|
358
|
+
result = self._moderate(text)
|
|
359
|
+
if not result.flagged:
|
|
360
|
+
return None
|
|
361
|
+
|
|
362
|
+
return self._apply_violation(
|
|
363
|
+
messages, index=idx, stage="input", content=text, result=result
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
async def _amoderate_user_message(
|
|
367
|
+
self, messages: Sequence[BaseMessage]
|
|
368
|
+
) -> dict[str, Any] | None:
|
|
369
|
+
idx = self._find_last_index(messages, HumanMessage)
|
|
370
|
+
if idx is None:
|
|
371
|
+
return None
|
|
372
|
+
|
|
373
|
+
message = messages[idx]
|
|
374
|
+
text = self._extract_text(message)
|
|
375
|
+
if not text:
|
|
376
|
+
return None
|
|
377
|
+
|
|
378
|
+
result = await self._amoderate(text)
|
|
379
|
+
if not result.flagged:
|
|
380
|
+
return None
|
|
381
|
+
|
|
382
|
+
return self._apply_violation(
|
|
383
|
+
messages, index=idx, stage="input", content=text, result=result
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
def _apply_violation(
|
|
387
|
+
self,
|
|
388
|
+
messages: Sequence[BaseMessage],
|
|
389
|
+
*,
|
|
390
|
+
index: int | None,
|
|
391
|
+
stage: ViolationStage,
|
|
392
|
+
content: str,
|
|
393
|
+
result: Moderation,
|
|
394
|
+
) -> dict[str, Any] | None:
|
|
395
|
+
violation_text = self._format_violation_message(content, result)
|
|
396
|
+
|
|
397
|
+
if self.exit_behavior == "error":
|
|
398
|
+
raise OpenAIModerationError(
|
|
399
|
+
content=content,
|
|
400
|
+
stage=stage,
|
|
401
|
+
result=result,
|
|
402
|
+
message=violation_text,
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
if self.exit_behavior == "end":
|
|
406
|
+
return {"jump_to": "end", "messages": [AIMessage(content=violation_text)]}
|
|
407
|
+
|
|
408
|
+
if index is None:
|
|
409
|
+
return None
|
|
410
|
+
|
|
411
|
+
new_messages = list(messages)
|
|
412
|
+
original = new_messages[index]
|
|
413
|
+
new_messages[index] = cast(
|
|
414
|
+
BaseMessage, original.model_copy(update={"content": violation_text})
|
|
415
|
+
)
|
|
416
|
+
return {"messages": new_messages}
|
|
417
|
+
|
|
418
|
+
def _moderate(self, text: str) -> Moderation:
|
|
419
|
+
if self._client is None:
|
|
420
|
+
self._client = self._build_client()
|
|
421
|
+
response = self._client.moderations.create(model=self.model, input=text)
|
|
422
|
+
return response.results[0]
|
|
423
|
+
|
|
424
|
+
async def _amoderate(self, text: str) -> Moderation:
|
|
425
|
+
if self._async_client is None:
|
|
426
|
+
self._async_client = self._build_async_client()
|
|
427
|
+
response = await self._async_client.moderations.create(
|
|
428
|
+
model=self.model, input=text
|
|
429
|
+
)
|
|
430
|
+
return response.results[0]
|
|
431
|
+
|
|
432
|
+
def _build_client(self) -> OpenAI:
|
|
433
|
+
self._client = OpenAI()
|
|
434
|
+
return self._client
|
|
435
|
+
|
|
436
|
+
def _build_async_client(self) -> AsyncOpenAI:
|
|
437
|
+
self._async_client = AsyncOpenAI()
|
|
438
|
+
return self._async_client
|
|
439
|
+
|
|
440
|
+
def _format_violation_message(self, content: str, result: Moderation) -> str:
|
|
441
|
+
# Convert categories to dict and filter for flagged items
|
|
442
|
+
categories_dict = result.categories.model_dump()
|
|
443
|
+
categories = [
|
|
444
|
+
name.replace("_", " ")
|
|
445
|
+
for name, flagged in categories_dict.items()
|
|
446
|
+
if flagged
|
|
447
|
+
]
|
|
448
|
+
category_label = (
|
|
449
|
+
", ".join(categories) if categories else "OpenAI's safety policies"
|
|
450
|
+
)
|
|
451
|
+
template = self.violation_message or DEFAULT_VIOLATION_TEMPLATE
|
|
452
|
+
scores_json = json.dumps(result.category_scores.model_dump(), sort_keys=True)
|
|
453
|
+
try:
|
|
454
|
+
message = template.format(
|
|
455
|
+
categories=category_label,
|
|
456
|
+
category_scores=scores_json,
|
|
457
|
+
original_content=content,
|
|
458
|
+
)
|
|
459
|
+
except KeyError:
|
|
460
|
+
message = template
|
|
461
|
+
return message
|
|
462
|
+
|
|
463
|
+
def _find_last_index(
|
|
464
|
+
self, messages: Sequence[BaseMessage], message_type: type[BaseMessage]
|
|
465
|
+
) -> int | None:
|
|
466
|
+
for idx in range(len(messages) - 1, -1, -1):
|
|
467
|
+
if isinstance(messages[idx], message_type):
|
|
468
|
+
return idx
|
|
469
|
+
return None
|
|
470
|
+
|
|
471
|
+
def _extract_text(self, message: BaseMessage) -> str | None:
|
|
472
|
+
if message.content is None:
|
|
473
|
+
return None
|
|
474
|
+
text_accessor = getattr(message, "text", None)
|
|
475
|
+
if text_accessor is None:
|
|
476
|
+
return str(message.content)
|
|
477
|
+
text = str(text_accessor)
|
|
478
|
+
return text if text else None
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
__all__ = [
|
|
482
|
+
"OpenAIModerationError",
|
|
483
|
+
"OpenAIModerationMiddleware",
|
|
484
|
+
]
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Output parsers for OpenAI tools."""
|
|
2
|
+
|
|
3
|
+
from coco_core.output_parsers.openai_tools import (
|
|
4
|
+
JsonOutputKeyToolsParser,
|
|
5
|
+
JsonOutputToolsParser,
|
|
6
|
+
PydanticToolsParser,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
__all__ = ["JsonOutputKeyToolsParser", "JsonOutputToolsParser", "PydanticToolsParser"]
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Output parsers for OpenAI tools."""
|
|
2
|
+
|
|
3
|
+
from coco_core.output_parsers.openai_tools import (
|
|
4
|
+
JsonOutputKeyToolsParser,
|
|
5
|
+
JsonOutputToolsParser,
|
|
6
|
+
PydanticToolsParser,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
__all__ = ["JsonOutputKeyToolsParser", "JsonOutputToolsParser", "PydanticToolsParser"]
|
coco_openai/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""Custom tool decorator for OpenAI custom tools."""
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
from collections.abc import Awaitable, Callable
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from coco_core.tools import tool
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _make_wrapped_func(func: Callable[..., str]) -> Callable[..., list[dict[str, Any]]]:
|
|
11
|
+
def wrapped(x: str) -> list[dict[str, Any]]:
|
|
12
|
+
return [{"type": "custom_tool_call_output", "output": func(x)}]
|
|
13
|
+
|
|
14
|
+
return wrapped
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _make_wrapped_coroutine(
|
|
18
|
+
coroutine: Callable[..., Awaitable[str]],
|
|
19
|
+
) -> Callable[..., Awaitable[list[dict[str, Any]]]]:
|
|
20
|
+
async def wrapped(*args: Any, **kwargs: Any) -> list[dict[str, Any]]:
|
|
21
|
+
result = await coroutine(*args, **kwargs)
|
|
22
|
+
return [{"type": "custom_tool_call_output", "output": result}]
|
|
23
|
+
|
|
24
|
+
return wrapped
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def custom_tool(*args: Any, **kwargs: Any) -> Any:
|
|
28
|
+
"""Decorator to create an OpenAI custom tool.
|
|
29
|
+
|
|
30
|
+
Custom tools allow for tools with (potentially long) freeform string inputs.
|
|
31
|
+
|
|
32
|
+
See below for an example using CocoGraph:
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
@custom_tool
|
|
36
|
+
def execute_code(code: str) -> str:
|
|
37
|
+
\"\"\"Execute python code.\"\"\"
|
|
38
|
+
return "27"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
model = ChatOpenAI(model="gpt-5", output_version="responses/v1")
|
|
42
|
+
|
|
43
|
+
agent = create_react_agent(model, [execute_code])
|
|
44
|
+
|
|
45
|
+
input_message = {"role": "user", "content": "Use the tool to calculate 3^3."}
|
|
46
|
+
for step in agent.stream(
|
|
47
|
+
{"messages": [input_message]},
|
|
48
|
+
stream_mode="values",
|
|
49
|
+
):
|
|
50
|
+
step["messages"][-1].pretty_print()
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
You can also specify a format for a corresponding context-free grammar using the
|
|
54
|
+
`format` kwarg:
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from coco_openai import ChatOpenAI, custom_tool
|
|
58
|
+
from coco_graph.prebuilt import create_react_agent
|
|
59
|
+
|
|
60
|
+
grammar = \"\"\"
|
|
61
|
+
start: expr
|
|
62
|
+
expr: term (SP ADD SP term)* -> add
|
|
63
|
+
| term
|
|
64
|
+
term: factor (SP MUL SP factor)* -> mul
|
|
65
|
+
| factor
|
|
66
|
+
factor: INT
|
|
67
|
+
SP: " "
|
|
68
|
+
ADD: "+"
|
|
69
|
+
MUL: "*"
|
|
70
|
+
%import common.INT
|
|
71
|
+
\"\"\"
|
|
72
|
+
|
|
73
|
+
format = {"type": "grammar", "syntax": "lark", "definition": grammar}
|
|
74
|
+
|
|
75
|
+
# highlight-next-line
|
|
76
|
+
@custom_tool(format=format)
|
|
77
|
+
def do_math(input_string: str) -> str:
|
|
78
|
+
\"\"\"Do a mathematical operation.\"\"\"
|
|
79
|
+
return "27"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
model = ChatOpenAI(model="gpt-5", output_version="responses/v1")
|
|
83
|
+
|
|
84
|
+
agent = create_react_agent(model, [do_math])
|
|
85
|
+
|
|
86
|
+
input_message = {"role": "user", "content": "Use the tool to calculate 3^3."}
|
|
87
|
+
for step in agent.stream(
|
|
88
|
+
{"messages": [input_message]},
|
|
89
|
+
stream_mode="values",
|
|
90
|
+
):
|
|
91
|
+
step["messages"][-1].pretty_print()
|
|
92
|
+
```
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
def decorator(func: Callable[..., Any]) -> Any:
|
|
96
|
+
metadata = {"type": "custom_tool"}
|
|
97
|
+
if "format" in kwargs:
|
|
98
|
+
metadata["format"] = kwargs.pop("format")
|
|
99
|
+
tool_obj = tool(infer_schema=False, **kwargs)(func)
|
|
100
|
+
tool_obj.metadata = metadata
|
|
101
|
+
tool_obj.description = func.__doc__
|
|
102
|
+
if inspect.iscoroutinefunction(func):
|
|
103
|
+
tool_obj.coroutine = _make_wrapped_coroutine(func)
|
|
104
|
+
else:
|
|
105
|
+
tool_obj.func = _make_wrapped_func(func)
|
|
106
|
+
return tool_obj
|
|
107
|
+
|
|
108
|
+
if args and callable(args[0]) and not kwargs:
|
|
109
|
+
return decorator(args[0])
|
|
110
|
+
|
|
111
|
+
return decorator
|