iT邦幫忙

2025 iThome 鐵人賽

DAY 27
1
Software Development

MCP的30天養成計畫系列 第 27

【Day 27】來做個簡單的機器人吧! Claude

  • 分享至 

  • xImage
  •  

聊了這麼多理論,今天換點實戰,
我們要用 Claude + MCP 工具,做一個小小機器人!


Claude 是什麼?

Claude 是 Anthropic 推出的 LLM,和 OpenAI 的 ChatGPT 類似。
它的特點是:

  • 支援 MCP
  • 很擅長處理長文本
  • 回答風格偏向穩重、細膩

Claude + MCP

要做一個機器人很簡單:

  1. 把 MCP 工具(例如 summarizer、sentiment)掛上去
  2. 透過 Claude 的 function calling
  3. 讓 Claude 自己決定要不要使用工具

舉例:

  • 使用者問「幫我整理今天 PTT 股市文章」
  • Claude 會呼叫 ptt_crawler → summarizer → 回傳懶人包

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 做一個可以互動的機器人介面。


上一篇
【Day 26】Tool搭配LangGraph的使用(二)
系列文
MCP的30天養成計畫27
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言