news 2026/7/10 9:16:06

CrewAI源码深度解析:LLM调度、Agent耦合与生产避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CrewAI源码深度解析:LLM调度、Agent耦合与生产避坑指南

1. 项目概述:这不是一个“框架教程”,而是一份源码级实战手记

CrewAI,最近半年在中文技术社区里出现的频率高得有点反常——不是因为它突然爆火,而是因为大量开发者在尝试接入、调试、二次开发时,被它表面简洁、底层复杂的结构卡住了。我从去年底开始跟进这个项目,从第一个pip install crewai跑通demo,到后来把整个crewai包解压进PyCharm逐行打日志,再到用pdbtask.py第237行断点看execute()如何调度agent,前后拆了三遍源码,重装了七次虚拟环境,才真正搞清楚它到底在“调度什么”、又在“隐藏什么”。这本《CrewAI(源码笔记)》不是教你怎么写Crew()Task()的API调用文档,而是记录我在源码层面对它的每一次“触碰”:为什么SequentialProcess默认不支持并行?为什么Agentllm属性在初始化后会被悄悄替换成CacheHandler包装对象?为什么Taskcontext参数在_execute_with_tool_usage里会触发两次get_tools调用?这些细节,官方文档不会写,GitHub Issues里散落着几十个相似提问却没人给出根因,而它们恰恰是线上部署失败、本地复现不一致、自定义Agent行为异常的真正源头。如果你正卡在“能跑demo但改不动逻辑”、“加了自定义Tool就报错”、“想换LLM但不知道该动哪几个文件”,或者你是个习惯性先看setup.py再动手的工程师——这份笔记就是为你写的。它不预设你熟悉LangChain或LlamaIndex,但要求你愿意打开终端、输入pip show crewai、然后cd进那个site-packages里的crewai目录。

2. 整体架构与设计思路拆解:三层抽象下的真实约束

2.1 源码组织逻辑:从__init__.py开始的“信任链”

拿到crewai源码,第一件事不是看crew.py,而是打开根目录下的__init__.py。这里藏着整个项目的“信任起点”:

# crewai/__init__.py from .crew import Crew from .agent import Agent from .task import Task from .process import Process from .llm import LLM

这五条导入语句,构成了CrewAI对外暴露的全部接口。但注意:LLM类在这里被导出,可它在源码中实际位于crewai/llm/__init__.py,而该文件里只有一行:

from .llm import LLM

真正的LLM类定义在crewai/llm/llm.py,但它本身不继承任何基类,也不实现invoke方法——它只是一个数据容器,只存model_nametemperatureapi_key等字段。那谁来真正调用大模型?答案在crewai/agent/agent.pyAgent._execute_task方法里:

# crewai/agent/agent.py 第189行 if self.llm: response = self.llm.invoke(prompt) else: response = self._default_llm_invoke(prompt)

这里的self.llm.invoke,实际调用的是crewai/llm/llm.pyLLM类的invoke方法。但翻遍llm.py,你会发现invoke方法是动态绑定的:

# crewai/llm/llm.py 第42行 def __init__(self, **kwargs): # ...省略参数赋值... self._bind_invoke_method() def _bind_invoke_method(self): if "openai" in self.model.lower(): self.invoke = self._openai_invoke elif "anthropic" in self.model.lower(): self.invoke = self._anthropic_invoke elif "groq" in self.model.lower(): self.invoke = self._groq_invoke else: self.invoke = self._default_invoke

这就是CrewAI的第一层设计哲学:用字符串匹配做LLM路由,而非抽象基类约束。好处是接入新模型快(只要改model字符串),坏处是类型安全为零——你传个model="my_custom_api",它会默默走_default_invoke,而这个方法内部直接抛NotImplementedError,错误堆栈里根本看不到你的model名。我第一次踩坑就是因为把model="qwen2.5"写成model="qwen",结果报错信息是'NoneType' object has no attribute 'choices',花了两小时才定位到_default_invokeresponseNone

提示:所有LLM适配逻辑都集中在_bind_invoke_method和对应私有方法中。想加通义千问支持?别动LLM类,直接在_bind_invoke_method里加elif "qwen" in self.model.lower(): self.invoke = self._qwen_invoke,然后在同文件下写_qwen_invoke方法即可。这是最安全的扩展点,不影响其他模块。

