算法艺术创作
algorithmic-art
通过代码和可调参数创作生成艺术与交互式视觉效果。
design-agent【设计与创意】CrewAI agent design and configuration. Use when creating, configuring, or debugging crewAI agents — choosing role/goal/backstory, selecting LLMs, assigning tools, tuning max_iter/max_rpm/max_execution_time, enabling planning/delegation, setting up knowledge sources, using guardrails, or configuring agents in YAML vs code.
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
How to design effective agents with the right role, goal, backstory, tools, and configuration.
Verified against crewai 1.15.23 on 2026-10-01. Live-tested with real LLMs on 2026-10-01.
Exact API forms (imports, defaults, provider extras, structured output) are in the check-crewai-api skill, and tools and MCP are in connect-tools-and-mcp. Where this skill and those disagree, follow them.
Spend 80% of your effort on task design, 20% on agent design. A well-designed task elevates even a simple agent. But even the best agent cannot rescue a vague, poorly scoped task. Get the task right first (see the design-task skill), then refine the agent.
Default to ONE agent. Add more only when the task genuinely splits into work that requires:
DO NOT add an agent just because the workflow has multiple steps. A single agent can call multiple tools in sequence within one kickoff (search → scrape → summarize is one agent's loop), produce structured multi-section output in one response, and iterate via its own tool-use loop.
Cost calculus: every extra agent = at least one more LLM kickoff plus a context handoff. Splitting linear, single-persona work into multiple agents multiplies token cost and adds fragility for marginal quality wins.
❌ Three agents for what is one researcher's job:
source_finder = Agent(role="Finds URLs via Firecrawl search", tools=[firecrawl_search])
scraper = Agent(role="Scrapes URLs via Firecrawl scrape", tools=[firecrawl_scrape])
writer = Agent(role="Writes the report") # plus goal, backstory, llm
✅ One researcher does the gathering loop; one writer synthesizes — two agents because the personas and LLMs genuinely differ:
researcher = Agent(role="Web Researcher", tools=[firecrawl_search, firecrawl_scrape], llm="anthropic/claude-haiku-4-5")
writer = Agent(role="Technical Report Writer", llm="anthropic/claude-sonnet-4-6")
❌ A Summarizer agent plus a Slack Sender agent (apps=["slack"]) to summarize a string and DM it.
✅ One Slack Reporter agent with apps=["slack"] and a task: "Write a 2-3 sentence executive summary at the top, then DM {recipient_email} the summary followed by the full body."
Heuristic: if two "agents" share the same persona, the same tool surface, and the same LLM, they are one agent with a longer task description.
Use Agent.kickoff() directly inside a Flow method - no Crew, no Task ceremony. The Flow owns sequencing and state; each step is a single agent kickoff (Flow mechanics: build-flow skill; docs: https://docs.crewai.com/en/concepts/agents#direct-agent-interaction-with-kickoff).
@listen(previous_step)
def my_step(self):
agent = Agent(role="…", goal="…", backstory="…", tools=[...], llm="anthropic/claude-haiku-4-5")
result = agent.kickoff(
f"Use this prior step's output: {self.state.prior_field}",
response_format=MyPydanticModel, # optional
)
self.state.my_field = result.pydantic # or result.raw
Reach for Crew.kickoff() only when a step genuinely benefits from multi-agent collaboration (delegation, hierarchical management, parallel specialists feeding one synthesis), or when the agent needs something that only works inside a crew task: knowledge sources and max_execution_time (Section 2).
Every agent needs three things: who it is, what it wants, and why it's qualified. {placeholders} in all three are filled from crew.kickoff(inputs={...}).
The role defines the agent's area of expertise. Be specific, not generic.
| Bad | Good |
|---|---|
Researcher | Senior Data Researcher specializing in {topic} |
Writer | Technical Blog Writer for developer audiences |
Analyst | Financial Risk Analyst with regulatory compliance expertise |
The role directly shapes how the LLM reasons. A "Senior Data Researcher" will produce different output than a "Research Assistant" even with the same task.
The goal is the agent's individual objective. It should be outcome-focused with quality standards.
| Bad | Good |
|---|---|
Do research | Uncover cutting-edge developments in {topic} and identify the top 5 trends with supporting evidence |
Write content | Produce publication-ready technical articles that explain complex topics clearly for non-technical readers |
Analyze data | Deliver actionable risk assessments with confidence levels and recommended mitigations |
The backstory establishes expertise, experience, values, and working style. It's the agent's "personality prompt."
backstory: >
You're a seasoned researcher with 15 years of experience in AI/ML.
You're known for your ability to find obscure but relevant papers
and synthesize complex findings into clear, actionable insights.
You always cite your sources and flag uncertainty explicitly.
Include: years/depth of experience, specific domain knowledge, working style and values ("always cites sources", "prefers concise output"), and the quality standards the agent holds itself to.
Leave out: implementation details (tools, models, config), task-specific instructions (those go in the task description), and personality traits that don't affect output quality.
Agent silently ignores keyword arguments it does not know, so a misspelled or removed parameter does nothing and raises nothing. Check "name" in Agent.model_fields when in doubt.
Agent(
role="...", goal="...", backstory="...", # Required
llm="anthropic/claude-haiku-4-5", # See LLM Selection
tools=[...], # Tool instances
max_iter=25, # Max reasoning iterations per task (default: 25)
max_execution_time=300, # Seconds, crew tasks only (default: None - no limit)
max_rpm=10, # Max LLM calls per minute for this agent (default: None)
max_retry_limit=2, # Retries when task execution errors (default: 2)
allow_delegation=False, # Default: False — agent works alone
respect_context_window=True, # Summarize and continue on overflow (default: True)
inject_date=False, # Put today's date in the prompt (default: False; format via date_format)
verbose=False, # Detailed execution logs (default: False)
)
| Limit | Behaviour |
|---|---|
max_iter | Reaching it does not raise. The agent is told to give its final answer now, and the run "succeeds" with whatever it has. With max_iter=1, a four-step tool procedure recorded one step and returned an incomplete answer. |
max_rpm | Calls over the limit block until the next minute window. With max_rpm=2, the third LLM call started at 62 s instead of about 2 s. |
max_execution_time | Applies only when the agent runs a Task inside a Crew; Agent.kickoff() ignores it (a 5 s limit with a 15 s tool completed normally after 17 s). In a crew it does not stop work at the deadline: the running tool and LLM calls finish, then the task raises TimeoutError: Task '...' execution timed out after 5 seconds (raised at 18.8 s), and the finished work is discarded. |
So max_execution_time turns a slow run into an error but does not stop a hang. To bound wall-clock time, set timeouts where the time is spent: LLM(model=..., timeout=60) and timeouts inside your tools, or run the kickoff in a subprocess you can kill.
Tuning max_iter: the default 25 is generous - most tasks finish in 3-8 iterations. Lower it to 10-15 to fail faster when tasks are well-defined. If an agent keeps hitting it, the task is too vague (fix the task, not the limit). Hitting it does not raise, so check outputs, not exit codes.
Other switches: respect_context_window=True summarizes the conversation and continues when the provider reports that the context length was exceeded; with False the run stops with SystemExit: Context length exceeded .... inject_date=True is worth it for time-sensitive tasks (research, news, scheduling): asked for today's date, an agent with it answered correctly, and one without it answered UNKNOWN.
Task(tools=[...]) replaces them for that task. In a live test, an agent with a weather tool given a task with only a population tool could call only the population tool.SerperDevTool() needs SERPER_API_KEY). Custom tools, the crewai_tools names that really exist, caching and MCP servers: connect-tools-and-mcp.from crewai import LLM
Agent(..., llm="anthropic/claude-haiku-4-5")
Agent(..., llm=LLM(model="anthropic/claude-haiku-4-5", temperature=0.2, timeout=60))
llm=, the agent uses env MODEL, then MODEL_NAME, then OPENAI_MODEL_NAME, else gpt-4.1-mini. That is an OpenAI model, so kickoff fails without OPENAI_API_KEY.anthropic/... needs uv add "crewai[anthropic]", gemini/... needs crewai[google-genai], and non-native providers need crewai[litellm] (full table in check-crewai-api).function_calling_llm (a separate model for tool calls) is deprecated on both Agent and Crew - set each agent's llm instead.Set allow_delegation=True only when the agent is part of a crew with other specialized agents, the task genuinely benefits from handing off subtasks, or you're using hierarchical process where the manager delegates. Warning: delegation without clear task boundaries leads to infinite loops or wasted iterations.
With a PlanningConfig, the agent first generates a plan (a list of steps), executes each step in its own multi-turn loop (capped by max_step_iterations), checks each result, then continues, replans, or finishes early.
from crewai import Agent
from crewai.agent.planning_config import PlanningConfig
agent = Agent(
role="…", goal="…", backstory="…", tools=[...],
planning_config=PlanningConfig(reasoning_effort="medium"), # most common
)
To disable planning, omit planning_config. planning=True alone is shorthand for PlanningConfig(reasoning_effort="low", max_attempts=1). reasoning=True is deprecated (DeprecationWarning) - use planning_config.
reasoning_effort | After each step the planner... | Pick when |
|---|---|---|
"low" | checks the step with a heuristic (no extra LLM call) and continues. No replan, no refine. | You want plan visibility (todos, observations) but trust the agent to follow it linearly. Fastest. |
"medium" (default) | observes the step with an LLM call; replans on failure only. | The agent's tools can fail (network, exec, scrape) and you want graceful recovery. The right default for sandbox-coding, research, and other tool-heavy loops. |
"high" | observes, then can finish early, replan fully, or refine the plan after every step. | The task changes shape based on intermediate findings, or you need maximum adaptiveness. Most LLM calls per run. |
PlanningConfig(
reasoning_effort="medium",
observe_steps=None, # None = LLM observation for medium/high, heuristic for low
max_steps=20, # cap on planned steps
max_replans=3, # full re-plans before finalizing
max_attempts=None, # refinement attempts during plan generation
max_step_iterations=15, # LLM turns per step
step_timeout=None, # wall-clock seconds per step
system_prompt=None, plan_prompt=None, refine_prompt=None, # custom prompts
llm=None, # separate (cheaper) LLM for planning; else agent.llm
)
When to enable: for autonomous loops where the agent picks its own steps and you want failure recovery (a coding agent that writes → runs → patches; a research agent that searches → scrapes → revises). Skip it for single-tool, single-purpose calls ("summarize this string", "post this Slack DM").
Cost: In one measured run, planning added 3-7 calls to a two-step task and wrapped the answer in prose. Measure on your own tasks before turning it on.
allow_code_execution and code_execution_mode are deprecated no-ops (allow_code_execution=True emits a DeprecationWarning), and CodeInterpreterTool no longer exists in crewai_tools. Give the agent a sandbox tool such as E2BPythonTool or DaytonaPythonTool instead (see connect-tools-and-mcp).
from typing import Any
def require_uppercase(result) -> tuple[bool, Any]:
if result.raw != result.raw.upper():
return (False, "Rewrite the whole answer in UPPERCASE letters only.")
return (True, result.raw)
agent = Agent(..., guardrail=require_uppercase, guardrail_max_retries=3) # default retries: 3
result = agent.kickoff("...") # the agent guardrail runs here
A guardrail can also be a string, which is checked by an extra LLM call. Agent guardrails have two limits on 1.15.23, both verified with a real LLM:
Agent.kickoff(). When the same agent executes a Task in a Crew, the agent guardrail is never called.kickoff raises ValueError: Agent's guardrail failed validation after N retries. Last error: ....When output must be fixed and not just rejected, put the guardrail on the Task (Task(guardrail=..., guardrail_max_retries=...)). The error message is fed back there, and the same uppercase check passed on the second attempt. See design-task and check-crewai-api.
from crewai.knowledge.source.text_file_knowledge_source import TextFileKnowledgeSource
Agent(
...,
knowledge_sources=[TextFileKnowledgeSource(file_paths=["company_handbook.txt"])], # read from ./knowledge/
embedder={"provider": "onnx"}, # local, no API key; omit it to use OpenAI embeddings
)
Knowledge sources give agents domain-specific data via RAG. Use them when agents need to reference large documents, policies, or datasets. Two things to know:
Crew. Agent.kickoff() silently ignores knowledge_sources. In a live test, the same agent answered "UNKNOWN" from kickoff() and correctly from a one-task crew.embedder=, knowledge uses OpenAI embeddings. With no OPENAI_API_KEY, agent knowledge raises ValueError: Invalid Knowledge Configuration at crew kickoff. Crew-level knowledge only logs Failed to upsert documents and runs without it.Embedders that work without OpenAI, KnowledgeConfig, memory and storage paths: references/memory-and-knowledge.md.
Define agents in agents.yaml for clean separation of config and code. Create a YAML project with crewai create crew <name> --classic. Without --classic, crewai create crew starts an interactive wizard that writes a JSON project instead.
researcher:
role: >
{topic} Senior Data Researcher
goal: >
Uncover cutting-edge developments in {topic}
with supporting evidence and source citations
backstory: >
You're a seasoned researcher with 15 years of experience.
Known for finding obscure but relevant sources and
synthesizing complex findings into clear insights.
You always cite your sources and flag uncertainty.
# Optional overrides: llm: anthropic/claude-haiku-4-5, max_iter: 15, max_rpm: 10,
# allow_delegation: false, verbose: true
Then wire in crew.py:
@CrewBase
class MyCrew:
agents_config = "config/agents.yaml"
tasks_config = "config/tasks.yaml"
@agent
def researcher(self) -> Agent:
return Agent(config=self.agents_config["researcher"], tools=[SerperDevTool()])
Critical: The method name (def researcher) must match the YAML key (researcher:). Mismatch causes KeyError (verified: KeyError: 'researcher').
Verified live: llm and max_iter set in YAML are applied, {topic} in the role is filled at kickoff, and tools attach in Python. Keep tools, guardrail functions and Pydantic models in Python; YAML holds the text and scalar settings.
Use Agent.kickoff() when you need one agent with tools and reasoning, without crew overhead. This is the most common pattern in Flows. It does not use the agent's knowledge sources or max_execution_time (Section 2).
from pydantic import BaseModel
from crewai import Agent
researcher = Agent(
role="Senior Research Analyst",
goal="Find comprehensive, factual information with source citations",
backstory="Expert researcher known for thorough, evidence-based analysis.",
tools=[...],
llm="anthropic/claude-haiku-4-5",
)
result = researcher.kickoff("What are the latest developments in quantum computing?")
print(result.raw) # str — the agent's full response
print(result.usage_metrics) # dict: total_tokens, prompt_tokens, completion_tokens, ...
class ResearchFindings(BaseModel):
key_trends: list[str]
sources: list[str]
confidence: float
result = researcher.kickoff("Research the latest AI agent frameworks", response_format=ResearchFindings)
print(result.pydantic.key_trends, result.pydantic.confidence)
result = await researcher.kickoff_async("...", response_format=ResearchFindings) # async variant
Note:
Agent.kickoff()returnsLiteAgentOutput- access structured output viaresult.pydantic. This differs fromllm.call(messages, response_model=Model), which returns the Pydantic object directly.Agent.kickoff(..., response_model=...)is aTypeError.
File inputs need an extra: uv add "crewai[file-processing]", then from crewai_files import FileInput and researcher.kickoff("Summarize this document", input_files={"document": FileInput(path="report.pdf")}). Without the extra, the import fails with ModuleNotFoundError: No module named 'crewai_files'.
Agent.kickoff() vs Crew.kickoff(): use Agent.kickoff() when each step is a distinct agent and a Flow controls sequencing (the Section 0 shape - verified live as a two-step Flow). Use Crew.kickoff() when multiple agents collaborate on related tasks within a single step, or the agent needs knowledge sources or max_execution_time.
In conversational Flows (from crewai.flow import ConversationState), the Flow owns the chat lifecycle and route selection. Call agents inside route handlers for bounded tool-backed work: research, docs lookup, account actions, triage, drafting, or escalation prep. The mechanics (handle_turn, ConversationConfig, a tested example) are in the build-flow skill's conversational-flows reference.
Design implications:
Flow responsible for session id, message history, routing, trace finalization, and approvals.self.append_agent_result(name, result, visibility="private") for scratch work that should not enter canonical chat history.self.append_assistant_message(reply)) so the next turn has the assistant context.Apply this section after you've decided you genuinely need multiple agents (Section 0). With one agent, the only question is how to design that agent.
When you do need multiple agents, prefer specialists. An agent that does one thing well outperforms one that does many things acceptably. Use a specialist when the task needs deep domain knowledge, quality matters more than speed, or the task is complex enough to benefit from focused expertise. A generalist is acceptable for simple tasks with clear instructions, for prototyping you'll specialize later, and for tasks that truly span several domains equally.
Instead of one "Content Writer" agent, create technical_writer (technical accuracy, code examples), copywriter (persuasive, audience-focused copy) and editor (grammar, consistency, style guide). Each has a narrow role, specific goal, and a backstory that reinforces that expertise.
Sequential (default): Researcher → Writer → Editor. Agents work one after another, and each receives prior outputs as context. Best for linear pipelines where each step builds on the last.
Hierarchical: a manager agent delegates and validates; task assignment is dynamic. Best for complex workflows where assignment depends on intermediate results.
Crew(
agents=[researcher, writer, editor],
tasks=[research_task, writing_task, editing_task],
process=Process.hierarchical,
manager_llm="anthropic/claude-haiku-4-5", # or manager_agent=...; one is required
)
Without manager_llm or manager_agent, Crew(...) raises a ValidationError.
Agent-to-agent delegation: with allow_delegation=True, an agent gets two tools, Delegate work to coworker and Ask question to coworker, that name the other crew members. Verified live: asked to get a sentence from the Writer, a lead agent called ask_question_to_coworker and returned the Writer's answer.
| Mistake | Impact | Fix |
|---|---|---|
| Generic role like "Assistant" | Agent produces unfocused, shallow output | Use specific expertise: "Senior Financial Analyst" |
| No tools for data-gathering tasks | Agent hallucinates data instead of searching | Always add tools when the task requires external info |
| Too many tools (10+) | Agent gets confused choosing between tools | Limit to 3-5 relevant tools per agent |
| Backstory full of task instructions | Agent mixes personality with task execution | Keep backstory about WHO the agent is; task details go in the task |
allow_delegation=True by default | Agents waste iterations delegating trivially | Only enable when delegation genuinely helps |
| max_iter too high for simple tasks | Agent loops unnecessarily on vague tasks | Lower max_iter; fix the task description instead |
| No guardrail on critical output | Bad output passes through unchecked | Add a Task guardrail for outputs that feed into production systems |
| Agent guardrail on an agent used in a Crew | Guardrail never runs | Put the guardrail on the Task |
Trusting max_execution_time to stop a hang | It raises only after the slow call returns, and Agent.kickoff() ignores it | Add LLM and tool timeouts |
Knowledge sources on an agent run via Agent.kickoff() | Knowledge silently unused | Run it as a crew task, or put the facts in the prompt |
No llm= and no OpenAI key | Kickoff fails: the default is OpenAI gpt-4.1-mini | Set llm= (or env MODEL) and install the provider extra |
| Planning on a simple task | 2-3x the LLM calls, sometimes a worse answer | Enable planning only for open-ended tool loops |
Before deploying an agent, verify:
timeout, tool timeouts); max_execution_time is only a backstop for crew tasksembedder, and the agent runs inside a crewFor deeper dives into specific topics, see:
@tool decorator and BaseTool subclassFor related skills:
crewai_tools names, caching, MCP servers