跳到主要內容

【2026 最新】LFM2.5-24B-A2B 工具呼叫驗收:三個 Mock 任務,定位格式、回傳與重複登入

最後更新: ·
工具呼叫驗收 教學首圖

你把 LFM2.5-24B-A2B 接上本機 Agent,聊天正常,它卻反覆說「正在登入」,甚至一直重做同一件事。這時最有用的第一步,是做一輪工具呼叫驗收:看模型提出什麼、程式實際做什麼,以及結果有沒有送回下一輪。

這篇寫給第一次接本機模型的讀者。你會先用假訂單、假筆記、假登入三個任務,認出整條工具鏈;再用一份 Python 小程式留下 Trace(每一步的紀錄)。完全不熟程式的人,可以先讀流程與判讀,再把程式交給協作者照同一份考卷執行。

如果你要比較多種 Runtime 與 thinking 模式,先看 兩輪工具協定驗收;本篇固定一條本機端點,把重點放在拒絕登入之後的重複攔截,以及執行前取消。成果是一個能指出「誰踩了煞車」的小型執行層。

查核截至 2026 年 10 月 15 日。本文提供官方文件支援的接入方式與教學驗收程式;未執行 LFM 模型推論,也不提供它的完成率或速度實測。小程式的預寫回應檢查,只驗接線與停止邏輯,模型能力要由你自己的下一輪紀錄回答。

先說結論:工具呼叫驗收,要接通「提單、執行、回單、交卷」

可交付的 Agent = 提對工具單 + 執行對動作 + 收到工具回單 + 答對並停下來。把模型當成會寫工作單的人,Runtime 是翻譯櫃台,Harness(執行層)則是派工與收件的人。四關各留收據,才知道該修哪一層。

Liquid AI 的 工具使用文件把流程分成工具定義、模型呼叫、外部執行與最後回答。這解釋了為什麼畫面上出現函式名稱,只能先算「提出動作」。想先補分工,可以讀 Agent Harness 是什麼。

工具呼叫驗收四關:模型提單、程式執行、工具回單、答案與停止
四關各存紀錄;任一關中斷,都應先定位,再接更大的 Agent。

原理:同一張工具單,為什麼有兩種長相?

依 這款模型的官方模型卡,LFM 預設產生 Python 形式的呼叫,例如 <|tool_call_start|>[get_order(order_id="DEMO-42")]<|tool_call_end|>。這是模型原生文字格式;我們下面的程式,讀的是伺服器整理好的 message.tool_calls。兩者之間需要解析器,也就是把工作單翻成電腦可讀欄位的櫃台。

API 層的一次呼叫應有識別碼 id、工具名稱 function.name,以及 JSON 字串 function.arguments。程式把參數解成物件、檢查名稱與值,才執行假工具;回傳時用 role: "tool" 與同一個 tool_call_id 對帳。llama.cpp 的函式呼叫文件說明了 Jinja 模板與原生/通用格式處理的分工。

還有第三個容易混淆的抽屜:思考文字。模型卡將此模型描述為推理模型,隨附聊天模板會在回答起點加入 <think>。因此要分開查看思考欄位、一般內容與結構化呼叫;不要用「看見 get_order 這幾個字」當成執行證據,更不要把那段文字交給 eval() 執行。

如果自己直接使用 Transformers,還有一處格式邊界:這份模板回放工具歷史時要求 arguments 是物件,非空的 JSON 字串會觸發例外。那條路要先 json.loads();下面走伺服器 API,則保留 API 回應的 assistant 訊息,由伺服器做適配。兩條路分清楚,才不會把同一份資料轉換兩次。

工具呼叫驗收第一步:「認門牌」,固定本機端點與模型檔

先選一條路就好。以下用 llama.cpp + 官方 Q4_K_M GGUF。先從 官方部署說明選對作業系統的 Runtime,閱讀 模型授權條款,再評估下載與記憶體。若你的設備裝不下,先停在考卷設計;不要讓 A2B 名字代替容量規劃,本機 LLM 記憶體指南可以補這一課。

以下是 macOS/Linux shell 指令,前提是已備好 llama-server 與 hf CLI。下載會取得大型權重檔;已有同一檔案可跳過下載。把版本與檔案雜湊一起保存,整輪驗收期間保持不變。這裡固定官方 GGUF revision,避免 main 更新後同題其實換了權重。

mkdir -p lfm-mock/models
cd lfm-mock
llama-server --version > runtime-version.txt 2>&1
hf download LiquidAI/LFM2.5-24B-A2B-GGUF LFM2.5-24B-A2B-Q4_K_M.gguf --revision 0912d33141f77e10705345a6732bfd83f0252fdc --local-dir models
shasum -a 256 models/LFM2.5-24B-A2B-Q4_K_M.gguf > model-sha256.txt
llama-server -m models/LFM2.5-24B-A2B-Q4_K_M.gguf --alias LFM2.5-24B-A2B --jinja --host 127.0.0.1 --port 8080 -c 8192 -n 4096