2.2 Agent与Task的耦合真相:不是“任务分发”,而是“上下文透传”

官方文档说“Agent执行Task”,但源码揭示:Task本身不持有执行逻辑,它只是Agent的输入参数容器。看crewai/task/task.py

# crewai/task/task.py class Task: def __init__(self, description: str, agent: Optional[Agent] = None, ...): self.description = description self.agent = agent # 注意:这里存的是Agent实例引用,不是拷贝 # ...其他字段

关键在self.agent = agent。这意味着当你创建一个Task并指定agent=my_agent时,Task对象内部直接持有了my_agent的内存地址。再看Crew._run_sequentially方法:

# crewai/crew.py 第215行 for task in self.tasks: result = task.agent.execute_task(task) # 直接调用Agent的方法!

所以真实执行流是:Crew → Task → Agent.execute_task(Task)。Task没有run()方法,只有Agentexecute_task()。这解释了为什么你无法单独运行一个Task——它缺少Agent的上下文(如tools、llm、memory)。更隐蔽的耦合在Task.context字段:它被设计为“前序Task的输出”,但在源码中,context只是个字符串或dictAgent.execute_task会把它原样塞进prompt模板:

# crewai/agent/agent.py 第268行 full_prompt = self.prompt_template.format( task_description=task.description, context=task.context or "", # 这里直接拼接,不做任何校验 ... )

问题来了:如果前序Task返回的是JSON字符串{"status": "success", "data": [...]},而当前Task的prompt期望context是纯文本摘要,就会导致大模型解析失败。我在线上遇到过一次生产事故,就是因为context里混入了未转义的双引号,导致整个prompt格式错乱。解决方案不是改prompt模板,而是在Task之间加一层context_processor——但这需要你修改Crew._run_sequentially循环,在每次task.agent.execute_task(task)后,对返回值做清洗再赋给下一个Task的context

2.3 Process机制的本质:状态机,而非工作流引擎

Process枚举类(crewai/process.py)只有四个值:SEQUENTIAL,HIERARCHICAL,CONSENSUS,ROUTING。但源码里,只有SEQUENTIALHIERARCHICAL有完整实现,另外两个是占位符。看Crew._get_process_method

# crewai/crew.py 第198行 def _get_process_method(self): if self.process == Process.SEQUENTIAL: return self._run_sequentially elif self.process == Process.HIERARCHICAL: return self._run_hierarchical else: raise NotImplementedError(f"Process {self.process} not implemented")

_run_hierarchical的实现更值得玩味:它把tasks按agent.role分组,然后让role层级高的agent(如Manager)去review role层级低的agent(如Researcher)的结果。但这个“层级”判断,完全依赖字符串比较

# crewai/crew.py 第342行 def _get_agent_hierarchy(self): # 按role字符串长度排序?不,是硬编码关键词! hierarchy = ["CEO", "CTO", "Manager", "Senior", "Lead", "Junior", "Intern"] return sorted(self.agents, key=lambda a: hierarchy.index(a.role) if a.role in hierarchy else -1)

也就是说,如果你的Agent.role是"Project Director",它会被排在最后(-1),永远当不了reviewer。这根本不是AI驱动的层级判断,而是开发者偷懒的字符串映射。我曾试图用llm动态评估agent能力来替代硬编码,结果发现_run_hierarchical里根本没有预留LLM调用点——所有决策都在内存里完成。所以CrewAI的Process,本质是一个基于预设规则的状态机,不是像Airflow那样的DAG工作流引擎。想实现真正的动态路由?你得重写_get_process_method,并接管整个执行循环。

3. 核心模块源码解析与实操要点

3.1Crew类:协调者,而非控制器

Crew类(crewai/crew.py)常被误解为“总控中心”,但源码显示它更像一个配置聚合器+执行触发器。它的核心字段只有三个:

class Crew: def __init__(self, agents: List[Agent], tasks: List[Task], process: Process = Process.SEQUENTIAL): self.agents = agents self.tasks = tasks self.process = process self._results = [] # 私有,只读

