聊了這麼多理論,今天換點實戰,
我們要用 Claude + MCP 工具,做一個小小機器人!
Claude 是 Anthropic 推出的 LLM,和 OpenAI 的 ChatGPT 類似。
它的特點是:
要做一個機器人很簡單:
舉例:
import os
import json
from datetime import datetime
from dateutil.tz import gettz
from anthropic import Anthropic
# ===== 這裡模擬你的 MCP 工具 =====
# 你可以把這些函式內容替換成實際呼叫你的 MCP Server(例如用 websocket/stdio 連線)
# 範例:ptt_crawler -> summarizer -> (sentiment 可選)
def mcp_ptt_crawler(board: str, date: str, limit: int = 5) -> list[dict]:
"""
模擬:抓取 PTT 文章。實務上請改成呼叫你的 ptt_crawler MCP tool。
回傳格式:[{title, url, author, time, content}, ...]
"""
# TODO: 改成你的 MCP 呼叫
# 這裡放幾筆假資料,讓流程可跑
return [
{
"title": "[標的] 2330 台積電 法說後觀察",
"url": "https://www.ptt.cc/bbs/Stock/M.1234567890.A.html",
"author": "stocklover",
"time": f"{date} 10:12",
"content": "法說重點:先進製程訂單穩定,高頻寬記憶體需求強勁,資本支出維持不變。市場擔憂毛利率壓力。"
},
{
"title": "[情報] 散裝航運運價走勢",
"url": "https://www.ptt.cc/bbs/Stock/M.1234567891.A.html",
"author": "shipman",
"time": f"{date} 12:30",
"content": "BDI 上漲但分項走勢分歧,中小型船舶較強。股價短線反映,下週留意回檔風險。"
}
][:limit]
def mcp_summarizer(texts: list[str]) -> str:
"""
模擬:摘要多篇文章 -> 懶人包。實務上請改成你的 summarizer MCP tool。
"""
# TODO: 改成你的 MCP 呼叫
bullets = []
for i, t in enumerate(texts, 1):
# 超簡單「句首擷取」示範,實務請換模型摘要
bullets.append(f"{i}. {t[:80]}{'...' if len(t) > 80 else ''}")
return "今日重點整理:\n" + "\n".join(bullets)
def mcp_sentiment(texts: list[str]) -> list[str]:
"""
模擬:情緒分析(可選)。實務上請改成你的 sentiment MCP tool。
回傳與 texts 等長,例如:['正向', '中性', '負向']
"""
# TODO: 改成你的 MCP 呼叫
return ["中性" for _ in texts]
# ===== Anthropic 工具定義(讓 Claude 知道有哪些工具可用、輸入 schema 長什麼樣)=====
TOOLS = [
{
"name": "ptt_crawler",
"description": "抓取 PTT 文章",
"input_schema": {
"type": "object",
"properties": {
"board": {"type": "string", "description": "PTT 看板,如 Stock / car"},
"date": {"type": "string", "description": "日期 YYYY-MM-DD"},
"limit": {"type": "integer", "description": "抓取篇數,預設 5"}
},
"required": ["board", "date"]
}
},
{
"name": "summarizer",
"description": "將多篇文章內容整理成懶人包",
"input_schema": {
"type": "object",
"properties": {
"texts": {
"type": "array",
"items": {"type": "string"},
"description": "多篇文章的純文字內容"
}
},
"required": ["texts"]
}
},
{
"name": "sentiment",
"description": "對多篇文本做情緒分析(可選)",
"input_schema": {
"type": "object",
"properties": {
"texts": {
"type": "array",
"items": {"type": "string"},
"description": "多篇文章內容"
}
},
"required": ["texts"]
}
}
]
def run_chat(user_query: str):
client = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
# 讓程式自動帶「今天」日期(台北時區)
today = datetime.now(gettz("Asia/Taipei")).strftime("%Y-%m-%d")
system_prompt = (
"你是投資版機器人。若需要外部資訊,請使用可用的工具(ptt_crawler, summarizer, sentiment)。"
"當使用者問『幫我整理今天 PTT 股市文章』時,建議流程:ptt_crawler(board='Stock', date=today)-> summarizer -> 回答。"
"若覺得不需要 sentiment 可省略。所有最終回答請用繁體中文。"
)
# 初始 user 訊息
messages = [
{"role": "user", "content": user_query.replace("今天", today)}
]
# 發送第一次請求,讓 Claude 自己決定是否用工具
resp = client.messages.create(
model="claude-3-5-sonnet-20240620", # 可換成你有權限的最新型號
max_tokens=1500,
system=system_prompt,
tools=TOOLS,
messages=messages
)
# 工具迴圈:只要 Claude 發出 tool_use,我們就執行並回傳 tool_result,直到得到最終文字
while True:
tool_uses = []
final_text_chunks = []
for block in resp.content:
if block.type == "tool_use":
tool_uses.append(block)
elif block.type == "text":
final_text_chunks.append(block.text)
if tool_uses:
# 逐一執行 Claude 要求的工具,並回傳 tool_result
tool_results = []
for tu in tool_uses:
name = tu.name
args = tu.input or {}
if name == "ptt_crawler":
out = mcp_ptt_crawler(
board=args.get("board", "Stock"),
date=args.get("date", today),
limit=int(args.get("limit", 5)),
)
tool_results.append({
"type": "tool_result",
"tool_use_id": tu.id,
"content": json.dumps(out, ensure_ascii=False)
})
elif name == "summarizer":
texts = args.get("texts", [])
out = mcp_summarizer(texts)
tool_results.append({
"type": "tool_result",
"tool_use_id": tu.id,
"content": out
})
elif name == "sentiment":
texts = args.get("texts", [])
out = mcp_sentiment(texts)
tool_results.append({
"type": "tool_result",
"tool_use_id": tu.id,
"content": json.dumps(out, ensure_ascii=False)
})
else:
tool_results.append({
"type": "tool_result",
"tool_use_id": tu.id,
"content": f"未知工具: {name}"
})
# 將 tool_result 當成「使用者訊息」補回對話,再請 Claude 繼續
messages.append({
"role": "assistant",
"content": resp.content # 保留上一輪模型輸出(含 tool_use),讓對話連貫
})
messages.append({
"role": "user",
"content": tool_results
})
resp = client.messages.create(
model="claude-3-5-sonnet-20240620",
max_tokens=1500,
system=system_prompt,
tools=TOOLS,
messages=messages
)
# 進入下一輪 while,直到沒有 tool_use
continue
# 沒有 tool_use,代表 Claude 已產生最終敘述
final_text = "\n".join(final_text_chunks).strip()
return final_text or "(沒有產生內容)"
if __name__ == "__main__":
# 範例:使用者問「幫我整理今天 PTT 股市文章」
query = "幫我整理今天 PTT 股市文章(給重點懶人包)"
print(run_chat(query))
Claude + MCP 工具,就能快速拼出一個「懂得抓資料 + 分析 + 回答」的機器人。
明天 (Day28),我們來換個角度:用 Gradio 做一個可以互動的機器人介面。