這份起跑配置用 8,192 context、每次最多 4,096 輸出 tokens,目的是先跑短假任務,並非模型容量上限,也不是推薦給所有硬體的最佳設定。若載入失敗,先檢查 Runtime 是否支援此模型、實際 RAM/顯存與啟動日誌;不要一開始把上下文開到最大。上述旗標可在 llama-server 參數文件逐項核對。

服務啟動後,在第二個終端執行 curl --noproxy "*" http://127.0.0.1:8080/v1/models,確認模型 alias。再停止這個服務重送一次;若仍拿到成功回答,應查另一個監聽程序、代理或轉送。端點、程序與日誌對得上,才進下一關;一個叫 local 的模型名稱本身不足以證明資料路線。

工具呼叫驗收第二步:「縮小菜單」,先寫三個假任務的答案

Mock 就像演習用道具。下面的「查訂單」「讀筆記」「登入」都只從程式內的固定資料回傳結果,不讀你的檔案、不接真實帳號,也不建立 Repo。每次只給一個工具,先把選錯工具的干擾拿掉。

  • 假訂單:get_order(order_id="DEMO-42") 回傳 status="packed"、total=320。通過要同時有一次有效執行,以及答案說明已包裝、金額 320;幣別未提供,別自行補。
  • 假筆記:read_note(note_id="NOTE-7") 回傳「會議改到星期四 15:00」。通過要保留星期四和 15:00,並有工具回單,不能只看到一句像樣的摘要。
  • 假登入:mock_login(account="demo") 回傳 ok=true。答案應明說是模擬成功;FAKE-SESSION 是道具,並非可以拿去登入網站的憑證。

第四輪沿用登入任務,故意改回 AUTH_REJECTED,要求失敗就停止。重點是同一錯誤出現後,它能否如實交卷;若又提出同一呼叫,執行層要在第二次動作前攔住。這項停止政策是本教學訂的規則,不是模型內建保證。

第三步:「一張單一張回單」,用小程式把四關記下來

把下列完整內容存成剛才資料夾裡的 mock_acceptance.py,用 Python 3 執行。它只用標準函式庫;模型端點固定為 loopback。程式最多請求四輪、HTTP socket 逾時設為 120 秒;這是小型驗收預算,不是整段工作的嚴格總時限。

import json, sys, time, urllib.request
from pathlib import Path

BASE = 'http://127.0.0.1:8080/v1'
MODEL = 'LFM2.5-24B-A2B'
CASES = {
    'order': ('get_order', 'order_id', 'DEMO-42',
              {'status': 'packed', 'total': 320}, '查假訂單 DEMO-42 的狀態與金額。'),
    'note': ('read_note', 'note_id', 'NOTE-7',
             {'text': '會議改到星期四 15:00'}, '讀假筆記 NOTE-7,回答會議時間。'),
    'login': ('mock_login', 'account', 'demo',
              {'ok': True, 'session': 'FAKE-SESSION'}, '模擬登入 demo,報告結果。'),
    'reject': ('mock_login', 'account', 'demo',
               {'ok': False, 'error': 'AUTH_REJECTED'}, '模擬登入 demo,失敗就停止,不要重試。'),
}

def run(case):
    name, key, expected, result, prompt = CASES[case]
    tool = {'type': 'function', 'function': {
        'name': name, 'description': '只操作教學假資料',
        'parameters': {'type': 'object', 'properties': {key: {'type': 'string'}},
                       'required': [key], 'additionalProperties': False}}}
    messages = [{'role': 'system', 'content':
                 '使用指定工具取得假資料後回答。工具失敗就如實報告,不重試。'},
                {'role': 'user', 'content': prompt}]
    seen, executed, tokens = set(), 0, 0
    started = time.monotonic()
    trace = Path('trace-' + case + '.jsonl')
    def log(event, **fields):
        with trace.open('a', encoding='utf-8') as f:
            f.write(json.dumps({'event': event, 'elapsed_seconds': time.monotonic()-started, **fields}, ensure_ascii=False) + '\n')
    log('start', case=case, base=BASE, model=MODEL)
    for turn in range(4):
        if Path('CANCEL').exists():
            log('stop', reason='cancelled'); return
        payload = {'model': MODEL, 'messages': messages, 'tools': [tool],
                   'temperature': 0, 'max_tokens': 4096, 'stream': False,
                   'parallel_tool_calls': False}
        req = urllib.request.Request(BASE + '/chat/completions',
              data=json.dumps(payload).encode(),
              headers={'Content-Type': 'application/json'})
        with urllib.request.urlopen(req, timeout=120) as r:
            body = json.load(r)
        log('response', turn=turn, body=body)
        usage = body.get('usage', {}).get('total_tokens')
        tokens = tokens + usage if tokens is not None and usage is not None else None
        choice = body['choices'][0]
        if choice.get('finish_reason') == 'length':
            log('stop', reason='truncated'); return
        msg = choice['message']
        calls = msg.get('tool_calls') or []
        if not calls:
            log('final', text=msg.get('content'), executed=executed,
                seconds=time.monotonic()-started, total_tokens=tokens)
            print(msg.get('content')); return
        if len(calls) != 1:
            log('stop', reason='expected_one_call'); return
        call = calls[0]; fn = call['function']
        args = json.loads(fn['arguments'])
        if fn['name'] != name or args != {key: expected} or not call.get('id'):
            log('stop', reason='invalid_call'); return
        signature = (fn['name'], json.dumps(args, sort_keys=True))
        if signature in seen:
            log('stop', reason='duplicate_call'); return
        if Path('CANCEL').exists():
            log('stop', reason='cancelled_before_tool'); return
        seen.add(signature)
        # Only returns a constant. No filesystem, network, shell or real login.
        executed += 1
        log('mock_result', call_id=call['id'], result=result)
        messages.append(msg)
        messages.append({'role': 'tool', 'tool_call_id': call['id'],
                         'content': json.dumps(result, ensure_ascii=False)})
    log('stop', reason='turn_limit')