注意:self.agentsself.tasks是直接引用传入的列表,Crew不管理Agent生命周期,也不缓存Task状态。当你调用crew.kickoff(),它只做三件事:

  1. 验证agents/tasks非空(_validate_inputs
  2. 调用_get_process_method()获取执行函数
  3. 执行该函数,并将返回值存入self._results

这意味着:Agent的llmtoolsmemory等状态,完全由你创建时决定,Crew绝不干预。我见过太多人以为crew.add_agent(new_agent)能动态扩容,其实add_agent方法根本不存在——agents列表是只读的。想热更新Agent?必须重建Crew实例。

实操心得:线上服务中,我用functools.lru_cache缓存Crew实例,但缓存key必须包含agentstasks的哈希值。因为agents[0].llm.api_key变了,Crew实例却还是旧的,会导致密钥泄露风险。正确做法是:

from hashlib import md5 def get_crew_hash(agents, tasks): # 对每个agent的model+api_key+tools做hash agent_hashes = [md5(f"{a.llm.model}_{a.llm.api_key}_{len(a.tools)}".encode()).hexdigest() for a in agents] task_hashes = [md5(t.description.encode()).hexdigest() for t in tasks] return md5("".join(agent_hashes + task_hashes).encode()).hexdigest()

3.2Agent类:工具箱与记忆体的缝合怪

Agent类(crewai/agent/agent.py)是源码中最复杂的模块,因为它要同时处理三件不相干的事:LLM调用、Tool执行、Memory存储。看它的__init__

def __init__( self, role: str, goal: str, backstory: str, llm: Optional[LLM] = None, tools: Optional[List[BaseTool]] = None, memory: bool = False, verbose: bool = False, max_iter: int = 15, max_rpm: Optional[int] = None, step_callback: Optional[Callable] = None, ): self.role = role self.goal = goal self.backstory = backstory self.llm = llm or LLM() # 默认构造 self.tools = tools or [] self.memory = memory self.verbose = verbose self.max_iter = max_iter self.max_rpm = max_rpm self.step_callback = step_callback self._tool_usage = {} # 私有:记录tool调用次数 self._memory_backend = None # 私有:内存后端实例

关键点在于self._memory_backend。当memory=True时,Agent.__post_init__会初始化它:

def __post_init__(self): if self.memory: self._memory_backend = SQLiteSaver() # 硬编码!只能是SQLite

SQLiteSaver类在crewai/memory/sqlite_saver.py,它用sqlite3库存task_id,agent_role,input,output四字段。问题来了:它不支持分布式。如果你用Celery部署多个Worker,所有Worker会竞争同一个crewai_memory.db文件,轻则IO阻塞,重则数据库损坏。我线上用flock加文件锁解决,但更彻底的方案是替换_memory_backend——在Agent初始化后,手动赋值:

from crewai.memory.redis_saver import RedisSaver agent._memory_backend = RedisSaver(redis_url="redis://localhost:6379/1")

但注意:RedisSaver不在官方代码里,是我自己写的。CrewAI源码只提供了SQLite实现,这是它的第二个硬编码约束。

3.3Task类:Prompt工程的载体

Task类(crewai/task/task.py)表面简单,实则暗藏Prompt设计的全部逻辑。它的__init__接收descriptionexpected_outputagent等,但真正影响执行的是_prompt_template属性:

# crewai/task/task.py 第68行 @property def _prompt_template(self) -> str: return f"""{self.description} Your final answer must be the final output of the task, and nothing else. {self.expected_output or ""}"""

这个模板被Agent.execute_task调用时,会和Agent自己的prompt_template合并。而Agent的模板在agent.py第122行:

self.prompt_template = """You are {role}. {backstory} Your goal is to {goal} Here is the context you have: {context} Here is the task you need to execute: {task_description} {expected_output} {tools_description} {format_instructions} """

看到没?Taskdescriptionexpected_output,最终都成了Agent Prompt的一部分。这意味着:Task不是独立单元,它是Agent Prompt的参数化片段。所以当你想让Task输出JSON格式,不能只在expected_output里写“请输出JSON”,而必须确保Agent的format_instructions包含JSON Schema。我试过在Task里加format_instructions="{'type': 'object', 'properties': {...}}",但源码里Task类根本不存这个字段——它只存在于Agent的prompt模板里。正确做法是:在创建Agent时,把JSON Schema写进format_instructions参数:

agent = Agent( role="Data Analyst", goal="Extract structured data from text", backstory="You are expert in JSON schema validation", format_instructions='{"type": "object", "properties": {"name": {"type": "string"}, "age": {"type": "integer"}}, "required": ["name", "age"]}' )

这样,所有分配给它的Task,都会自动带上这个Schema约束。

3.4Tool机制:动态加载的“插件系统”

Tool是CrewAI扩展性的核心,但源码揭示其加载机制极其脆弱。所有Tool必须继承BaseToolcrewai/tools/base_tool.py),而BaseTool的关键方法是_run

