Compare commits

..
Author SHA1 Message Date
lorenzejay e77d1c0d6e added 2026-04-24 13:53:44 -07:00
lorenzejay c8716e7062 regen cassettes 2026-04-24 13:52:58 -07:00
lorenzejay e48cf3604e refactor: implement _loop_response_model to manage response_model during tool loops
This change introduces the _loop_response_model method in BaseAgentExecutor to conditionally return the response_model based on the presence of tools. The CrewAgentExecutor and AgentExecutor classes have been updated to utilize this new method, ensuring that the response_model is not sent to the LLM during tool loops, which prevents issues with structured-output schemas. Additionally, tests have been added to verify the correct behavior of this implementation.
2026-04-24 12:20:32 -07:00
14 changed files with 444 additions and 383 deletions

No files matched your search

+2
View File
@@ -187,6 +187,8 @@ HEADERS_TO_FILTER = {
"anthropic-ratelimit-tokens-remaining": "ANTHROPIC-RATELIMIT-TOKENS-REMAINING-XXX",
"anthropic-ratelimit-tokens-reset": "ANTHROPIC-RATELIMIT-TOKENS-RESET-XXX",
"x-amz-date": "X-AMZ-DATE-XXX",
"x-amz-security-token": "X-AMZ-SECURITY-TOKEN-XXX",
"x-crewai-organization-id": "X-CREWAI-ORGANIZATION-ID-XXX",
"amz-sdk-invocation-id": "AMZ-SDK-INVOCATION-ID-XXX",
"accept-encoding": "ACCEPT-ENCODING-XXX",
"x-amzn-requestid": "X-AMZN-REQUESTID-XXX",
-6
View File
@@ -94,16 +94,10 @@ try:
}
from crewai.tools.base_tool import BaseTool as _BaseTool
from crewai.tools.flow_tool import (
FlowTool as _FlowTool,
create_flow_tools as _create_flow_tools,
)
from crewai.tools.structured_tool import CrewStructuredTool as _CrewStructuredTool
_base_namespace["BaseTool"] = _BaseTool
_base_namespace["CrewStructuredTool"] = _CrewStructuredTool
_base_namespace["FlowTool"] = _FlowTool
_base_namespace["create_flow_tools"] = _create_flow_tools # type: ignore[assignment]
try:
from crewai.a2a.config import (
-16
View File
@@ -85,7 +85,6 @@ from crewai.skills.loader import activate_skill, discover_skills
from crewai.skills.models import INSTRUCTIONS, Skill as SkillModel
from crewai.state.checkpoint_config import CheckpointConfig, apply_checkpoint
from crewai.tools.agent_tools.agent_tools import AgentTools
from crewai.tools.flow_tool import create_flow_tools
from crewai.types.callback import SerializableCallable
from crewai.utilities.agent_utils import (
get_tool_names,
@@ -306,10 +305,6 @@ class Agent(BaseAgent):
Can be a single A2AConfig/A2AClientConfig/A2AServerConfig, or a list of any number of A2AConfig/A2AClientConfig with a single A2AServerConfig.
""",
)
flows: list[Any] | None = Field(
default=None,
description="Flow classes that the agent can invoke as tools. Each entry is a Flow subclass (not an instance).",
)
agent_executor: CrewAgentExecutor | AgentExecutor | None = Field(
default=None, description="An instance of the CrewAgentExecutor class."
)
@@ -352,7 +347,6 @@ class Agent(BaseAgent):
)
self.set_skills()
self._set_flow_tools()
if self.reasoning and self.planning_config is None:
warnings.warn(
@@ -465,16 +459,6 @@ class Agent(BaseAgent):
self.skills = resolved if resolved else None
def _set_flow_tools(self) -> None:
"""Convert Flow classes in ``self.flows`` to tools and merge them."""
if not self.flows:
return
flow_tools = create_flow_tools(self.flows)
if flow_tools:
if self.tools is None:
self.tools = []
self.tools.extend(flow_tools)
def _is_any_available_memory(self) -> bool:
"""Check if unified memory is available (agent or crew)."""
if getattr(self, "memory", None):
@@ -2,7 +2,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING
from pydantic import BaseModel, Field, PrivateAttr
from pydantic import BaseModel as PydanticBaseModel, Field, PrivateAttr
from crewai.agents.parser import AgentFinish
from crewai.memory.utils import sanitize_scope_name
@@ -16,7 +16,7 @@ if TYPE_CHECKING:
from crewai.task import Task
class BaseAgentExecutor(BaseModel):
class BaseAgentExecutor(PydanticBaseModel):
model_config = {"arbitrary_types_allowed": True}
executor_type: str = "base"
@@ -28,6 +28,26 @@ class BaseAgentExecutor(BaseModel):
messages: list[LLMMessage] = Field(default_factory=list)
_resuming: bool = PrivateAttr(default=False)
def _loop_response_model(self) -> type[PydanticBaseModel] | None:
"""Return the response_model to forward to the LLM during a tool loop.
When the executor has tools, returns ``None``. Sending a strict
structured-output schema alongside ``tools=`` makes many providers
(notably OpenAI's Responses API with ``text.format`` json_schema)
constrain the first response to the schema, so the model emits
placeholder JSON instead of calling tools. Schema conversion is
applied as post-processing in ``Task._export_output()`` (Crew path)
or via ``Converter`` in ``Agent._build_output_from_result()``
(standalone kickoff path).
Subclasses must define ``original_tools`` and ``response_model``
attributes; this base implementation reads them via ``getattr`` so
the helper works for any executor that exposes those names.
"""
if getattr(self, "original_tools", None):
return None
return getattr(self, "response_model", None)
def _save_to_memory(self, output: AgentFinish) -> None:
"""Save task result to unified memory (memory or crew._memory)."""
if self.agent is None:
@@ -334,6 +334,7 @@ class CrewAgentExecutor(BaseAgentExecutor):
enforce_rpm_limit(self.request_within_rpm_limit)
loop_response_model = self._loop_response_model()
answer = get_llm_response(
llm=cast("BaseLLM", self.llm),
messages=self.messages,
@@ -341,11 +342,11 @@ class CrewAgentExecutor(BaseAgentExecutor):
printer=PRINTER,
from_task=self.task,
from_agent=self.agent,
response_model=self.response_model,
response_model=loop_response_model,
executor_context=self,
verbose=self.agent.verbose,
)
if self.response_model is not None:
if loop_response_model is not None:
try:
if isinstance(answer, BaseModel):
output_json = answer.model_dump_json()
@@ -355,7 +356,7 @@ class CrewAgentExecutor(BaseAgentExecutor):
text=output_json,
)
else:
self.response_model.model_validate_json(answer)
loop_response_model.model_validate_json(answer)
formatted_answer = AgentFinish(
thought="",
output=answer,
@@ -486,7 +487,7 @@ class CrewAgentExecutor(BaseAgentExecutor):
available_functions=None,
from_task=self.task,
from_agent=self.agent,
response_model=self.response_model,
response_model=self._loop_response_model(),
executor_context=self,
verbose=self.agent.verbose,
)
@@ -1142,6 +1143,7 @@ class CrewAgentExecutor(BaseAgentExecutor):
enforce_rpm_limit(self.request_within_rpm_limit)
loop_response_model = self._loop_response_model()
answer = await aget_llm_response(
llm=cast("BaseLLM", self.llm),
messages=self.messages,
@@ -1149,12 +1151,12 @@ class CrewAgentExecutor(BaseAgentExecutor):
printer=PRINTER,
from_task=self.task,
from_agent=self.agent,
response_model=self.response_model,
response_model=loop_response_model,
executor_context=self,
verbose=self.agent.verbose,
)
if self.response_model is not None:
if loop_response_model is not None:
try:
if isinstance(answer, BaseModel):
output_json = answer.model_dump_json()
@@ -1164,7 +1166,7 @@ class CrewAgentExecutor(BaseAgentExecutor):
text=output_json,
)
else:
self.response_model.model_validate_json(answer)
loop_response_model.model_validate_json(answer)
formatted_answer = AgentFinish(
thought="",
output=answer,
@@ -1295,7 +1297,7 @@ class CrewAgentExecutor(BaseAgentExecutor):
available_functions=None,
from_task=self.task,
from_agent=self.agent,
response_model=self.response_model,
response_model=self._loop_response_model(),
executor_context=self,
verbose=self.agent.verbose,
)
@@ -1229,7 +1229,7 @@ class AgentExecutor(Flow[AgentExecutorState], BaseAgentExecutor): # type: ignor
printer=PRINTER,
from_task=self.task,
from_agent=self.agent,
response_model=self.response_model,
response_model=self._loop_response_model(),
executor_context=self,
verbose=self.agent.verbose,
)
@@ -1317,7 +1317,7 @@ class AgentExecutor(Flow[AgentExecutorState], BaseAgentExecutor): # type: ignor
available_functions=None,
from_task=self.task,
from_agent=self.agent,
response_model=self.response_model,
response_model=self._loop_response_model(),
executor_context=self,
verbose=self.agent.verbose,
)
-82
View File
@@ -1,82 +0,0 @@
"""Wrap Flow classes as callable tools so agents can invoke them."""
from __future__ import annotations
import json
from typing import Any
from pydantic import BaseModel, Field
from crewai.tools.base_tool import BaseTool
from crewai.utilities.string_utils import sanitize_tool_name
class FlowToolInputSchema(BaseModel):
"""Default input schema for a FlowTool."""
inputs: str = Field(
default="{}",
description=(
"JSON string of key-value pairs to pass as inputs to the flow. "
"Use '{}' if the flow requires no inputs."
),
)
class FlowTool(BaseTool):
"""Wraps a Flow class as a BaseTool so an agent can invoke it.
The tool instantiates the Flow, calls ``kickoff(inputs=...)`` and returns
the result as a string.
"""
name: str = ""
description: str = ""
flow_class: Any = Field(
default=None,
description="The Flow class (not instance) to wrap.",
exclude=True,
)
args_schema: Any = FlowToolInputSchema
def _run(self, inputs: str = "{}") -> str:
"""Instantiate the Flow, run kickoff, and return the result."""
try:
parsed_inputs = json.loads(inputs) if isinstance(inputs, str) else inputs
except (json.JSONDecodeError, TypeError):
parsed_inputs = {}
if not isinstance(parsed_inputs, dict):
parsed_inputs = {}
flow_instance = self.flow_class()
result = flow_instance.kickoff(inputs=parsed_inputs if parsed_inputs else None)
return str(result)
def create_flow_tools(flows: list[type] | None) -> list[BaseTool]:
"""Convert a list of Flow classes into BaseTool wrappers.
Args:
flows: Flow classes (not instances) to wrap as tools.
Returns:
A list of FlowTool instances ready for agent use.
"""
if not flows:
return []
tools: list[BaseTool] = []
for flow_cls in flows:
name = sanitize_tool_name(flow_cls.__name__)
docstring = (flow_cls.__doc__ or "").strip()
description = docstring if docstring else f"Run the {flow_cls.__name__} flow."
tools.append(
FlowTool(
name=name,
description=description,
flow_class=flow_cls,
)
)
return tools
@@ -2044,3 +2044,103 @@ class TestVisionImageFormatContract:
assert hasattr(AnthropicCompletion, "_convert_image_blocks"), (
"Anthropic provider must have _convert_image_blocks for auto-conversion"
)
class TestLoopResponseModelGuard:
"""Regression: response_model must not be sent to the LLM during a tool loop.
Bug: when output_pydantic + tools are both set, OpenAI's Responses API
(and several other providers) constrain the first response to the
structured-output schema, so the model emits placeholder JSON instead of
calling tools. Schema conversion is post-processed, so the LLM call must
omit response_model whenever tools are present.
"""
def _make_pydantic_model(self):
from pydantic import BaseModel as _BaseModel
class _Out(_BaseModel):
answer: str
return _Out
def _crew_executor(self, *, with_tools: bool, response_model):
from crewai.agents.crew_agent_executor import CrewAgentExecutor
tool = Mock()
tool.name = "search"
tool.description = "search"
executor = CrewAgentExecutor.model_construct(
response_model=response_model,
original_tools=[tool] if with_tools else [],
)
return executor
def test_crew_helper_strips_when_tools_present(self):
Model = self._make_pydantic_model()
ex = self._crew_executor(with_tools=True, response_model=Model)
assert ex._loop_response_model() is None
def test_crew_helper_preserves_when_no_tools(self):
Model = self._make_pydantic_model()
ex = self._crew_executor(with_tools=False, response_model=Model)
assert ex._loop_response_model() is Model
def test_crew_helper_returns_none_when_no_response_model(self):
ex = self._crew_executor(with_tools=False, response_model=None)
assert ex._loop_response_model() is None
def test_experimental_helper_strips_when_tools_present(self):
Model = self._make_pydantic_model()
llm = Mock()
llm.supports_stop_words.return_value = False
tool = Mock()
tool.name = "search"
ex = _build_executor(llm=llm, response_model=Model, original_tools=[tool])
assert ex._loop_response_model() is None
def test_experimental_helper_preserves_when_no_tools(self):
Model = self._make_pydantic_model()
llm = Mock()
llm.supports_stop_words.return_value = False
ex = _build_executor(llm=llm, response_model=Model, original_tools=[])
assert ex._loop_response_model() is Model
def test_loop_call_sites_route_through_helper(self):
"""Source-level guard: every in-loop LLM call must use the helper.
This catches regressions where someone reintroduces
`response_model=self.response_model` inside a tool loop.
"""
import inspect
from crewai.agents import crew_agent_executor as _crew_mod
from crewai.experimental import agent_executor as _exp_mod
for mod, names in (
(
_crew_mod,
(
"_invoke_loop_react",
"_invoke_loop_native_tools",
"_ainvoke_loop_react",
"_ainvoke_loop_native_tools",
),
),
(
_exp_mod,
("call_llm_and_parse", "call_llm_native_tools"),
),
):
cls = (
_crew_mod.CrewAgentExecutor
if mod is _crew_mod
else _exp_mod.AgentExecutor
)
for name in names:
src = inspect.getsource(getattr(cls, name))
assert "response_model=self.response_model" not in src, (
f"{cls.__name__}.{name} forwards self.response_model directly. "
"Use self._loop_response_model() instead — see "
"TestLoopResponseModelGuard for the bug it prevents."
)
@@ -1,14 +1,10 @@
interactions:
- request:
body: '{"max_tokens":4096,"messages":[{"role":"user","content":"\nCurrent Task:
Calculate 15 + 27 using your add_numbers tool. Report the result."}],"model":"claude-3-5-haiku-20241022","stop_sequences":["\nObservation:"],"stream":false,"system":"You
Calculate 15 + 27 using your add_numbers tool. Report the result."}],"model":"claude-sonnet-4-6","stop_sequences":["\nObservation:"],"stream":false,"system":"You
are Calculator. You are a calculator assistant that uses tools to compute results.\nYour
personal goal is: Perform calculations using available tools","tool_choice":{"type":"tool","name":"structured_output"},"tools":[{"name":"structured_output","description":"Output
the structured response","input_schema":{"type":"object","description":"Structured
output for calculation results.","title":"CalculationResult","properties":{"operation":{"type":"string","description":"The
mathematical operation performed","title":"Operation"},"result":{"type":"integer","description":"The
result of the calculation","title":"Result"},"explanation":{"type":"string","description":"Brief
explanation of the calculation","title":"Explanation"}},"additionalProperties":false,"required":["operation","result","explanation"]}}]}'
personal goal is: Perform calculations using available tools","tools":[{"name":"add_numbers","description":"Add
two numbers together and return the sum.","input_schema":{"properties":{"a":{"type":"integer"},"b":{"type":"integer"}},"required":["a","b"],"type":"object","additionalProperties":false},"strict":true}]}'
headers:
User-Agent:
- X-USER-AGENT-XXX
@@ -21,7 +17,7 @@ interactions:
connection:
- keep-alive
content-length:
- '1050'
- '638'
content-type:
- application/json
host:
@@ -37,7 +33,7 @@ interactions:
x-stainless-os:
- X-STAINLESS-OS-XXX
x-stainless-package-version:
- 0.73.0
- 0.96.0
x-stainless-retry-count:
- '0'
x-stainless-runtime:
@@ -50,8 +46,8 @@ interactions:
uri: https://api.anthropic.com/v1/messages
response:
body:
string: '{"model":"claude-3-5-haiku-20241022","id":"msg_01MsoNSVoPuoMYGCcJLvfXS6","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_01TtAsqddWjE7C4GYmCKavdg","name":"structured_output","input":{"operation":"Addition","result":42,"explanation":"Added
15 and 27 together"}}],"stop_reason":"tool_use","stop_sequence":null,"usage":{"input_tokens":573,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":75,"service_tier":"standard","inference_geo":"not_available"}}'
string: '{"model":"claude-sonnet-4-6","id":"msg_016CHZY3K6G73PFc4iAJVC5C","type":"message","role":"assistant","content":[{"type":"text","text":"I''ll
calculate 15 + 27 right away using the `add_numbers` tool!"},{"type":"tool_use","id":"toolu_011UPTzkFNsgYshddoTX2A8X","name":"add_numbers","input":{"a":15,"b":27},"caller":{"type":"direct"}}],"stop_reason":"tool_use","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":633,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":92,"service_tier":"standard","inference_geo":"global"}}'
headers:
CF-RAY:
- CF-RAY-XXX
@@ -62,7 +58,7 @@ interactions:
Content-Type:
- application/json
Date:
- Thu, 12 Feb 2026 22:11:20 GMT
- Fri, 24 Apr 2026 20:48:28 GMT
Server:
- cloudflare
Transfer-Encoding:
@@ -84,11 +80,119 @@ interactions:
anthropic-ratelimit-output-tokens-reset:
- ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-RESET-XXX
anthropic-ratelimit-requests-limit:
- '4000'
- '20000'
anthropic-ratelimit-requests-remaining:
- '3999'
- '19999'
anthropic-ratelimit-requests-reset:
- '2026-02-12T22:11:18Z'
- '2026-04-24T20:48:25Z'
anthropic-ratelimit-tokens-limit:
- ANTHROPIC-RATELIMIT-TOKENS-LIMIT-XXX
anthropic-ratelimit-tokens-remaining:
- ANTHROPIC-RATELIMIT-TOKENS-REMAINING-XXX
anthropic-ratelimit-tokens-reset:
- ANTHROPIC-RATELIMIT-TOKENS-RESET-XXX
cf-cache-status:
- DYNAMIC
request-id:
- REQUEST-ID-XXX
set-cookie:
- SET-COOKIE-XXX
strict-transport-security:
- STS-XXX
x-envoy-upstream-service-time:
- '2056'
status:
code: 200
message: OK
- request:
body: '{"max_tokens":4096,"messages":[{"role":"user","content":"\nCurrent Task:
Calculate 15 + 27 using your add_numbers tool. Report the result."},{"role":"assistant","content":[{"type":"tool_use","id":"toolu_011UPTzkFNsgYshddoTX2A8X","name":"add_numbers","input":{"a":15,"b":27}}]},{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_011UPTzkFNsgYshddoTX2A8X","content":"42"}]}],"model":"claude-sonnet-4-6","stop_sequences":["\nObservation:"],"stream":false,"system":"You
are Calculator. You are a calculator assistant that uses tools to compute results.\nYour
personal goal is: Perform calculations using available tools","tools":[{"name":"add_numbers","description":"Add
two numbers together and return the sum.","input_schema":{"properties":{"a":{"type":"integer"},"b":{"type":"integer"}},"required":["a","b"],"type":"object","additionalProperties":false},"strict":true}]}'
headers:
User-Agent:
- X-USER-AGENT-XXX
accept:
- application/json
accept-encoding:
- ACCEPT-ENCODING-XXX
anthropic-version:
- '2023-06-01'
connection:
- keep-alive
content-length:
- '887'
content-type:
- application/json
cookie:
- COOKIE-XXX
host:
- api.anthropic.com
x-api-key:
- X-API-KEY-XXX
x-stainless-arch:
- X-STAINLESS-ARCH-XXX
x-stainless-async:
- 'false'
x-stainless-lang:
- python
x-stainless-os:
- X-STAINLESS-OS-XXX
x-stainless-package-version:
- 0.96.0
x-stainless-retry-count:
- '0'
x-stainless-runtime:
- CPython
x-stainless-runtime-version:
- 3.13.3
x-stainless-timeout:
- NOT_GIVEN
method: POST
uri: https://api.anthropic.com/v1/messages
response:
body:
string: "{\"model\":\"claude-sonnet-4-6\",\"id\":\"msg_01V13FHBygrTHV6pnhocqvd9\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[{\"type\":\"text\",\"text\":\"The
result of **15 + 27 = 42**! \U0001F389\\n\\nUsing the `add_numbers` tool,
I computed the sum of 15 and 27, and the result is **42**.\"}],\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":717,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":51,\"service_tier\":\"standard\",\"inference_geo\":\"global\"}}"
headers:
CF-RAY:
- CF-RAY-XXX
Connection:
- keep-alive
Content-Security-Policy:
- CSP-FILTERED
Content-Type:
- application/json
Date:
- Fri, 24 Apr 2026 20:48:32 GMT
Server:
- cloudflare
Transfer-Encoding:
- chunked
X-Robots-Tag:
- none
anthropic-organization-id:
- ANTHROPIC-ORGANIZATION-ID-XXX
anthropic-ratelimit-input-tokens-limit:
- ANTHROPIC-RATELIMIT-INPUT-TOKENS-LIMIT-XXX
anthropic-ratelimit-input-tokens-remaining:
- ANTHROPIC-RATELIMIT-INPUT-TOKENS-REMAINING-XXX
anthropic-ratelimit-input-tokens-reset:
- ANTHROPIC-RATELIMIT-INPUT-TOKENS-RESET-XXX
anthropic-ratelimit-output-tokens-limit:
- ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-LIMIT-XXX
anthropic-ratelimit-output-tokens-remaining:
- ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-REMAINING-XXX
anthropic-ratelimit-output-tokens-reset:
- ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-RESET-XXX
anthropic-ratelimit-requests-limit:
- '20000'
anthropic-ratelimit-requests-remaining:
- '19999'
anthropic-ratelimit-requests-reset:
- '2026-04-24T20:48:28Z'
anthropic-ratelimit-tokens-limit:
- ANTHROPIC-RATELIMIT-TOKENS-LIMIT-XXX
anthropic-ratelimit-tokens-remaining:
@@ -102,7 +206,139 @@ interactions:
strict-transport-security:
- STS-XXX
x-envoy-upstream-service-time:
- '1234'
- '4700'
status:
code: 200
message: OK
- request:
body: "{\"max_tokens\":4096,\"messages\":[{\"role\":\"user\",\"content\":\"The
result of **15 + 27 = 42**! \U0001F389\\n\\nUsing the `add_numbers` tool, I
computed the sum of 15 and 27, and the result is **42**.\"}],\"model\":\"claude-sonnet-4-6\",\"stop_sequences\":[\"\\nObservation:\"],\"stream\":false,\"system\":\"Format
your final answer according to the following OpenAPI schema: {\\n \\\"type\\\":
\\\"json_schema\\\",\\n \\\"json_schema\\\": {\\n \\\"name\\\": \\\"CalculationResult\\\",\\n
\ \\\"strict\\\": true,\\n \\\"schema\\\": {\\n \\\"description\\\":
\\\"Structured output for calculation results.\\\",\\n \\\"properties\\\":
{\\n \\\"operation\\\": {\\n \\\"description\\\": \\\"The mathematical
operation performed\\\",\\n \\\"title\\\": \\\"Operation\\\",\\n \\\"type\\\":
\\\"string\\\"\\n },\\n \\\"result\\\": {\\n \\\"description\\\":
\\\"The result of the calculation\\\",\\n \\\"title\\\": \\\"Result\\\",\\n
\ \\\"type\\\": \\\"integer\\\"\\n },\\n \\\"explanation\\\":
{\\n \\\"description\\\": \\\"Brief explanation of the calculation\\\",\\n
\ \\\"title\\\": \\\"Explanation\\\",\\n \\\"type\\\": \\\"string\\\"\\n
\ }\\n },\\n \\\"required\\\": [\\n \\\"operation\\\",\\n
\ \\\"result\\\",\\n \\\"explanation\\\"\\n ],\\n \\\"title\\\":
\\\"CalculationResult\\\",\\n \\\"type\\\": \\\"object\\\",\\n \\\"additionalProperties\\\":
false\\n }\\n }\\n}\\n\\nIMPORTANT: Preserve the original content exactly
as-is. Do NOT rewrite, paraphrase, or modify the meaning of the content. Only
structure it to match the schema format.\\n\\nDo not include the OpenAPI schema
in the final output. Ensure the final output does not include any code block
markers like ```json or ```python.\",\"tool_choice\":{\"type\":\"tool\",\"name\":\"structured_output\"},\"tools\":[{\"name\":\"structured_output\",\"description\":\"Output
the structured response\",\"input_schema\":{\"type\":\"object\",\"description\":\"Structured
output for calculation results.\",\"title\":\"CalculationResult\",\"properties\":{\"operation\":{\"type\":\"string\",\"description\":\"The
mathematical operation performed\",\"title\":\"Operation\"},\"result\":{\"type\":\"integer\",\"description\":\"The
result of the calculation\",\"title\":\"Result\"},\"explanation\":{\"type\":\"string\",\"description\":\"Brief
explanation of the calculation\",\"title\":\"Explanation\"}},\"additionalProperties\":false,\"required\":[\"operation\",\"result\",\"explanation\"]}}]}"
headers:
User-Agent:
- X-USER-AGENT-XXX
accept:
- application/json
accept-encoding:
- ACCEPT-ENCODING-XXX
anthropic-version:
- '2023-06-01'
connection:
- keep-alive
content-length:
- '2313'
content-type:
- application/json
cookie:
- COOKIE-XXX
host:
- api.anthropic.com
x-api-key:
- X-API-KEY-XXX
x-stainless-arch:
- X-STAINLESS-ARCH-XXX
x-stainless-async:
- 'false'
x-stainless-lang:
- python
x-stainless-os:
- X-STAINLESS-OS-XXX
x-stainless-package-version:
- 0.96.0
x-stainless-retry-count:
- '0'
x-stainless-runtime:
- CPython
x-stainless-runtime-version:
- 3.13.3
x-stainless-timeout:
- NOT_GIVEN
method: POST
uri: https://api.anthropic.com/v1/messages
response:
body:
string: "{\"model\":\"claude-sonnet-4-6\",\"id\":\"msg_01Csx2k6XmGyjfWkdLrXzLyA\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[{\"type\":\"tool_use\",\"id\":\"toolu_01DzJ3ZUUnvPPJgzioZnxjKM\",\"name\":\"structured_output\",\"input\":{\"operation\":\"add_numbers\",\"result\":42,\"explanation\":\"The
result of **15 + 27 = 42**! \U0001F389\\n\\nUsing the `add_numbers` tool,
I computed the sum of 15 and 27, and the result is **42**.\"},\"caller\":{\"type\":\"direct\"}}],\"stop_reason\":\"tool_use\",\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":1122,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":116,\"service_tier\":\"standard\",\"inference_geo\":\"global\"}}"
headers:
CF-RAY:
- CF-RAY-XXX
Connection:
- keep-alive
Content-Security-Policy:
- CSP-FILTERED
Content-Type:
- application/json
Date:
- Fri, 24 Apr 2026 20:48:34 GMT
Server:
- cloudflare
Transfer-Encoding:
- chunked
X-Robots-Tag:
- none
anthropic-organization-id:
- ANTHROPIC-ORGANIZATION-ID-XXX
anthropic-ratelimit-input-tokens-limit:
- ANTHROPIC-RATELIMIT-INPUT-TOKENS-LIMIT-XXX
anthropic-ratelimit-input-tokens-remaining:
- ANTHROPIC-RATELIMIT-INPUT-TOKENS-REMAINING-XXX
anthropic-ratelimit-input-tokens-reset:
- ANTHROPIC-RATELIMIT-INPUT-TOKENS-RESET-XXX
anthropic-ratelimit-output-tokens-limit:
- ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-LIMIT-XXX
anthropic-ratelimit-output-tokens-remaining:
- ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-REMAINING-XXX
anthropic-ratelimit-output-tokens-reset:
- ANTHROPIC-RATELIMIT-OUTPUT-TOKENS-RESET-XXX
anthropic-ratelimit-requests-limit:
- '20000'
anthropic-ratelimit-requests-remaining:
- '19999'
anthropic-ratelimit-requests-reset:
- '2026-04-24T20:48:32Z'
anthropic-ratelimit-tokens-limit:
- ANTHROPIC-RATELIMIT-TOKENS-LIMIT-XXX
anthropic-ratelimit-tokens-remaining:
- ANTHROPIC-RATELIMIT-TOKENS-REMAINING-XXX
anthropic-ratelimit-tokens-reset:
- ANTHROPIC-RATELIMIT-TOKENS-RESET-XXX
cf-cache-status:
- DYNAMIC
request-id:
- REQUEST-ID-XXX
server-timing:
- x-originResponse;dur=1594
strict-transport-security:
- STS-XXX
vary:
- Accept-Encoding
x-envoy-upstream-service-time:
- '1588'
status:
code: 200
message: OK
@@ -8,20 +8,10 @@ interactions:
[{"toolSpec": {"name": "add_numbers", "description": "Add two numbers together
and return the sum.", "inputSchema": {"json": {"properties": {"a": {"title":
"A", "type": "integer"}, "b": {"title": "B", "type": "integer"}}, "required":
["a", "b"], "type": "object", "additionalProperties": false}}}}, {"toolSpec":
{"name": "structured_output", "description": "Use this tool to provide your
final structured response. Call this tool when you have gathered all necessary
information and are ready to provide the final answer in the required format.",
"inputSchema": {"json": {"description": "Structured output for calculation results.",
"properties": {"operation": {"description": "The mathematical operation performed",
"title": "Operation", "type": "string"}, "result": {"description": "The result
of the calculation", "title": "Result", "type": "integer"}, "explanation": {"description":
"Brief explanation of the calculation", "title": "Explanation", "type": "string"}},
"required": ["operation", "result", "explanation"], "title": "CalculationResult",
"type": "object", "additionalProperties": false}}}}]}}'
["a", "b"], "type": "object", "additionalProperties": false}}}}]}}'
headers:
Content-Length:
- '1509'
- '702'
Content-Type:
- !!binary |
YXBwbGljYXRpb24vanNvbg==
@@ -36,21 +26,23 @@ interactions:
- AUTHORIZATION-XXX
x-amz-date:
- X-AMZ-DATE-XXX
x-amz-security-token:
- X-AMZ-SECURITY-TOKEN-XXX
method: POST
uri: https://bedrock-runtime.us-east-1.amazonaws.com/model/anthropic.claude-3-sonnet-20240229-v1%3A0/converse
uri: https://bedrock-runtime.us-east-1.amazonaws.com/model/us.anthropic.claude-sonnet-4-6/converse
response:
body:
string: '{"metrics":{"latencyMs":1968},"output":{"message":{"content":[{"text":"Okay,
let''s calculate 15 + 27 using the add_numbers tool:"},{"toolUse":{"input":{"a":15,"b":27},"name":"add_numbers","toolUseId":"tooluse_pSseOamVELzpL3kQG5VukN"}}],"role":"assistant"}},"stopReason":"tool_use","usage":{"inputTokens":488,"outputTokens":91,"serverToolUsage":{},"totalTokens":579}}'
string: '{"metrics":{"latencyMs":1570},"output":{"message":{"content":[{"text":"I''ll
calculate 15 + 27 right away using the `add_numbers` tool!"},{"toolUse":{"input":{"a":15,"b":27},"name":"add_numbers","toolUseId":"tooluse_lYs0ulbVGQaYseRBvZ7tdX","type":"tool_use"}}],"role":"assistant"}},"stopReason":"tool_use","usage":{"cacheReadInputTokenCount":0,"cacheReadInputTokens":0,"cacheWriteInputTokenCount":0,"cacheWriteInputTokens":0,"inputTokens":645,"outputTokens":92,"serverToolUsage":{},"totalTokens":737}}'
headers:
Connection:
- keep-alive
Content-Length:
- '366'
- '500'
Content-Type:
- application/json
Date:
- Thu, 12 Feb 2026 22:01:04 GMT
- Fri, 24 Apr 2026 20:49:36 GMT
x-amzn-RequestId:
- X-AMZN-REQUESTID-XXX
status:
@@ -59,29 +51,19 @@ interactions:
- request:
body: '{"messages": [{"role": "user", "content": [{"text": "\nCurrent Task: Calculate
15 + 27 using your add_numbers tool. Report the result."}]}, {"role": "assistant",
"content": [{"toolUse": {"toolUseId": "tooluse_pSseOamVELzpL3kQG5VukN", "name":
"content": [{"toolUse": {"toolUseId": "tooluse_lYs0ulbVGQaYseRBvZ7tdX", "name":
"add_numbers", "input": {"a": 15, "b": 27}}}]}, {"role": "user", "content":
[{"toolResult": {"toolUseId": "tooluse_pSseOamVELzpL3kQG5VukN", "content": [{"text":
[{"toolResult": {"toolUseId": "tooluse_lYs0ulbVGQaYseRBvZ7tdX", "content": [{"text":
"42"}]}}]}], "inferenceConfig": {"stopSequences": ["\nObservation:"]}, "system":
[{"text": "You are Calculator. You are a calculator assistant that uses tools
to compute results.\nYour personal goal is: Perform calculations using available
tools"}], "toolConfig": {"tools": [{"toolSpec": {"name": "add_numbers", "description":
"Add two numbers together and return the sum.", "inputSchema": {"json": {"properties":
{"a": {"title": "A", "type": "integer"}, "b": {"title": "B", "type": "integer"}},
"required": ["a", "b"], "type": "object", "additionalProperties": false}}}},
{"toolSpec": {"name": "structured_output", "description": "Use this tool to
provide your final structured response. Call this tool when you have gathered
all necessary information and are ready to provide the final answer in the required
format.", "inputSchema": {"json": {"description": "Structured output for calculation
results.", "properties": {"operation": {"description": "The mathematical operation
performed", "title": "Operation", "type": "string"}, "result": {"description":
"The result of the calculation", "title": "Result", "type": "integer"}, "explanation":
{"description": "Brief explanation of the calculation", "title": "Explanation",
"type": "string"}}, "required": ["operation", "result", "explanation"], "title":
"CalculationResult", "type": "object", "additionalProperties": false}}}}]}}'
"required": ["a", "b"], "type": "object", "additionalProperties": false}}}}]}}'
headers:
Content-Length:
- '1784'
- '977'
Content-Type:
- !!binary |
YXBwbGljYXRpb24vanNvbg==
@@ -96,41 +78,50 @@ interactions:
- AUTHORIZATION-XXX
x-amz-date:
- X-AMZ-DATE-XXX
x-amz-security-token:
- X-AMZ-SECURITY-TOKEN-XXX
method: POST
uri: https://bedrock-runtime.us-east-1.amazonaws.com/model/anthropic.claude-3-sonnet-20240229-v1%3A0/converse
uri: https://bedrock-runtime.us-east-1.amazonaws.com/model/us.anthropic.claude-sonnet-4-6/converse
response:
body:
string: '{"metrics":{"latencyMs":7598},"output":{"message":{"content":[{"toolUse":{"input":{"operation":"Addition","result":42,"explanation":"I
added 15 and 27 using the add_numbers tool."},"name":"structured_output","toolUseId":"tooluse_RT8uSPaM37Q8CVuo3rJLtI"}}],"role":"assistant"}},"stopReason":"tool_use","usage":{"inputTokens":571,"outputTokens":102,"serverToolUsage":{},"totalTokens":673}}'
string: "{\"metrics\":{\"latencyMs\":1456},\"output\":{\"message\":{\"content\":[{\"text\":\"The
result of **15 + 27 = 42**! \U0001F9EE\\n\\nUsing the `add_numbers` tool,
I computed the sum of 15 and 27, and the result is **42**.\"}],\"role\":\"assistant\"}},\"stopReason\":\"end_turn\",\"usage\":{\"cacheReadInputTokenCount\":0,\"cacheReadInputTokens\":0,\"cacheWriteInputTokenCount\":0,\"cacheWriteInputTokens\":0,\"inputTokens\":729,\"outputTokens\":51,\"serverToolUsage\":{},\"totalTokens\":780}}"
headers:
Connection:
- keep-alive
Content-Length:
- '387'
- '443'
Content-Type:
- application/json
Date:
- Thu, 12 Feb 2026 22:01:12 GMT
- Fri, 24 Apr 2026 20:49:37 GMT
x-amzn-RequestId:
- X-AMZN-REQUESTID-XXX
status:
code: 200
message: OK
- request:
body: '{"messages": [{"role": "user", "content": [{"text": "\nCurrent Task: Calculate
15 + 27 using your add_numbers tool. Report the result."}]}, {"role": "assistant",
"content": [{"toolUse": {"toolUseId": "tooluse_pSseOamVELzpL3kQG5VukN", "name":
"add_numbers", "input": {"a": 15, "b": 27}}}]}, {"role": "user", "content":
[{"toolResult": {"toolUseId": "tooluse_pSseOamVELzpL3kQG5VukN", "content": [{"text":
"42"}]}}]}, {"role": "assistant", "content": [{"text": "{\"operation\":\"Addition\",\"result\":42,\"explanation\":\"I
added 15 and 27 using the add_numbers tool.\"}"}]}], "inferenceConfig": {"stopSequences":
["\nObservation:"]}, "system": [{"text": "You are Calculator. You are a calculator
assistant that uses tools to compute results.\nYour personal goal is: Perform
calculations using available tools"}], "toolConfig": {"tools": [{"toolSpec":
{"name": "add_numbers", "description": "Add two numbers together and return
the sum.", "inputSchema": {"json": {"properties": {"a": {"title": "A", "type":
"integer"}, "b": {"title": "B", "type": "integer"}}, "required": ["a", "b"],
"type": "object", "additionalProperties": false}}}}, {"toolSpec": {"name": "structured_output",
body: '{"messages": [{"role": "user", "content": [{"text": "The result of **15
+ 27 = 42**! \ud83e\uddee\n\nUsing the `add_numbers` tool, I computed the sum
of 15 and 27, and the result is **42**."}]}], "inferenceConfig": {"stopSequences":
["\nObservation:"]}, "system": [{"text": "Format your final answer according
to the following OpenAPI schema: {\n \"type\": \"json_schema\",\n \"json_schema\":
{\n \"name\": \"CalculationResult\",\n \"strict\": true,\n \"schema\":
{\n \"description\": \"Structured output for calculation results.\",\n \"properties\":
{\n \"operation\": {\n \"description\": \"The mathematical operation
performed\",\n \"title\": \"Operation\",\n \"type\": \"string\"\n },\n \"result\":
{\n \"description\": \"The result of the calculation\",\n \"title\":
\"Result\",\n \"type\": \"integer\"\n },\n \"explanation\":
{\n \"description\": \"Brief explanation of the calculation\",\n \"title\":
\"Explanation\",\n \"type\": \"string\"\n }\n },\n \"required\":
[\n \"operation\",\n \"result\",\n \"explanation\"\n ],\n \"title\":
\"CalculationResult\",\n \"type\": \"object\",\n \"additionalProperties\":
false\n }\n }\n}\n\nIMPORTANT: Preserve the original content exactly as-is.
Do NOT rewrite, paraphrase, or modify the meaning of the content. Only structure
it to match the schema format.\n\nDo not include the OpenAPI schema in the final
output. Ensure the final output does not include any code block markers like
```json or ```python."}], "toolConfig": {"tools": [{"toolSpec": {"name": "structured_output",
"description": "Use this tool to provide your final structured response. Call
this tool when you have gathered all necessary information and are ready to
provide the final answer in the required format.", "inputSchema": {"json": {"description":
@@ -140,10 +131,10 @@ interactions:
"type": "integer"}, "explanation": {"description": "Brief explanation of the
calculation", "title": "Explanation", "type": "string"}}, "required": ["operation",
"result", "explanation"], "title": "CalculationResult", "type": "object", "additionalProperties":
false}}}}]}}'
false}}}}], "toolChoice": {"tool": {"name": "structured_output"}}}}'
headers:
Content-Length:
- '1942'
- '2545'
Content-Type:
- !!binary |
YXBwbGljYXRpb24vanNvbg==
@@ -158,28 +149,27 @@ interactions:
- AUTHORIZATION-XXX
x-amz-date:
- X-AMZ-DATE-XXX
x-amz-security-token:
- X-AMZ-SECURITY-TOKEN-XXX
method: POST
uri: https://bedrock-runtime.us-east-1.amazonaws.com/model/anthropic.claude-3-sonnet-20240229-v1%3A0/converse
uri: https://bedrock-runtime.us-east-1.amazonaws.com/model/us.anthropic.claude-sonnet-4-6/converse
response:
body:
string: '{"message":"The model returned the following errors: Your API request
included an `assistant` message in the final position, which would pre-fill
the `assistant` response. When using tools, pre-filling the `assistant` response
is not supported."}'
string: "{\"metrics\":{\"latencyMs\":1729},\"output\":{\"message\":{\"content\":[{\"toolUse\":{\"input\":{\"operation\":\"add_numbers\",\"result\":42,\"explanation\":\"The
result of **15 + 27 = 42**! \U0001F9EE\\n\\nUsing the `add_numbers` tool,
I computed the sum of 15 and 27, and the result is **42**.\"},\"name\":\"structured_output\",\"toolUseId\":\"tooluse_FavcTtQCbzFrqURMiduSla\",\"type\":\"tool_use\"}}],\"role\":\"assistant\"}},\"stopReason\":\"tool_use\",\"usage\":{\"cacheReadInputTokenCount\":0,\"cacheReadInputTokens\":0,\"cacheWriteInputTokenCount\":0,\"cacheWriteInputTokens\":0,\"inputTokens\":1150,\"outputTokens\":116,\"serverToolUsage\":{},\"totalTokens\":1266}}"
headers:
Connection:
- keep-alive
Content-Length:
- '246'
- '603'
Content-Type:
- application/json
Date:
- Thu, 12 Feb 2026 22:01:12 GMT
x-amzn-ErrorType:
- ValidationException:http://internal.amazon.com/coral/com.amazon.bedrock/
- Fri, 24 Apr 2026 20:49:39 GMT
x-amzn-RequestId:
- X-AMZN-REQUESTID-XXX
status:
code: 400
message: Bad Request
code: 200
message: OK
version: 1
@@ -975,7 +975,7 @@ def test_anthropic_agent_kickoff_structured_output_with_tools():
role="Calculator",
goal="Perform calculations using available tools",
backstory="You are a calculator assistant that uses tools to compute results.",
llm=LLM(model="anthropic/claude-3-5-haiku-20241022"),
llm=LLM(model="anthropic/claude-sonnet-4-6"),
tools=[add_numbers],
verbose=True,
)
@@ -952,7 +952,7 @@ def test_bedrock_agent_kickoff_structured_output_with_tools():
role="Calculator",
goal="Perform calculations using available tools",
backstory="You are a calculator assistant that uses tools to compute results.",
llm=LLM(model="bedrock/anthropic.claude-3-sonnet-20240229-v1:0"),
llm=LLM(model="bedrock/us.anthropic.claude-sonnet-4-6"),
tools=[add_numbers],
verbose=True,
)
-185
View File
@@ -1,185 +0,0 @@
"""Tests for Flow-as-tool functionality."""
from __future__ import annotations
from unittest.mock import MagicMock
from crewai.flow.flow import Flow, start
from crewai.tools.flow_tool import FlowTool, create_flow_tools
# ---------------------------------------------------------------------------
# Test Flow classes
# ---------------------------------------------------------------------------
class SimpleFlow(Flow):
"""A simple flow that greets the user."""
@start()
def greet(self) -> str:
return "Hello from SimpleFlow!"
class MathFlow(Flow):
"""Performs basic math operations."""
@start()
def compute(self) -> str:
return "42"
class NoDocFlow(Flow):
@start()
def run_it(self) -> str:
return "no doc"
# ---------------------------------------------------------------------------
# FlowTool unit tests
# ---------------------------------------------------------------------------
class TestFlowTool:
def test_wrap_simple_flow(self) -> None:
tool = FlowTool(
name="simple_flow",
description="A simple flow that greets the user.",
flow_class=SimpleFlow,
)
assert tool.name == "simple_flow"
assert "greets the user" in tool.description
def test_run_invokes_kickoff(self) -> None:
mock_flow = MagicMock()
mock_flow.return_value = mock_flow # __init__ returns self
mock_flow.kickoff.return_value = "mocked result"
tool = FlowTool(
name="test_flow",
description="test",
flow_class=mock_flow,
)
result = tool._run(inputs="{}")
assert result == "mocked result"
mock_flow.kickoff.assert_called_once()
def test_run_with_json_inputs(self) -> None:
mock_flow = MagicMock()
mock_flow.return_value = mock_flow
mock_flow.kickoff.return_value = "result with inputs"
tool = FlowTool(
name="test_flow",
description="test",
flow_class=mock_flow,
)
result = tool._run(inputs='{"key": "value"}')
assert result == "result with inputs"
mock_flow.kickoff.assert_called_once_with(inputs={"key": "value"})
def test_run_with_invalid_json_defaults_to_empty(self) -> None:
mock_flow = MagicMock()
mock_flow.return_value = mock_flow
mock_flow.kickoff.return_value = "ok"
tool = FlowTool(
name="test_flow",
description="test",
flow_class=mock_flow,
)
result = tool._run(inputs="not valid json")
assert result == "ok"
mock_flow.kickoff.assert_called_once_with(inputs=None)
def test_run_returns_string(self) -> None:
mock_flow = MagicMock()
mock_flow.return_value = mock_flow
mock_flow.kickoff.return_value = 42
tool = FlowTool(
name="test_flow",
description="test",
flow_class=mock_flow,
)
result = tool._run()
assert result == "42"
assert isinstance(result, str)
# ---------------------------------------------------------------------------
# create_flow_tools tests
# ---------------------------------------------------------------------------
class TestCreateFlowTools:
def test_creates_tools_from_flow_classes(self) -> None:
tools = create_flow_tools([SimpleFlow, MathFlow])
assert len(tools) == 2
names = {t.name for t in tools}
assert "simple_flow" in names
assert "math_flow" in names
def test_description_from_docstring(self) -> None:
tools = create_flow_tools([SimpleFlow])
assert len(tools) == 1
assert "greets the user" in tools[0].description
def test_description_fallback_when_no_docstring(self) -> None:
tools = create_flow_tools([NoDocFlow])
assert len(tools) == 1
assert "NoDocFlow" in tools[0].description
def test_empty_list_returns_empty(self) -> None:
assert create_flow_tools([]) == []
def test_none_returns_empty(self) -> None:
assert create_flow_tools(None) == []
def test_tools_are_base_tool_instances(self) -> None:
from crewai.tools.base_tool import BaseTool
tools = create_flow_tools([SimpleFlow])
for tool in tools:
assert isinstance(tool, BaseTool)
# ---------------------------------------------------------------------------
# Agent integration tests
# ---------------------------------------------------------------------------
class TestAgentFlowIntegration:
def test_agent_with_flows_has_flow_tools(self) -> None:
from crewai.agent.core import Agent
agent = Agent(
role="Test Agent",
goal="Test flows",
backstory="I test things",
flows=[SimpleFlow, MathFlow],
)
tool_names = {t.name for t in (agent.tools or [])}
assert "simple_flow" in tool_names
assert "math_flow" in tool_names
def test_agent_without_flows_no_extra_tools(self) -> None:
from crewai.agent.core import Agent
agent = Agent(
role="Test Agent",
goal="Test",
backstory="I test things",
)
# Should not have any flow tools
flow_tool_names = {
t.name for t in (agent.tools or []) if isinstance(t, FlowTool)
}
assert len(flow_tool_names) == 0
def test_flow_tool_executes_real_flow(self) -> None:
"""Test that a FlowTool actually runs the Flow's kickoff."""
tools = create_flow_tools([SimpleFlow])
tool = tools[0]
result = tool.run(inputs="{}")
assert "Hello from SimpleFlow" in result
Generated
+1 -1
View File
@@ -13,7 +13,7 @@ resolution-markers = [
]
[options]
exclude-newer = "2026-04-23T07:00:00Z"
exclude-newer = "2026-04-22T16:00:00Z"
[manifest]
members = [