昨天我們讓系統具備了「會查條文、會記得你說過什麼」的能力,但這些功能仍然是人工判斷。
今天要邁向真正的「Agent」:讓模型自己看到工具列表後,決定何時使用哪個工具。
今天的目標有:
一樣只有新增新的檔案,不要刪掉舊的資料夾窩><
project/
└─ agent/
└─ agent_autogen.py # AutoGen Tool Selection MVP
pip install pyautogen
pip install "autogen-ext[ollama]"
雖然之前都有說到要用 AutoGen 但好像都還沒有好好介紹他,他其實是是微軟開源的「多 Agent 框架」,可以讓 LLM 在工具之間「互動、呼叫、溝通」。
但我們還不會用到 多 Agent 就是了~
agent/agent_autogen.py
先從最小可執行版本開始:我們只註冊兩個工具,讓模型自己選用。
import sys, json, argparse, asyncio
from pathlib import Path
from datetime import datetime
# 讓我們能 import 到 project/ 下的模組
sys.path.append(str(Path(__file__).resolve().parents[1]))
# 原本就有的工具函式
from rag.connect import search_chunks, build_prompt, ask_ollama, article_lookup
from utils.memory import memory_lines, remember
from autogen_agentchat.agents import AssistantAgent
from autogen_ext.models.ollama import OllamaChatCompletionClient
# --- 定義工具(直接用 Python 函式,Agent 會自動包 FunctionTool) ---
def lookup_article_tool(q: str) -> str:
"""直查第N條全文;適合『第X條是什麼?』"""
return article_lookup(q) or "(沒有查到對應條文)"
def search_law_tool(query: str, k: int = 4) -> str:
"""一般問答:RAG 檢索 + 生成"""
hits = search_chunks(query, k)
prompt = build_prompt(query, hits, mem_lines=memory_lines())
return ask_ollama(prompt).strip()
async def main():
ap = argparse.ArgumentParser(description="AgentChat 單代理(工具選擇 MVP)")
ap.add_argument("--q", required=True, help="問題內容")
ap.add_argument("--model", default="mistral", help="Ollama 模型名(預設 mistral,例如 llama3)")
ap.add_argument("--max_tool_iter", type=int, default=3, help="單次最多工具呼叫次數")
ap.add_argument("--json_out", action="store_true", help="是否輸出 JSONL 到 logs/")
args = ap.parse_args()
# 建立 Ollama 模型客戶端
model_client = OllamaChatCompletionClient(model=args.model) # 預設 http://localhost:11434
# 建立 agent,工具直接丟函式
agent = AssistantAgent(
name="law_agent",
model_client=model_client,
tools=[lookup_article_tool, search_law_tool], # 讓模型自己決定用哪個
system_message=(
"你是台灣資安法規助理。遇到『第X條』請優先呼叫條文直接查詢;"
"其他一般法規問題請用 RAG 檢索工具。回答務必簡潔、列點。"
),
max_tool_iterations=args.max_tool_iter,
reflect_on_tool_use=True, # 讓模型對工具結果做一次短反思
model_client_stream=False, # 需要串流可改 True
)
# 跑起來
result = await agent.run(task=args.q)
answer = getattr(result, "content", str(result))
print("\n=== 最終回答 ===")
print(answer)
# 記憶:寫入本次 Q/A 摘要
remember(args.q, answer)
# 統一 JSONL 日誌
if args.json_out:
logs = Path("logs"); logs.mkdir(parents=True, exist_ok=True)
out = logs / f"run_{datetime.now():%Y%m%d}.jsonl"
record = {
"ts": datetime.now().isoformat(timespec="seconds"),
"mode": "autogen-1agent",
"query": args.q,
"model": args.model,
"answer": answer,
}
out.open("a", encoding="utf-8").write(json.dumps(record, ensure_ascii=False) + "\n")
print(f"\n[log] 已寫入:{out}")
if __name__ == "__main__":
asyncio.run(main())
之後可以嘗試輸入指令做測試:
python agent/agent_autogen.py --q "第14條是什麼?" --json_out
他可能輸出的東西你會看著很奇怪,但這是 AutoGen 預設的內容,方便我們除錯用,如果你還是看著不習慣,你可以自己處理,讓輸出更乾淨點~
明天會加上原先的 RAGAS,就不廢話了,明天見!