if __name__ == '__main__':
    try:
        run(sys.argv[1])
    except KeyboardInterrupt:
        print('已取消;已完成的動作不會自動回復。', file=sys.stderr)
        sys.exit(130)
    except Exception as e:
        print('已停止:' + type(e).__name__ + ': ' + str(e), file=sys.stderr)
        sys.exit(1)

此程式是最小接線測試,不會自動把 final 標成通過。即使有最後回答,也要查 executed=1 並逐字對照上面的答案;executed=0 代表這輪沒執行假工具。HTTP、JSON 或欄位錯誤會結束程序並印出錯誤,適合先定位相容性;正式系統還需要獨立的答案評分、完整錯誤事件、持久化與動作冪等性(重送不重做)。Harness 實作篇能接著補執行層設計。

為了把模型請求固定送往這個端點,這份教學可以在目前 shell 暫時移除代理環境變數後再跑。這會影響同一個終端後續程式的網路設定;完成後關掉這個終端即可回到其他新終端的原設定。

unset HTTP_PROXY HTTPS_PROXY ALL_PROXY http_proxy https_proxy all_proxy
python3 mock_acceptance.py order 2> order-error.txt
python3 mock_acceptance.py note 2> note-error.txt
python3 mock_acceptance.py login 2> login-error.txt
python3 mock_acceptance.py reject 2> reject-error.txt

每輪會把事件附加到 trace-order.jsonl 等檔案,一行一個 JSON。重跑時請另建資料夾或把舊紀錄改名,避免把兩次 start 算成同一輪。先讀 response 裡的 tool_calls,再找 mock_result,最後才看 final;這就是用收據確認工作,而不是相信完成宣告。

第四步:「沿著回單找斷點」,完整走一次假訂單

理想的順序是:你問 DEMO-42 → API 回傳 get_order 與正確參數 → 程式留下 mock_result → 下一次請求帶回原 assistant 訊息與配對的 tool 訊息 → 模型回答已包裝、320。這是預期 Trace,不是本文觀察到的 LFM 輸出。

  • 只在 content 看見原生工具標記:先查 Runtime、模板與解析日誌。此程式不把純文字當成可執行工具單;可以看到 final,但 executed 仍是 0。
  • 有 tool_calls,卻停在 invalid_call:查工具名、呼叫識別碼與參數。DEMO-42 和 DEMO-24 是不同訂單,參數多一個欄位也不符合這份固定考卷。
  • 有 mock_result,最後答案卻錯:回傳通道已走到一半;再核對第二次請求歷史是否正確保留,以及模型是否忠實讀取結果。
  • finish_reason 是 length:這次輸出被截斷,先記失敗。需要增加預算時另開一輪,保留舊結果,避免把花了更多資源的補考混成同條件通過。

如果你自己改成直接呼叫 tokenizer,記得前面提過的 arguments 物件轉換;若仍走 API,先使用伺服器內建的 GGUF 模板,不要隨手套另一個模型的 chatml 模板。真的要替換模板時,把模板檔與雜湊也放進版本紀錄,再重跑同一組考卷。

第五步:「先驗煞車」,拒絕、重複與取消各考一次

在 reject 輪,理想答案會報告驗證被拒絕並停止。若模型又叫同一工具、帶同一參數,程式應留下 duplicate_call,而且 mock_result 仍只有一次。這表示執行層攔截生效,模型「依錯誤自行停止」這項則未通過;兩張成績單別混在一起。