class BaseTool: def _run(self, *args, **kwargs) -> str: raise NotImplementedError

Agent.execute_task调用Tool时,用的是tool.run(),而run()方法在BaseTool里是这样实现的:

def run(self, *args, **kwargs) -> str: try: result = self._run(*args, **kwargs) return str(result) except Exception as e: return f"Tool execution failed: {str(e)}"

注意:_run的返回值被强制转为字符串。这意味着:如果你的Tool返回一个dict,它会被str()变成"{'key': 'value'}",再喂给LLM。而LLM看到的是字符串,不是JSON对象,无法做结构化解析。我为此重构了三个Tool:把return {"data": [...]}改成return json.dumps({"data": [...]}),并在_run里加json.loads校验。

更致命的是Tool的注册方式。Agent.tools是一个列表,但Agent._get_tools_description()方法会遍历它,生成一段描述文本:

def _get_tools_description(self) -> str: if not self.tools: return "" descriptions = [] for tool in self.tools: desc = f"- {tool.name}: {tool.description}" if tool.args_schema: desc += f" Args: {tool.args_schema.schema_json()}" descriptions.append(desc) return "\n".join(descriptions)

这里tool.args_schema.schema_json()调用Pydantic的schema_json(),但如果Tool没定义args_schema(比如用@tool装饰器创建的简易Tool),就会抛AttributeError。我线上因此挂过三次,最后统一给所有Tool加了兜底:

@property def args_schema(self): try: return self._args_schema except AttributeError: # 返回空schema,避免crash from pydantic import BaseModel class EmptySchema(BaseModel): pass return EmptySchema

4. 实操过程与核心环节实现:从本地调试到生产部署

4.1 源码级调试:如何让pdb在execute_task里停住

想真正理解执行流,必须在关键路径打断点。但Crew.kickoff()是高层API,直接breakpoint()会停在无关位置。正确姿势是:

  1. crewai/agent/agent.py第265行(full_prompt = self.prompt_template.format(...))加breakpoint()
  2. 运行脚本前,设置环境变量:PYTHONBREAKPOINT=pdb
  3. 启动时会进入pdb,此时可用p full_prompt查看完整prompt

但更高效的是用pdb++(比原生pdb多pp命令):

pip install pdbpp export PYTHONBREAKPOINT=pdbpp.set_trace

在pdb里,你可以:

  • p self.llm.model查看当前LLM型号
  • p [t.name for t in self.tools]查看已加载Tool
  • u(up)跳到上层栈帧,看是谁调用了execute_task
  • d(down)跳到下层,进入llm.invoke

我常用pp命令打印复杂对象:

(Pdb++) pp self.tools[0].__dict__ {'name': 'SearchTool', 'description': 'A tool that searches the web', 'args_schema': <class 'crewai.tools.search_tool.SearchToolArgs'>, '_run': <bound method SearchTool._run of <crewai.tools.search_tool.SearchTool object at 0x...>>}

这比p self.tools[0]直观得多。

4.2 自定义LLM接入:以Ollama为例的完整补丁

官方只支持OpenAI/Anthropic/Groq,但很多人想用本地Ollama。源码里没有Ollama适配,但我们可以“打补丁”。步骤如下:

  1. 创建ollama_llm.py(与crewai/llm/同级):
# ollama_llm.py import requests import json from crewai.llm.llm import LLM class OllamaLLM(LLM): def __init__(self, model: str = "llama2", base_url: str = "http://localhost:11434"): super().__init__(model=model) self.base_url = base_url def _ollama_invoke(self, prompt: str) -> str: response = requests.post( f"{self.base_url}/api/generate", json={"model": self.model, "prompt": prompt, "stream": False} ) if response.status_code == 200: return response.json()["response"] else: raise Exception(f"Ollama error: {response.text}")
  1. 修改crewai/llm/__init__.py,添加:
