Check CrewAI API
Load this before writing or reviewing crewai code: it maps what you probably remember to what crewai 1.15.x actually accepts.
Verified against crewai 1.15.22 and 1.15.23 on 2026-10-01.
Live-tested with real LLMs on 2026-10-01.
Run
first; if the major/minor differs from 1.15, re-verify version-sensitive rows with the
skill before trusting them.
1. Three rules before you write anything
-
Unknown keyword arguments are silently ignored. ,
and
accept and drop parameters that do not exist.
Crew(memory_config={...})
and
Task(response_format=Model)
construct fine and do nothing. A stale parameter name never raises, so check it exists:
python
from crewai import Agent, Crew, Task
assert "response_model" in Task.model_fields # real field
assert "response_format" not in Task.model_fields # silently dropped if passed
assert "memory_config" not in Crew.model_fields # silently dropped if passed
print(sorted(Agent.model_fields)[:5])
-
The installed source wins over any doc, tutorial, or memory. That includes the
that
writes into new projects. When a row below matters, check the installed signature (see
verify-installed-api.md).
-
Assert on structured output at the boundary. Several mistakes below fail quietly with
while the run reports success.
2. Imports
| You probably wrote | Current 1.15.x form | What the wrong form does |
|---|
from crewai_tools import BaseTool, tool
| from crewai.tools import BaseTool, tool
| ImportError: cannot import name 'BaseTool' from 'crewai_tools'
|
from crewai.flow.flow import Flow, start, listen, persist
| from crewai.flow import Flow, start, listen, router, or_, and_, persist
| ImportError: cannot import name 'persist'
|
from crewai.task import ConditionalTask
| from crewai.tasks.conditional_task import ConditionalTask
| |
from crewai.memory import ShortTermMemory, LongTermMemory, EntityMemory
| from crewai import Memory
| (the classes are gone) |
from crewai.utilities.events import crewai_event_bus
| from crewai.events import BaseEventListener, crewai_event_bus
| ModuleNotFoundError: No module named 'crewai.utilities.events'
|
from crewai_tools import CodeInterpreterTool
| No replacement in crewai - use a sandbox tool or service | (removed) |
The current top-level and project imports:
python
from crewai import Agent, BaseLLM, Crew, CrewOutput, Flow, LLM, Memory, Process, Task, TaskOutput
from crewai.project import CrewBase, after_kickoff, agent, before_kickoff, crew, llm, task, tool
from crewai.tools import BaseTool, tool
from crewai.flow import Flow, and_, listen, or_, persist, router, start
from crewai.knowledge.source.string_knowledge_source import StringKnowledgeSource
from crewai.mcp import MCPServerHTTP, MCPServerSSE, MCPServerStdio
from crewai.events import BaseEventListener, crewai_event_bus
3. Agent, Task, Crew parameters and defaults
| Parameter | Current value | Note |
|---|
| required | alone raises ValidationError ... expected_output Field required
|
| | Do not assume 15 or 20 |
| -> resolved at construction | From env , then , then , else |
| | Only permits caching; it does not turn it on |
| | Tool-result caching is opt-in: set |
| | |
| | Only and exist |
Task.guardrail_max_retries
| | is deprecated (DeprecationWarning) |
| You probably wrote | Current form | What the wrong form does |
|---|
| or | |
Crew(process=Process.hierarchical, ...)
with no manager | add manager_llm="openai/gpt-4.1-mini"
or | ValidationError: Attribute manager_llm or manager_agent is required
|
Crew(memory=True, memory_config={...})
| | is silently ignored |
Agent(allow_code_execution=True)
/ code_execution_mode="unsafe"
| Remove; use a sandbox tool | Deprecated no-ops; allow_code_execution=True
emits a |
Crew(function_calling_llm=...)
| Remove, or set per agent | Deprecated, |
| Two tasks with the same | One agent per concurrent async task | RuntimeError: Executor is already running. Cannot invoke the same executor instance concurrently.
|
python
from crewai import Agent, Crew, Process, Task
researcher = Agent(
role="Researcher",
goal="Find facts about {topic}",
backstory="A careful researcher.",
llm="openai/gpt-4.1-mini",
)
research = Task(
description="List three facts about {topic}.",
expected_output="Three bullet points.",
agent=researcher,
)
crew = Crew(agents=[researcher], tasks=[research], process=Process.sequential, cache=True)
out = crew.kickoff(inputs={"topic": "honey bees"})
print(out.raw)
4. Running a crew and reading the result
| You probably wrote | Current form | What the wrong form does |
|---|
crew.kickoff("honey bees")
| crew.kickoff(inputs={"topic": "honey bees"})
| TypeError: inputs must be a dict or Mapping, got str
|
await crew.kickoff_async(...)
for native async | | Works, but runs the sync in a thread () |
| / / | | AttributeError: 'CrewOutput' object has no attribute ...
|
then crew.kickoff_for_each(...)
on the same | Build a fresh for (or call it before any ) | No error, but every run reuses the first kickoff's inputs: the per-run copies keep the already-interpolated task text |
Agent(mcps=["https://..."])
in a crew run with | mcps=[MCPServerHTTP(url="https://...")]
(from ) | No error, but the agent gets zero MCP tools: string refs resolve with , which fails inside a running event loop (see connect-tools-and-mcp) |
fields:
,
,
,
(a list of
),
.
reads from
/
.
kickoff_for_each(inputs=[...])
returns a list of
.
is summed over each agent's
object for that object's whole lifetime, not per run. One
instance shared by two agents counts every call twice, and one reused across crews carries the earlier crews' tokens (verified with
anthropic/claude-haiku-4-5
: a second one-call crew on the same
reported 148 tokens and 2 requests, a fresh
73 and 1). For per-run cost, give each crew its own
objects, or count
s (
).
python
import asyncio
from crewai import Agent, Crew, Task
def make_crew() -> Crew:
writer = Agent(role="Writer", goal="Write", backstory="Writes short text.", llm="openai/gpt-4.1-mini")
task = Task(description="One line on {topic}.", expected_output="One line.", agent=writer)
return Crew(agents=[writer], tasks=[task])
out = make_crew().kickoff(inputs={"topic": "bees"})
print(out.raw, out.token_usage.total_tokens, [t.raw for t in out.tasks_output])
many = make_crew().kickoff_for_each(inputs=[{"topic": "bees"}, {"topic": "ants"}])
print([o.raw for o in many])
async def main():
return await make_crew().akickoff(inputs={"topic": "wasps"})
print(asyncio.run(main()).raw)
5. LLMs, model strings, and provider extras
LiteLLM is no longer a dependency.
is a factory that returns a native provider class; only the OpenAI SDK ships with core crewai.
| You probably wrote | Current form | What the wrong form does |
|---|
llm="anthropic/claude-sonnet-4-5"
with plain installed | uv add "crewai[anthropic]"
, then the same string | ImportError: Anthropic native provider not available, to install: uv add "crewai[anthropic]"
|
llm="gemini/gemini-2.5-flash"
with plain | uv add "crewai[google-genai]"
| ImportError: Google Gen AI native provider not available
|
llm="groq/llama-3.3-70b-versatile"
(or , any non-native provider) | | ImportError: Unable to initialize LLM with model '...'. The model did not match any supported native provider (...), and the LiteLLM fallback package is not installed.
|
| No and assuming | Default is ; set or | |
llm.call(messages, response_format=Model)
| llm.call(messages, response_model=Model)
| TypeError: ... unexpected keyword argument 'response_format'. Did you mean 'response_model'?
|
Custom BaseLLM.call(self, messages, tools=None, callbacks=None, available_functions=None)
| Also accept from_task=None, from_agent=None, response_model=None
(or ) | TypeError: call() got an unexpected keyword argument 'from_task'
at kickoff |
Native providers: openai, anthropic/claude, azure, google/gemini, bedrock/aws, openrouter, deepseek, ollama, hosted_vllm, cerebras, dashscope, snowflake. Extras:
,
,
,
crewai[azure-ai-inference]
,
. Construction never checks the key; kickoff does (
ValueError: OPENAI_API_KEY is required
, or
ANTHROPIC_API_KEY is required
; a wrong key gets the provider's
AuthenticationError ... 401
). The
scaffolds never add a provider extra, even with
: run
uv add "crewai[anthropic]"
yourself.
python
from crewai import LLM
gpt = LLM(model="openai/gpt-4.1-mini", temperature=0.2) # core install
local = LLM(model="ollama/llama3") # core install, OpenAI-compatible route
claude = LLM(model="anthropic/claude-sonnet-4-5") # needs: uv add "crewai[anthropic]"
print(type(gpt).__name__, type(local).__name__, type(claude).__name__)
A custom LLM subclasses
and implements
with the full signature. If
forwards
to a provider SDK, send only each message's
and
: crewai adds a
key to some messages, and the Anthropic API rejects it with
400 ... messages.0.cache_breakpoint: Extra inputs are not permitted
.
python
from typing import Any
from crewai import Agent, BaseLLM, Crew, Task
class EchoLLM(BaseLLM):
def call(self, messages, tools=None, callbacks=None, available_functions=None,
from_task=None, from_agent=None, response_model=None, **kwargs: Any) -> str:
return "echo: " + str(messages[-1]["content"])[:40]
agent = Agent(role="Echo", goal="Echo", backstory="Echoes.", llm=EchoLLM(model="echo"))
task = Task(description="Say hi", expected_output="Anything", agent=agent)
print(Crew(agents=[agent], tasks=[task]).kickoff().raw)
6. Structured output
| Where | Use | Result lands in |
|---|
Task(output_pydantic=Model)
| Parsed model on the task and crew output | , |
| Dict output | |
Task(response_model=Model)
| Native provider structured output only | (JSON text); stays |
Agent.kickoff(messages, response_format=Model)
| Standalone agent | |
llm.call(messages, response_model=Model)
| Direct LLM call | the parsed model |
LLM(model=..., response_format=Model)
| Constructor-level default | |
| You probably wrote | Current form | What the wrong form does |
|---|
Task(response_format=Model)
| Task(output_pydantic=Model)
| Silently ignored |
Agent.kickoff(msg, response_model=Model)
| Agent.kickoff(msg, response_format=Model)
| TypeError: ... Did you mean 'response_format'?
|
Task(output_pydantic=M, output_json=M)
| Pick one | ValidationError: Only one output type can be set
|
Task(output_pydantic=M, output_file="report.md")
expecting prose in the file | Drop , or render the model yourself | The file gets the model's JSON, whatever the extension |
output_file="/abs/path/report.md"
| A relative path (or output_file="{out_dir}/report.md"
via inputs) | The leading is stripped, so the file lands under the current directory |
| in | Set in the method in Python | With no matching class: . With one: silently dropped, |
python
from pydantic import BaseModel
from crewai import Agent, Crew, Task
class Report(BaseModel):
title: str
points: list[str]
analyst = Agent(role="Analyst", goal="Summarise", backstory="Concise.", llm="openai/gpt-4.1-mini")
task = Task(description="Summarise {topic}.", expected_output="A title and points.",
agent=analyst, output_pydantic=Report)
out = Crew(agents=[analyst], tasks=[task]).kickoff(inputs={"topic": "bees"})
assert out.pydantic is not None, "structured output was dropped"
print(out.pydantic.title, out["points"])
standalone = analyst.kickoff("Summarise bees.", response_format=Report)
print(standalone.pydantic)
In a
project keep the model in Python even when the rest of the task is YAML:
python
@task
def research_task(self) -> Task:
return Task(config=self.tasks_config["research_task"], output_pydantic=Report)
7. Guardrails
A function guardrail takes one argument (
) and returns a tuple
. On
the value is fed back to the agent as the error and the task retries, up to
(default 3); then the task raises
Task failed guardrail validation after N retries
. A string guardrail is checked by an extra LLM call.
| You probably wrote | Current form | What the wrong form does |
|---|
def g(out) -> bool: return True
| def g(out: TaskOutput) -> tuple[bool, Any]
returning | Annotated: ValidationError: If return type is annotated, it must be Tuple[bool, Any]
at . Unannotated: TypeError: cannot unpack non-iterable bool object
at kickoff |
| (or , ) | Same at |
| Task(guardrail_max_retries=2)
| |
| Relying on | Write a function guardrail | In open-source crewai it is a no-op that always passes |
python
from typing import Any
from crewai import Agent, Crew, Task, TaskOutput
def under_50_words(output: TaskOutput) -> tuple[bool, Any]:
if len(output.raw.split()) > 50:
return (False, "Keep it under 50 words.")
return (True, output.raw)
writer = Agent(role="Writer", goal="Write", backstory="Brief.", llm="openai/gpt-4.1-mini")
task = Task(description="Describe {topic}.", expected_output="A short paragraph.", agent=writer,
guardrails=[under_50_words, "Must not mention prices"], guardrail_max_retries=2)
print(Crew(agents=[writer], tasks=[task]).kickoff(inputs={"topic": "bees"}).raw)
runs each guardrail in order; a successful string result replaces
for the next one.
8. Memory and knowledge
Memory is one
class (LanceDB storage). Its defaults call OpenAI:
for embeddings and
for analysis.
analyses with the first agent's
instead, and knowledge with no
embeds with
. Live with an OpenAI key: a
crew stored a codename in one process and a new crew recalled it in the next; crew and agent knowledge both answered from the source.
| You probably wrote | Current form | What the wrong form does |
|---|
| , , | | |
| with no (or an invalid) | Set the key, or pass Memory(llm=..., embedder=...)
| Crew completes; every save fails silently (only a is emitted) and nothing is stored |
| with no key | Same | RuntimeError: Memory requires an embedder for vector search
(an invalid key: AuthenticationError ... 401
) |
Agent(knowledge_sources=[...])
with no key or embedder, agent in a crew | Pass on the Agent or Crew | ValueError: Invalid Knowledge Configuration: The OPENAI_API_KEY environment variable is not set.
at crew kickoff (an invalid key: AuthenticationError ... 401
) |
| on an agent with | Run the agent as a task in a , or put the facts in the prompt | No error with or without a key: never queries agent knowledge, so the answer ignores it |
Crew(knowledge_sources=[...])
with no key or embedder | Same | Logs Failed to upsert documents
; crew runs with no knowledge |
| Changing the knowledge after a run stored knowledge | crewai reset-memories -kn
(or crew.reset_memories(command_type="knowledge")
), then run | Embedding function conflict: new: <x> vs persisted: openai
; the crew runs with no knowledge |
Adding embedder={"provider": "openai"}
after knowledge was stored with no (or changing the OpenAI embedding model) | Same reset | Failed to upsert documents: Collection expecting embedding with dimension of 1536, got 3072
(the explicit provider defaults to ); the crew answers without its knowledge |
The embedder config shape is
{"provider": <name>, "config": {...}}
; the OpenAI model key is
.
raises
TypeError: ... unexpected keyword argument 'model'
in
, and for knowledge it is silently ignored (the crew embeds with
). The
provider below also needs
and a running Ollama server with the model pulled; without the package, crew knowledge is silently
.
is a local, key-free embedder that core crewai can already run (chromadb's
; about 80 MB of model downloaded on first use) - the design-agent skill's memory-and-knowledge reference has working examples. For a deterministic embedder in tests, use a callable (
) or a custom class (knowledge) - see
verify-installed-api.md.
python
from crewai import Agent, Crew, Memory, Task
from crewai.knowledge.source.string_knowledge_source import StringKnowledgeSource
embedder = {"provider": "ollama", "config": {"model_name": "nomic-embed-text"}} # needs: uv add ollama
memory = Memory(llm="openai/gpt-4.1-mini", embedder=embedder)
facts = StringKnowledgeSource(content="acme was founded in 2020.")
agent = Agent(role="Analyst", goal="Answer", backstory="Uses knowledge.", llm="openai/gpt-4.1-mini")
task = Task(description="When was acme founded?", expected_output="A year.", agent=agent)
crew = Crew(agents=[agent], tasks=[task], memory=memory, knowledge_sources=[facts], embedder=embedder)
if crew.knowledge is None:
print("knowledge failed to initialise - is the ollama package installed and the server running?")
Reset stored data from the project directory with
(memory; errors with
Memory memory system is not initialized
if the crew has no memory),
(knowledge),
(agent knowledge),
(latest kickoff outputs),
(all). The old
,
,
flags are hidden deprecated aliases that print a warning and reset the unified memory. Storage path and offline embedders:
verify-installed-api.md.
9. Project scaffold and CLI
| You probably wrote | Current form | What the wrong form does |
|---|
crewai create crew my_crew
expecting + YAML | crewai create crew my_crew --classic
| Starts an interactive wizard that writes a JSON project (, ); with no TTY it prints . Scripts that only need a crew should use --classic --skip-provider
. |
| on a JSON crew in CI or a script | For CI, create the crew with crewai create crew <name> --classic
and run it with | Opens a full-screen run view that waits for the user to quit, even with no TTY, so the command never exits. An environment variable, , skipped the run view on crewai 1.15.x, but it is unsupported and undocumented; may change |
| Leaving from the JSON scaffold with no | Set the key, set , or configure an embedder | The crew completes; memory recall and saves fail silently |
crewai run --inputs '{...}'
on a classic crew | Put inputs in | Error: --inputs is only supported for declarative flows and crews
|
crewai reset-memories -s -l -e
| | Deprecated aliases, warning printed |
| from | Do not return the output | The script calls and exits 1 after a successful run; prints An error occurred while running the crew: ... non-zero exit status 1
|
| Trusting 's exit code in CI on a classic crew () or a Python flow | Run (exit code propagates) or check the output | There exits 0 even when the crew or flow process failed; JSON (wizard) crews have no script, so create CI crews with |
bash
crewai version
crewai create crew research_crew --classic --skip-provider
cd research_crew && uv add "crewai[anthropic]" # only if an agent uses an anthropic/ model
crewai install && crewai run
crewai reset-memories --help
The classic layout:
(
class),
src/<name>/config/agents.yaml
and
(keys match the
/
method names),
, and
with
[tool.crewai] type = "crew"
and dependency
. In YAML,
resolves to an
method of that name, otherwise it is used as a model string.
also writes a
with a placeholder
- replace it, never commit it.
10. Removed and deprecated
| Feature | Status in 1.15.22-1.15.23 | Do instead |
|---|
| Removed from | Sandbox tool or service |
Agent(allow_code_execution=...)
, | Deprecated no-ops | Same |
| , , , | Removed / ignored | |
| Removed | |
Crew(function_calling_llm=...)
| Deprecated | Per-agent |
| Deprecated | |
| module | Deprecation shim | (the CLI is its own package) |
| LiteLLM as a core dependency | Optional extra | only for non-native providers |
11. Common mistakes
| Symptom | Cause | Fix |
|---|
| A parameter has no effect and no error | Unknown kwargs are dropped by Agent/Task/Crew | Check "name" in Task.model_fields
|
| set in YAML, or used instead of | Set in Python; assert non-None |
ImportError: ... native provider not available
| Provider SDK extra missing | uv add "crewai[anthropic]"
(or , , ) |
ValueError: OPENAI_API_KEY is required
at kickoff | No set, so the default was used | Set or , plus the provider key |
| Crew finishes but nothing was remembered | with no embedder key | Pass or set the key; listen for from |
| Knowledge never appears in prompts | Crew-level knowledge failed to embed | Set ; look for Failed to upsert documents
|
TypeError: ... unexpected keyword argument 'from_task'
| Custom with the old signature | Accept , , , |
| ValidationError at | Guardrail annotated or | Annotate and return a tuple |
| hangs or prints | It is an interactive JSON wizard now | Add for the layout; use it for CI and scripts |
| never returns on a JSON crew | The run view waits for a keypress | For CI, use crewai create crew <name> --classic
plus |
| results all answer the first topic | The was already kicked off | Build a fresh per batch |
| larger than the run could have used | An object shared by several agents or reused across crews | One per agent per crew, or count s |
| Agent never uses its MCP tools under | string in inside a running event loop | |
Embedding function conflict ... persisted: openai
| Knowledge was stored with another embedder | crewai reset-memories -kn
|
Collection expecting embedding with dimension of 1536, got 3072
| Knowledge was stored with another OpenAI embedding model | crewai reset-memories -kn
|
| reports after the crew succeeds | returns the | Do not return it |
12. Checklist
References
- Verify the installed API - one-liners to check fields, defaults and signatures in the installed crewai; storage paths; an offline embedder for memory and knowledge
- Source receipts - the crewai source file and line behind each row, so you can re-check after an upgrade
- Public docs: https://docs.crewai.com
For related skills:
- ask-docs - query the live CrewAI docs when a row may have changed
- getting-started - project scaffolding and choosing crews vs flows
- design-agent / design-task - tuning agents and tasks once the API is right
- build-flow - Flow state, routers, , human feedback
- connect-tools-and-mcp - , ,
- test-crewai-project - offline stub LLM and deterministic tests
- deploy-to-amp / call-deployed-crew - shipping and calling a crew