要測取消,在同一資料夾建立名為 CANCEL 的空白檔,再跑一輪;預期於發出新請求前停止。請求途中才建立它,會在 HTTP 回應後、執行工具前再次檢查;正在等待時可用 Ctrl+C 中斷。刪掉這個你建立的空白檔才恢復測試。這份程式沒有遠端工作取消協定,取消也不等於撤銷已完成的真實動作。

把相同簽章一律擋掉,是這三個一次性假任務的保守設定。真實工作可能合法地重讀狀態,不能照抄成通用規則;到時要分清查詢、可安全重送的操作,以及需要唯一動作識別碼的寫入。真正的登入與 Repo 管理,應等這個小圈圈驗收完成,再另寫專用考卷。

第六步:「同題對照」,把完成、耗時與 tokens 分開

保留原來模型,與 LFM 各跑同一套四輪,鎖定工具、假資料、提示詞、輸出上限、context 和停止規則。只替換模型端點/alias 與它需要的官方模板,逐項保存差異。先各做三次作為小規模探索,逐題記通過或失敗;三次是起步樣本,不足以推算廣泛任務的可靠率。

每次記三件事:交付看答案與工具證據;時間看 start 到 final,失敗與取消也另外計時;用量加總每次請求的 usage.total_tokens。程式遇到缺失用量會記 null,別當成零;這裡加總的是 API 報告的請求用量,會包含再次送入的歷史,不是獨特文字數。

比較結果可以寫「訂單通過、筆記時間錯、拒絕登入被 Harness 擋住」,再補時間與 tokens。不同模型的 tokenizer 與計量方式可能不同,token 總數只是其中一個觀察值。要建立較完整評分,可以讀 AI Evals 入門;想理解這款模型的容量與廠商成績,再讀 LFM2.5-24B-A2B 發布分析。

FAQ:新手最容易誤判的八件事

1. 聊天正常,就能直接放進 Agent 嗎?

還要驗。聊天只走輸入與文字輸出;工具鏈多了格式解析、參數檢查、執行、回傳與停止。先跑一個工具的假任務,逐關拿證據。

2. API 說相容,就代表每種工具格式都對嗎?

不一定。相容介面仍需模型模板與解析器合作。先看 tool_calls 是否存在、參數能否解成 JSON,再進第二輪工具結果回傳。

3. 把 Python 形式的呼叫 eval 一下最快嗎?

不要這樣做。把模型文字當程式執行,會越過這份測試的工具白名單。這裡只接受已解析欄位,並核對固定工具名與固定參數。

4. 最後回答正確,executed=0 也能算過嗎?

這張考卷不算。它要驗的是從工具取資料的完整路徑;碰巧答對或直接猜測,不能證明程式執行與結果回傳。

5. 看到 duplicate_call,算模型知道停止嗎?

不算。這證明 Harness 擋住重複執行。模型自行停止與執行層攔截,要各記一格,才能知道後續該修提示、解析還是控制流程。

6. 本機 API 代表任何工具都不會外傳嗎?

要逐條看路線。本文假工具只回傳程式內常數;換成網頁搜尋或外部登入後,請求就有其他目的地。把工具目的地與日誌位置另外列出。

7. 4,096 輸出 tokens 一定夠嗎?

不一定。它是本教學起跑上限。若遇到 length,保留失敗紀錄,再以另一份設定補考;思考與最終輸出都會占用生成預算。

8. 三個假任務通過,下一步直接接正式帳號嗎?

先擴大考卷。加入缺單、缺筆記、錯參數與中途取消,並確認錯誤不會引發真實寫入。每次只增加一種能力,才看得出新故障的來源。

給新手的三個重點

  • 先看結構化工具單,再看真的執行了什麼;最後答案只是第四關。
  • 把拒絕登入當必考題,分清模型停下來與 Harness 替它踩煞車。
  • 先保留同題的成功與失敗紀錄,再決定把哪一類工作交給本機模型。

接著閱讀

左右滑動查看更多推薦

下一步:今天先讓一張假訂單完整交卷

先執行 order 輪,把「提單、執行、回單、交卷」四張收據找齊,再跑 reject 輪確認煞車。當你能指出卡在哪一關,就有了比反覆換模型更具體的下一步。想循序學會把 AI 接成可管理的工作流程,可以接著看 AlphaLab 課程,或回到 AI 專題繼續補觀念。

ALPHALAB 社群

有問題?來 Telegram 聊

和 Terry、編輯、其他網友一起討論這篇文章。提問、分享觀點,回覆更即時。

加入 Telegram 討論

📩 訂閱 AlphaLab 電子報

每週最多三封:一封 Weekly 週報與最多兩封關鍵 Alpha Signal。

我們不會 spam,隨時可退訂。已訂閱?管理主題偏好(會寄登入連結到你的信箱)