from .ollama_llm import OllamaLLM
  1. 修改crewai/llm/llm.py_bind_invoke_method
def _bind_invoke_method(self): # ...原有代码... elif "ollama" in self.model.lower(): self.invoke = self._ollama_invoke # 注意:这里要动态导入,避免启动时报错 from .ollama_llm import OllamaLLM self._ollama_client = OllamaLLM(model=self.model) # ...后续代码...
  1. _ollama_invoke里调用self._ollama_client._ollama_invoke(prompt)

这样,只需LLM(model="ollama:llama2")就能用Ollama。我测试过llama2phi3qwen:7b,响应时间比OpenAI API快3倍,成本为零。

4.3 生产部署避坑:并发、超时与资源隔离

CrewAI默认不考虑并发,max_rpm参数形同虚设。源码里max_rpm只在_rpm_limit方法里被检查,但该方法从未被调用。真实限制靠time.sleep硬控:

# crewai/agent/agent.py 第312行(伪代码) if self.max_rpm: time.sleep(60 / self.max_rpm) # 错!这是每请求sleep,不是每分钟限流

这会导致QPS暴跌。正确做法是用ratelimit库:

from ratelimit import limits, sleep_and_retry class RateLimitedAgent(Agent): @sleep_and_retry @limits(calls=10, period=60) # 每分钟10次 def execute_task(self, task): return super().execute_task(task)

资源隔离更关键。Crew实例共享全局LLM连接,高并发下会耗尽连接池。解决方案是为每个Crew创建独立LLM实例:

# 不要这样(共享) shared_llm = LLM(model="gpt-4") # 要这样(隔离) def create_crew_for_request(): llm = LLM(model="gpt-4", api_key=request.api_key) # 每次新建 agent = Agent(llm=llm, ...) return Crew(agents=[agent], tasks=[...])

我线上用threading.local()做线程级LLM缓存,既避免重复创建,又保证隔离:

import threading _local = threading.local() def get_thread_local_llm(): if not hasattr(_local, 'llm'): _local.llm = LLM(model="gpt-4", api_key=get_current_user_key()) return _local.llm

4.4 日志与可观测性:如何追踪每个Token消耗

CrewAI不记录token用量,但llm.invoke返回的response对象里有usage字段(OpenAI兼容API)。要捕获它,需重写LLM._openai_invoke

# crewai/llm/llm.py def _openai_invoke(self, prompt: str) -> str: from openai import OpenAI client = OpenAI(api_key=self.api_key, base_url=self.base_url) response = client.chat.completions.create( model=self.model, messages=[{"role": "user", "content": prompt}], temperature=self.temperature, ) # 记录token用量到全局metrics if hasattr(response, 'usage'): log_token_usage( model=self.model, input_tokens=response.usage.prompt_tokens, output_tokens=response.usage.completion_tokens ) return response.choices[0].message.content

log_token_usage可以对接Prometheus,用Counter指标统计。我线上用prometheus_client暴露crewai_token_total{model="gpt-4", type="input"},运维能实时看到各模型消耗。

5. 常见问题与排查技巧实录:那些文档里找不到的答案

5.1 问题速查表:高频报错与根因定位

报错信息根因定位解决方案触发场景
'NoneType' object has no attribute 'choices'LLM.invoke返回None,通常因_default_invoke未实现检查llm.model字符串是否匹配任一_bind_invoke_method分支;或手动指定invoke方法自定义模型名拼写错误,如"qwen2"写成"qwen"
sqlite3.OperationalError: database is locked多进程/线程同时写crewai_memory.db改用RedisSaver;或在SQLiteSaversave()方法里加with sqlite3.connect(db_path, timeout=30)开启memory=True且部署在Gunicorn多worker模式
ValidationError: 1 validation error for SearchToolArgs queryTool的args_schema定义与LLM生成的JSON不匹配args_schemaquery字段加min_length=1约束;或在_run里加try/except兜底LLM返回空字符串""query参数
RecursionError: maximum recursion depth exceededTask的context包含自身输出,形成循环引用Crew._run_sequentially循环中,对contextjson.loads(json.dumps(context))深拷贝前序Task返回含__dict__的对象,未序列化
ModuleNotFoundError: No module named 'crewai.tools'pip install crewai安装的wheel包不含tools子模块卸载后用pip install git+https://github.com/joaomdmoura/crewai.git安装源码版使用@tool装饰器但未安装完整源码

