實戰(zhàn):從設(shè)計到踩坑全記錄)
最近在項目里折騰CrewAI多智能體開發(fā)最讓我上頭的不是Agent怎么編排而是“自定義工具”這塊。團(tuán)隊的需求很直白讓AI自動查庫存、核訂單、跟進(jìn)物流狀態(tài)。聽起來簡單可CrewAI自帶的那幾個工具根本碰不到企業(yè)內(nèi)部接口最后還是得老老實實寫自己的工具。這篇就把我在CrewAI里從零創(chuàng)建自定義工具的設(shè)計思路、代碼實現(xiàn)、踩坑記錄一起整理出來。適合剛把Agent跑通、卻發(fā)現(xiàn)內(nèi)置工具不夠用的同學(xué)也適合準(zhǔn)備在業(yè)務(wù)場景里擴(kuò)展智能體能力的開發(fā)者。不需要懂框架源碼只要會點Python跟著走一遍就能自己寫出第一支工具。1. 為什么要在CrewAI里寫自定義工具1.1 智能體與工具的“手腳關(guān)系”CrewAI的核心模型很簡單Agent是大腦負(fù)責(zé)理解任務(wù)、拆解計劃、判斷下一步做什么但大腦不會真的去調(diào)外部系統(tǒng)真正動手的是Tool。模型本身不具備“查詢數(shù)據(jù)庫”“調(diào)用訂單接口”“讀本地文件”這些能力它能做的只是“決定調(diào)用哪個工具、傳什么參數(shù)、怎么解讀返回結(jié)果”。如果沒有自定義工具Agent的能力邊界就非常有限。它只能靠訓(xùn)練時學(xué)到的知識和內(nèi)置工具提供的實時信息來回答問題一旦遇到私有系統(tǒng)、內(nèi)部API、特定業(yè)務(wù)規(guī)則就會開始編答案。所以自定義工具實際上是在給智能體“長手腳”每加一個工具就相當(dāng)于給Agent增加一種可以信賴的實操能力。在CrewAI里一個工具本質(zhì)上是一個可以被模型調(diào)用的函數(shù)包裝包含名稱、描述、參數(shù)定義和執(zhí)行邏輯。模型會從工具描述里判斷“這個工具是干什么的”“什么情況下使用它”。所以工具寫得好不好直接決定智能體能不能正確完成任務(wù)。1.2 內(nèi)置工具解決不了什么問題CrewAI插裝包提供了一些常用工具比如網(wǎng)頁搜索、文件讀取、網(wǎng)站內(nèi)容抓取、RAG檢索等。它們勝在通用開箱即用但問題也很明顯它們只面向“公開、通用、無業(yè)務(wù)規(guī)則”的場景。拿我手里的供應(yīng)鏈項目來說我需要查詢內(nèi)部訂單系統(tǒng)的訂單狀態(tài)這個接口有內(nèi)網(wǎng)訪問限制需要帶token認(rèn)證返回的是我們自定義的JSON結(jié)構(gòu)。內(nèi)置的網(wǎng)頁抓取工具根本不認(rèn)識這個接口也沒法處理認(rèn)證邏輯。更重要的是很多業(yè)務(wù)操作不只是“讀”還包括“寫”——比如審批、提交工單、標(biāo)記異常。內(nèi)置工具不會也不敢封裝這些有業(yè)務(wù)邏輯和權(quán)限控制的操作。所以自定義工具的核心價值在于封裝內(nèi)部系統(tǒng)的訪問邏輯把認(rèn)證、請求、解析細(xì)節(jié)收進(jìn)函數(shù)里把領(lǐng)域規(guī)則和校驗邏輯放到可執(zhí)行代碼中模型不需要自己“推理”這些規(guī)則控制返回給模型的內(nèi)容格式避免無關(guān)信息擠占上下文對寫操作做權(quán)限校驗和審計讓智能體的行為可控。1.3 自定義工具應(yīng)覆蓋的現(xiàn)實場景從實際項目看最值得自定義成工具的場景通常有幾類。第一類是內(nèi)部數(shù)據(jù)查詢。比如查庫存、查訂單、查客戶信息、查工單進(jìn)度。這類接口一般都在內(nèi)網(wǎng)而且數(shù)據(jù)結(jié)構(gòu)是公司內(nèi)部定義的模型沒法憑空猜到只能通過工具去拿。第二類是業(yè)務(wù)計算和規(guī)則判斷。比如計算運費、判斷是否滿足發(fā)貨條件、校驗訂單地址格式。這些規(guī)則用代碼寫清楚比讓模型“看著辦”靠譜得多。第三類是寫操作。比如創(chuàng)建工單、提交審批、發(fā)送消息。這類操作必須控制在工具層不能允許模型即興發(fā)揮否則容易產(chǎn)生不可控的副作用。第四類是外部系統(tǒng)的集成。比如調(diào)用天氣接口、查詢物流軌跡、獲取匯率等只要是有固定API的服務(wù)都可以包成工具。一句話凡是模型不能憑常識完成的、需要實時數(shù)據(jù)或業(yè)務(wù)口徑支撐的動作都應(yīng)該考慮做成自定義工具。2. 創(chuàng)建前的設(shè)計決定工具好用不好用2.1 工具本質(zhì)是給模型看的“API文檔”很多人第一次寫自定義工具時注意力全放在“功能怎么實現(xiàn)”上結(jié)果功能寫對了模型就是不會調(diào)用。問題往往出在描述上。一個工具對模型來說就是一份“API文檔”工具名叫什么、它是干什么的、參數(shù)是什么含義、返回值長什么樣。模型通過這份文檔來決定是否調(diào)用。如果文檔寫得含糊模型要么不敢用要么亂用。我習(xí)慣把工具描述當(dāng)成“給一個認(rèn)真但不太了解業(yè)務(wù)的新人寫的操作說明”。要告訴他什么時候該用這個工具什么時候不該用參數(shù)應(yīng)該填什么格式返回結(jié)果里哪些信息是有用的。比如“order_status_query”的描述我通常會寫成當(dāng)用戶詢問訂單狀態(tài)、物流節(jié)點、簽收情況時使用。參數(shù)order_id是訂單號格式如SO-2025-0001。工具會返回訂單當(dāng)前狀態(tài)和物流節(jié)點如果訂單不存在返回NOT_FOUND。這樣模型一看就知道用戶問“我的單到哪了”時應(yīng)該拿order_id調(diào)用這個工具。2.2 粒度怎么控制自定義工具最怕兩個極端一是功能太粗一個工具里又查庫存又改價格又發(fā)消息模型用起來完全失控二是功能太細(xì)查一個訂單要分“查基本信息”“查物流信息”“查商品明細(xì)”三個工具模型容易選錯任務(wù)流程也變得冗長。我的經(jīng)驗是按“業(yè)務(wù)動作的最小完整單元”來切分。也就是說一個工具應(yīng)該完整回答一類問題而不是做一些零碎的操作。例如“查詢訂單詳情”是一個完整動作它應(yīng)該返回模型回答“訂單現(xiàn)在什么狀態(tài)、預(yù)計什么時候送達(dá)”所需的核心信息?!案掠唵蔚刂贰笔橇硪粋€完整動作它負(fù)責(zé)校驗新地址、調(diào)用更新接口、返回更新結(jié)果。粒度控制也不需要一開始就追求完美。我一般是先根據(jù)真實業(yè)務(wù)問題列一個工具清單然后拿幾個典型問題走一遍流程發(fā)現(xiàn)模型頻繁組合調(diào)用多個工具再考慮是不是要合并發(fā)現(xiàn)某個工具容易被誤用再考慮是不是要拆分。2.3 描述與參數(shù)Schema的拿捏工具設(shè)計里最容易翻車的兩個點一是description寫得不夠“觸發(fā)”二是args_schema定義得不夠清楚。description的寫法有個小技巧把觸發(fā)條件明確寫出來。不要只寫“查庫存”要寫“當(dāng)用戶詢問某個SKU在當(dāng)前倉庫是否有貨、可用庫存數(shù)量是多少時使用本工具”。觸發(fā)條件越具體模型調(diào)用準(zhǔn)確率越高??梢栽诿枋隼锛訄鼍笆纠热纭袄缬脩魡枴甋KU-10086還有多少貨’就適合調(diào)用本工具”。參數(shù)Schema要盡量用Field把每個字段的含義講清楚必要時給示例值。比如from pydantic import BaseModel, Field from typing import Type class StockInput(BaseModel): sku_id: str Field(..., description商品SKU編碼例如SKU-10086) warehouse: str Field(default, description倉庫編碼缺省為default倉)這樣模型在生成參數(shù)時可以根據(jù)描述填出正確的sku_id而不是隨便傳個“蘋果手機(jī)”之類的模糊值。如果字段是必填的用...表示如果不是必填的給出默認(rèn)值。類型也要卡緊別用object或dict否則模型不知道該傳什么結(jié)構(gòu)。2.4 錯誤處理與返回值設(shè)計工具返回值會被拼到模型上下文里。模型會基于這段內(nèi)容組織回答。所以返回值設(shè)計有一個核心原則返回“模型可以直接引用”的結(jié)論而不是返回一團(tuán)原始數(shù)據(jù)。比如查詢訂單接口返回了一大段JSON里面有創(chuàng)建時間、修改時間、內(nèi)部備注、嵌套的商品列表、物流軌跡數(shù)組。如果直接把這段JSON扔給模型模型也能解析但會浪費大量token而且容易被無關(guān)字段干擾。更聰明的做法是在工具內(nèi)部提取關(guān)鍵信息整理成“訂單SO-2025-0001當(dāng)前狀態(tài)為已發(fā)貨物流公司順豐當(dāng)前節(jié)點為運輸中預(yù)計明天18點前送達(dá)”這樣的文本。錯誤處理同樣重要。工具執(zhí)行時如果拋異常輕則讓本次調(diào)用失敗重則讓整個Crew任務(wù)中斷。我通常會在工具內(nèi)部捕獲所有異常把它轉(zhuǎn)換成人類可讀的錯誤消息。模型看到“訂單接口請求超時請稍后重試”后會自然地轉(zhuǎn)述給用戶而不是輸出一堆堆棧信息。3. 實操用BaseTool從零寫一個自定義工具3.1 環(huán)境準(zhǔn)備與項目目錄先準(zhǔn)備好環(huán)境建議用獨立的虛擬目錄。mkdir crew-tools-demo cd crew-tools-demo python -m venv .venv source .venv/bin/activate pip install crewai crewai-tools安裝完成后創(chuàng)建一個簡單的項目結(jié)構(gòu)crew-tools-demo/ ├── main.py ├── .env └── tools/ ├── __init__.py ├── holiday_tool.py └── order_tool.py把工具放在獨立文件夾里主要是為了復(fù)用和測試。一個工具文件只負(fù)責(zé)一個領(lǐng)域主流程文件只負(fù)責(zé)Agent和Crew的編排這樣后面維護(hù)起來非常清爽。工具內(nèi)部需要調(diào)用外部API時把地址和密鑰放在.env里用環(huán)境變量讀取不要硬編碼在代碼中。3.2 第一支工具節(jié)假日計算器先寫一個簡單的工具用來熟悉BaseTool的基本結(jié)構(gòu)。# tools/holiday_tool.py from datetime import datetime from pydantic import BaseModel, Field from crewai.tools import BaseTool from typing import Type class HolidayInput(BaseModel): year: int Field(..., description年份例如2025) country: str Field(CN, description國家代碼CN代表中國US代表美國) class HolidayTool(BaseTool): name: str holiday_calculator description: str ( 當(dāng)用戶詢問某個年份、某個國家的法定節(jié)假日數(shù)量 或最近的一個節(jié)假日日期時使用本工具。 ) args_schema: Type[BaseModel] HolidayInput def _run(self, year: int, country: str CN) - str: # 這里只是演示數(shù)據(jù)生產(chǎn)環(huán)境請?zhí)鎿Q為真實節(jié)假日API holiday_map { CN: [2025-01-01, 2025-01-28, 2025-04-04], US: [2025-01-01, 2025-01-20], } holidays holiday_map.get(country, []) if not holidays: return f沒有找到{country}的節(jié)假日數(shù)據(jù)請確認(rèn)國家代碼。 return ( f{year}年{country}共返回{len(holidays)}個節(jié)假日 f最近的一個是{holidays[0]}。 )這段代碼的核心結(jié)構(gòu)是定義輸入?yún)?shù)模型HolidayInput繼承BaseTool設(shè)置name和description在_run方法里實現(xiàn)業(yè)務(wù)邏輯。注意_run方法的參數(shù)名和類型必須和args_schema里的字段對應(yīng)。模型會按照schema生成參數(shù)然后框架把這些參數(shù)傳給_run。3.3 第二支工具查詢內(nèi)部訂單系統(tǒng)節(jié)假日工具只是熱身真正派得上用場的是連接內(nèi)部系統(tǒng)的工具。這里用requests調(diào)一個訂單API并做好錯誤處理。# tools/order_tool.py import os import requests from pydantic import BaseModel, Field from crewai.tools import BaseTool from typing import Type class OrderQueryInput(BaseModel): order_id: str Field(..., description訂單號例如SO-2025-0001) include_items: bool Field(False, description是否返回商品明細(xì)數(shù)量) class OrderQueryTool(BaseTool): name: str order_status_query description: str ( 當(dāng)用戶詢問訂單狀態(tài)、物流節(jié)點、簽收情況時使用本工具。 參數(shù)order_id為訂單號格式如SO-2025-0001。 如果訂單不存在返回NOT_FOUND。 ) args_schema: Type[BaseModel] OrderQueryInput def _run(self, order_id: str, include_items: bool False) - str: api_base os.getenv(ORDER_API_BASE, http://localhost:8000) try: resp requests.get( f{api_base}/api/orders/{order_id}, params{include_items: include_items}, timeout5, ) resp.raise_for_status() data resp.json() except requests.exceptions.Timeout: return 訂單接口請求超時請稍后重試。 except requests.exceptions.HTTPError as e: return f訂單接口返回錯誤{e}。 except Exception as e: return f訂單查詢失敗{e}。 if resp.status_code 404: return NOT_FOUND items_text if include_items and data.get(items): items_text f商品明細(xì)共{len(data[items])}件 return ( f訂單{order_id}狀態(tài)為{data.get(status)} f物流公司{data.get(logistics)} f當(dāng)前節(jié)點{data.get(node)}{items_text}。 )這個工具做了幾件重要的事設(shè)置超時時間避免接口卡死捕獲異常并返回可讀信息整理返回結(jié)果只保留模型回答問題所需的信息。如果include_items為True也只返回商品數(shù)量不會把明細(xì)節(jié)全部塞進(jìn)上下文。這樣模型獲得的是干凈、可直接引用的答案。3.4 注冊到Agent并跑通Crew工具寫好后在main.py里把它掛到Agent上。# main.py from crewai import Agent, Task, Crew, Process from tools.order_tool import OrderQueryTool from tools.holiday_tool import HolidayTool order_tool OrderQueryTool() holiday_tool HolidayTool() support_agent Agent( role訂單客服專員, goal準(zhǔn)確回答用戶關(guān)于訂單和假期的詢問, backstory你是一名細(xì)心的客服只使用工具提供的事實回答不編造信息。, tools[order_tool, holiday_tool], verboseTrue, ) query_task Task( description用戶剛剛問SO-2025-0001這個訂單什么時候能送到請先查訂單狀態(tài)再回答。, expected_output給出訂單當(dāng)前所處節(jié)點與預(yù)計送達(dá)時間, agentsupport_agent, ) crew Crew( agents[support_agent], tasks[query_task], processProcess.sequential, verboseTrue, ) result crew.kickoff() print(result)執(zhí)行時Crew會先把任務(wù)交給Agent處理。模型看到任務(wù)描述里的“訂單狀態(tài)”再看到可用工具里有名稱和描述匹配的order_status_query就會自動生成調(diào)用參數(shù)并執(zhí)行工具。工具返回結(jié)果被放回上下文模型再組織成最終答復(fù)。如果你用的是其他模型服務(wù)只需要提前配置好對應(yīng)的API Key和模型名CrewAI本身并不綁定某個廠商。這里不展開具體配置按你平時用CrewAI的方式設(shè)置即可。3.5 使用工具時常見配置細(xì)節(jié)新手第一次接自定義工具最容易在導(dǎo)入路徑和類定義上卡住。先說導(dǎo)入路徑。不同版本CrewAI的BaseTool位置不完全一樣有的從crewai.tools導(dǎo)入有的從crewai_tools導(dǎo)入。我實驗過幾個版本建議直接查看你安裝版本的官方文檔或者使用pip show crewai確認(rèn)版本。代碼層面只要導(dǎo)入路徑統(tǒng)一一般不會有大問題。再說工具實例。一個工具類可以實例化多次比如訂單工具可以根據(jù)環(huán)境不同創(chuàng)建測試實例和生產(chǎn)實例。實例傳給Agent時要放在tools列表里。有些版本還支持在Task級別臨時傳工具但我更推薦統(tǒng)一放在Agent上這樣Agent相關(guān)的所有任務(wù)都能復(fù)用不會出現(xiàn)某個Task忘了掛工具、模型瞎編的情況。還有一點BaseTool類本身是Pydantic模型所以類屬性里的name和description要定義為類字段并給出值。如果名字取得太隨意比如“tool1”模型很難理解它的用途。工具命名建議用小寫字母和下劃線比如order_status_query和Python函數(shù)命名規(guī)范保持一致。4. 進(jìn)階讓工具更穩(wěn)、更快、更省token4.1 狀態(tài)管理與線程安全多數(shù)自定義工具是無狀態(tài)的輸入?yún)?shù)進(jìn)來調(diào)用外部接口返回結(jié)果。這種設(shè)計最安全因為CrewAI可能并行執(zhí)行多個任務(wù)多個Agent也可能共享同一個工具實例。如果你在工具內(nèi)部用self.xxx保存可變狀態(tài)就可能出現(xiàn)競態(tài)條件。如果確實需要統(tǒng)計調(diào)用次數(shù)、維護(hù)臨時緩存建議用鎖來保護(hù)共享狀態(tài)。比如給訂單工具加一個調(diào)用計數(shù)器import threading class OrderQueryTool(BaseTool): def __init__(self, **kwargs): super().__init__(**kwargs) self._count 0 self._lock threading.Lock() def _run(self, order_id: str, include_items: bool False) - str: with self._lock: self._count 1 current self._count return 第{current}次調(diào)用... # 實際內(nèi)容省略這段代碼只是示意實際項目中這種計數(shù)器多用于監(jiān)控和限流。核心思路是任何需要修改實例變量的地方都要考慮線程安全。能不用可變狀態(tài)就不用能用局部變量就用局部變量。4.2 緩存與冪等設(shè)計有些API查詢邏輯比較重同一個訂單號短期內(nèi)可能被模型反復(fù)查詢。如果能做一層緩存可以顯著減少外部接口壓力。查詢類工具的緩存很好加用內(nèi)存里的字典或者Redis都可以。from functools import lru_cache lru_cache(maxsize128) def _fetch_order_api(order_id: str, include_items: bool) - dict: # 實際請求邏輯 ...但要注意緩存會帶來數(shù)據(jù)陳舊的問題。訂單狀態(tài)是會變化的如果你把“運輸中”的狀態(tài)緩存了30秒模型可能給用戶一個已經(jīng)“已簽收”的舊答案。所以我通常只對“短時間內(nèi)不會變化”的數(shù)據(jù)做緩存或者給緩存設(shè)置很短的過期時間。寫入類操作則要額外注意冪等性同一個操作不能被重復(fù)提交工具內(nèi)部要做防重校驗。4.3 外部接口調(diào)用的超時與重試自定義工具一旦接通外部API穩(wěn)定性就成了最大的問題。外部接口可能慢、可能超時、可能返回5xx錯誤。requests庫的timeout參數(shù)一定要設(shè)否則一個接口卡住整個Crew任務(wù)都可能被拖死。我一般的做法是先設(shè)一個較短的連接超時比如3秒再設(shè)一個稍長的讀取超時比如5秒。失敗后可以重試但別無限重試通常兩到三次就夠了。重試之間加一點退避時間避免把下游接口打爆。import time for attempt in range(3): try: resp requests.get(url, timeout(3.05, 5)) resp.raise_for_status() break except requests.exceptions.Timeout: if attempt 2: return 訂單接口超時請稍后重試。 time.sleep(0.5 * (attempt 1))這種重試邏輯寫起來不難但能給整個智能體系統(tǒng)省下很多“看起來像傻了”的故障。模型面對超時錯誤時有時候會反復(fù)調(diào)用同一個工具試圖“碰運氣”加上了重試之后至少外部接口層面已經(jīng)盡量可靠了。4.4 輸出精簡與上下文控制一個很多人會忽略的問題是工具返回值會被拼到模型的上下文中如果返回內(nèi)容太長會帶來兩個問題一是token消耗劇增成本變高二是上下文窗口被無關(guān)信息塞滿模型的注意力會被稀釋反而更容易答錯。所以工具返回一定不能“有言必錄”。我見過有人把整個數(shù)據(jù)庫表結(jié)構(gòu)返回給模型結(jié)果模型分不清哪些字段是給用戶看的哪些是內(nèi)部狀態(tài)。正確的做法是只返回“回答用戶問題所需的最小信息集”。如果某個信息用戶不關(guān)心就不要返回。如果結(jié)果是列表比如查到了50條待處理工單不要全部輸出??梢苑祷亍肮?0條前5條為xxxx”同時提供另一個分頁查詢工具讓模型在用戶要求更多的時候再調(diào)下一步。這種設(shè)計既控制了上下文又保留了擴(kuò)展空間。5. 實際運行中的問題與排查技巧5.1 模型不會調(diào)用工具先改描述最常見的現(xiàn)象是Agent跑完了但完全是靠模型“腦補”回答根本沒有調(diào)用你的工具。打開verbose日志如果看不到Tool調(diào)用記錄基本可以確定是描述沒有觸發(fā)模型。先檢查description是不是寫得“太文縐縐”。模型不是靠語義聯(lián)想來猜工具的它是根據(jù)任務(wù)文本和工具描述的相關(guān)性來判斷的。如果你在描述里只寫“查詢訂單狀態(tài)”可能不夠應(yīng)該寫成“當(dāng)用戶詢問訂單狀態(tài)、物流節(jié)點、何時送達(dá)、簽收情況時必須使用本工具查詢不要自行猜測”。把觸發(fā)詞寫得越具體模型越容易調(diào)用。我還習(xí)慣在描述里補一句“如果訂單不存在請不要編造直接返回NOT_FOUND給用戶”。5.2 參數(shù)傳錯或類型不符另一個高頻問題模型倒是調(diào)用工具了但參數(shù)傳得離譜。比如把訂單號傳成“那筆訂單”或者把year傳成“今年”。這通常是Schema描述不夠清楚導(dǎo)致的。解決辦法有三個層面一是給Field加更詳細(xì)的描述注明格式和示例二是給參數(shù)做兜底處理在_run里做類型轉(zhuǎn)換或默認(rèn)值填充三是工具內(nèi)部對非法參數(shù)返回明確錯誤讓模型有機(jī)會重試。比如if not order_id.startswith(SO-): return 訂單號格式不正確應(yīng)以SO-開頭請確認(rèn)后重試。這樣即模型傳錯了也能得到一個可理解的反饋而不是直接拋異常。5.3 工具拋異常導(dǎo)致對話中斷工具代碼里如果存在未捕獲的異常整個Crew任務(wù)經(jīng)常會中斷而且日志里全是堆棧。用戶體驗極差。正確的做法是把異常攔截在工具內(nèi)部。前面訂單工具已經(jīng)演示了try-except的寫法。需要注意的一點是返回錯誤信息時不要返回一堆技術(shù)細(xì)節(jié)比如“KeyError: status”。應(yīng)該轉(zhuǎn)譯成“訂單數(shù)據(jù)缺少狀態(tài)字段暫時無法獲取完整信息”。模型看到這樣的內(nèi)容至少能組織出一句“系統(tǒng)暫時查詢不到該訂單的完整狀態(tài)”給用戶。5.4 智能體陷入循環(huán)或長時間不返回運行過程中可能遇到Agent反復(fù)調(diào)用同一工具比如因為工具返回了一個錯誤模型不死心又用同樣的參數(shù)調(diào)了一次形成死循環(huán)。CrewAI里可以給Agent設(shè)置max_iter限制最大迭代次數(shù)。如果超過次數(shù)還沒完成任務(wù)會以失敗或部分結(jié)果結(jié)束總比無限循環(huán)好。另外工具本身的耗時也要設(shè)上限。如前所述requests必須設(shè)timeout重試要設(shè)次數(shù)。如果一個工具的平均耗時就超過30秒那整個Crew的交互體驗會非常差。遇到這種情況要考慮異步處理或把長任務(wù)拆出去而不是讓Agent一直等著同一個同步接口。5.5 調(diào)試CrewAI應(yīng)用的輕量方法調(diào)試自定義工具我會分三步走。第一步脫離框架單獨測工具。直接寫一個腳本實例化工具類調(diào)用_run方法確認(rèn)返回值符合預(yù)期。這一步能過濾掉80%的邏輯問題。第二步用一個極小Crew做集成測試。只放一個Agent、一個Task、一個工具任務(wù)描述是固定的真實業(yè)務(wù)問題。打開verboseTrue觀察模型是否調(diào)用工具、調(diào)用參數(shù)是什么、返回結(jié)果如何被使用。第三步逐步增加復(fù)雜度。先把一個工具跑順再加第二個工具先跑單Agent再加多Agent協(xié)作。每次只變更一個變量出了問題就能立刻鎖定原因。我還習(xí)慣在工具的關(guān)鍵位置加print或log。CrewAI的verbose輸出會顯示一部分日志但工具內(nèi)部的print內(nèi)容更直接。上生產(chǎn)前再把這些調(diào)試輸出刪掉或改成logging級別。最后說一點個人體會在多個項目里改過自定義工具之后我發(fā)現(xiàn)最深的坑往往不是代碼而是工具描述。剛開始我會把description寫得很“像人話”什么“獲取訂單運輸軌跡信息”結(jié)果模型就是不愛用。后來改成“當(dāng)用戶問我的訂單到哪了、快遞到哪了、什么時候能送到時必須使用本工具”調(diào)用準(zhǔn)確率立刻上來了。另一個很深的體會是工具返回一定要精簡別一股腦把原始數(shù)據(jù)丟給模型模型不差信息差的是結(jié)構(gòu)清晰、可直接引用的答案。如果手頭正在搭CrewAI智能體我建議從一個小工具跑起。先挑一個你每天都要重復(fù)查的內(nèi)部接口做成工具掛到一個最簡單Agent上跑通。跑通之后再加第二個工具、再加第二個Agent。這套節(jié)奏看著慢但每一步都能積累可復(fù)現(xiàn)的配置和排查經(jīng)驗后面多智能體協(xié)作起來會穩(wěn)得多。