5.2 独家避坑技巧:来自生产环境的血泪经验

技巧1:Prompt注入防御必须前置
CrewAI的prompt模板直接拼接task.description,如果用户输入含{},会破坏Jinja2格式。我在Task.__init__里加了校验:

def __init__(self, description: str, ...): # 防注入:移除description中的{ },防止模板注入 if "{" in description or "}" in description: description = description.replace("{", "{{").replace("}", "}}") self.description = description # ...其余代码

技巧2:Tool超时必须可控
requests.get默认无超时,一个挂掉的Tool会让整个Crew卡死。我在BaseTool.run()里加了统一超时:

import signal def run(self, *args, **kwargs) -> str: def timeout_handler(signum, frame): raise TimeoutError(f"Tool {self.name} timed out after 30s") signal.signal(signal.SIGALRM, timeout_handler) signal.alarm(30) # 30秒超时 try: result = self._run(*args, **kwargs) signal.alarm(0) # 取消alarm return str(result) except Exception as e: signal.alarm(0) return f"Tool execution failed: {str(e)}"

技巧3:Agent角色冲突的静默降级
当多个Agent有相同role(如都是"Researcher"),_get_agent_hierarchy会因hierarchy.index()ValueError。我在Crew._validate_inputs里加了去重:

def _validate_inputs(self): roles = [a.role for a in self.agents] if len(roles) != len(set(roles)): # 自动追加序号去重 seen = {} for i, role in enumerate(roles): if role in seen: self.agents[i].role = f"{role}_{seen[role]}" seen[role] += 1 else: seen[role] = 1

技巧4:Task输出截断的精准控制
LLM可能输出超长内容,Agent.execute_task会无脑返回全部。我在_execute_with_tool_usage末尾加了截断:

# crewai/agent/agent.py 第305行 if len(result) > 4000: # 限制4000字符 result = result[:3997] + "..." return result

5.3 性能调优实测数据:参数调整的真实影响

我用locust对CrewAI做了压力测试(单节点,16核32G),对比不同参数组合的QPS:

配置项QPS平均延迟(ms)CPU使用率关键观察
max_iter=15,verbose=False默认2.1420092%CPU瓶颈,LLM调用串行
max_iter=5,verbose=False降低迭代3.8230078%迭代减少50%,QPS提升80%
max_iter=5,verbose=True开启日志1.2680095%日志I/O拖慢3倍
max_iter=5,step_callback=print回调函数0.9820098%print是性能杀手
max_iter=5,step_callback=lambda x: None空回调3.7240077%回调开销可忽略

结论:max_iter是最大优化点,verbosestep_callback应仅在调试时开启。生产环境务必设verbose=False,且step_callback设为None或空函数。

6. 源码改造与二次开发:构建企业级AI工作流平台

6.1 插件化架构设计:从toolsplugins

CrewAI的tools是扁平列表,但企业需求是“按业务域分组”。我设计了Plugin抽象:

class Plugin: def __init__(self, name: str, description: str, tools: List[BaseTool]): self.name = name self.description = description self.tools = tools def register_to_agent(self, agent: Agent): agent.tools.extend(self.tools) # 注入plugin专属prompt片段 agent.prompt_template += f"\n\nAvailable {self.name} tools:\n{self._get_plugin_tools_desc()}"

这样,一个FinancePlugin可封装StockPriceToolCurrencyConvertToolMarketingPlugin封装SocialMediaPostTool。Agent创建时只需:

agent = Agent(...) finance_plugin = FinancePlugin() marketing_plugin = MarketingPlugin() finance_plugin.register_to_agent(agent) marketing_plugin.register_to_agent(agent)

register_to_agent会自动把tools注入,并扩展prompt模板。这比手动维护tools列表清晰十倍。

6.2 可视化执行追踪:自动生成Mermaid流程图

虽然禁止在输出中用Mermaid,但源码里可以生成。我在Crew.kickoff()末尾加了:

def kickoff(self): start_time = time.time() result = self._get_process_method()() end_time = time.time() # 生成执行流程图 graph = ["graph TD"] for i, task in enumerate(self.tasks): graph.append(f" T{i}['Task {i+1}: {task.description[:20]}...']") if i > 0: graph.append(f" T{i-1} --> T{i}") graph.append(f" style T0 fill:#4CAF50,stroke:#388E3C") graph.append(f" classDef success fill:#4CAF50,stroke:#388E3C;") with open(f"crew_trace_{int(start_time)}.mmd", "w") as f: f.write("\n".join(graph)) return result

每次运行生成.mmd文件,用mermaid-cli转成PNG,嵌入监控看板。运维能一眼看出哪个Task耗时最长。

6.3 安全加固:输入输出的双向过滤

企业场景必须防越狱。我在Agent.execute_task入口加了输入过滤:

def execute_task(self, task: Task) -> str: # 输入过滤:移除潜在越狱指令 safe_desc = re.sub(r"(?i)ignore.*previous.*instruction|system.*role|you.*are.*not.*an.*ai", "[FILTERED]", task.description) safe_desc = re.sub(r"(?i)act.*as.*root|exec.*shell.*command", "[BLOCKED]", safe_desc) # 输出过滤:扫描敏感词 result = self._execute_task(task) if any(word in result.lower() for word in ["password", "api_key", "secret"]): result = "[SECURITY BLOCKED: SENSITIVE DATA DETECTED
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/10 9:14:45

Unity 2022新手入门:从零搭建3D滚球收集游戏全流程指南

1. 项目概述与核心价值如果你一直对游戏开发充满好奇&#xff0c;看着那些炫酷的3D世界却不知从何下手&#xff0c;那么这篇文章就是为你准备的。我将带你从零开始&#xff0c;手把手完成Unity2022的安装&#xff0c;并最终制作出你的第一个可交互的3D小游戏。整个过程&#xf…

作者头像 李华
网站建设 2026/7/10 9:14:00

双节锂电池电压平衡系统设计与MP2672A应用

1. 项目背景与核心需求 在便携式电子设备和储能系统中&#xff0c;多节串联锂电池组的电压平衡问题一直是设计难点。当电池组中各单体电池存在容量、内阻等参数差异时&#xff0c;充放电过程中会出现电压不一致现象&#xff0c;这不仅影响电池组整体性能&#xff0c;更会加速电…

作者头像 李华
网站建设 2026/7/10 9:11:28

遇南三老板是谁?庭院火锅选购避坑指南|内行推荐不踩雷

一、行业痛点&#xff1a;新手吃庭院火锅&#xff0c;90%踩这5类坑近两年庭院火锅赛道爆火&#xff0c;依托自然场景、松弛氛围&#xff0c;成为聚餐、宴请首选&#xff0c;红餐大数据显示&#xff0c;全国庭院类火锅门店年增速超22%。但市面同质化品牌泛滥&#xff0c;普通消费…

作者头像 李华
网站建设 2026/7/10 9:10:37

论文AI率30%降不下来?这几招直接把论文AI率干到5%以下

看到这个标题点进来的朋友&#xff0c;大概率正在为论文的AI率发愁吧&#xff01;我自己上个月刚经历过这样的处境&#xff0c;提交论文前用学校指定的检测平台跑了一遍&#xff0c;结果显示AI疑似度50多%。当时心里确实有些着急&#xff0c;毕竟现在很多学校对论文的AI率都有硬…

作者头像 李华
网站建设 2026/7/10 9:10:07

OpenCV 4.x 巴特沃斯滤波器实战:图像去噪与边缘保留的 3 种参数对比

OpenCV 4.x 巴特沃斯滤波器实战&#xff1a;图像去噪与边缘保留的3种参数对比 在计算机视觉领域&#xff0c;频域滤波是实现图像增强和特征提取的核心技术之一。巴特沃斯滤波器以其独特的"最大平坦"特性&#xff0c;成为平衡去噪效果与边缘保留的理想选择。本文将深入…

作者头像 李华