---
title: 客戶支援代理
url: https://platform.claude.com/docs/zh-TW/about-claude/use-case-guides/customer-support-chat
description: 使用 Claude 建構一個客戶支援聊天機器人，能夠回答產品問題、保持主題聚焦，並透過工具使用產生報價。
---

## 先決條件

要依照本指南操作，您需要：

* 一個 Claude API 金鑰（設定為 `ANTHROPIC_API_KEY` 環境變數）
* Python 3.10 或更新版本

安裝所需的套件：

```bash
pip install anthropic streamlit python-dotenv
```

## 使用 Claude 建構之前

### 決定是否使用 Claude 進行支援聊天

以下是一些關鍵指標，表明您應該採用像 Claude 這樣的「large language model」（大型語言模型），即 LLM，來自動化部分客戶支援流程：

<AccordionGroup>
  <Accordion title="大量重複性查詢">
    Claude 擅長高效處理大量相似的問題，讓人工客服人員能夠專注於更複雜的問題。
  </Accordion>

  <Accordion title="需要快速整合資訊">
    Claude 可以快速從龐大的知識庫中檢索、處理並整合資訊，而人工客服人員可能需要時間進行研究或查閱多個來源。
  </Accordion>

  <Accordion title="需要 24/7 全天候服務">
    Claude 可以不知疲倦地提供全天候支援，而安排人工客服人員持續值班可能成本高昂且具有挑戰性。
  </Accordion>

  <Accordion title="尖峰時段快速擴展">
    Claude 可以處理查詢量的突然增加，而無需招募和培訓額外的員工。
  </Accordion>

  <Accordion title="一致的品牌語調">
    您可以指示 Claude 始終如一地呈現您品牌的語調和價值觀，而人工客服人員的溝通風格可能各有差異。
  </Accordion>
</AccordionGroup>

選擇 Claude 而非其他 LLM 的一些考量因素：

* 您重視自然、細膩的對話：Claude 精密的語言理解能力可實現更自然、更具上下文感知的對話，比與其他 LLM 的聊天更像人類。
* 您經常收到複雜且開放式的查詢：Claude 可以處理各式各樣的主題和詢問，而不會產生制式回應，也不需要針對使用者話語的各種排列組合進行大量程式設計。
* 您需要可擴展的多語言支援：Claude 的多語言能力使其能夠以超過 200 種語言進行對話，而無需為每種支援的語言建立單獨的聊天機器人或進行繁瑣的翻譯流程。

### 定義您理想的聊天互動

勾勒出理想的客戶互動，以定義您期望客戶如何以及何時與 Claude 互動。這份大綱將有助於確定您解決方案的技術需求。

以下是汽車保險客戶支援的聊天互動範例：

* **客戶：** 開啟支援聊天體驗
  * **Claude：** 熱情地問候客戶並開啟對話

* **客戶：** 詢問其新電動車的保險事宜
  * **Claude：** 提供有關電動車保障範圍的相關資訊

* **客戶：** 詢問與電動車保險獨特需求相關的問題
  * **Claude：** 以準確且資訊豐富的答案回應，並提供來源連結

* **客戶：** 詢問與保險或汽車無關的離題問題
  * **Claude：** 說明其不討論無關主題，並引導使用者回到汽車保險話題

* **客戶：** 表示對保險報價感興趣

  * **Claude：** 提出一系列問題以確定適當的報價，並根據客戶的回答進行調整
  * **Claude：** 發送使用報價產生 API 工具的請求，並附上從使用者收集的必要資訊
  * **Claude：** 接收來自 API 工具使用的回應資訊，將資訊整合為自然的回應，並向使用者呈現所提供的報價

* **客戶：** 提出後續問題

  * **Claude：** 視需要回答後續問題
  * **Claude：** 引導客戶進入保險流程的下一步並結束對話

<Tip>
  在您為自己的使用案例撰寫的實際範例中，您可能會發現寫出此互動中的實際用語很有幫助，這樣您也可以掌握您希望 Claude 具備的理想語調、回應長度和詳細程度。
</Tip>

### 將互動拆解為獨立任務

客戶支援聊天是多種不同任務的集合，從回答問題到資訊檢索，再到對請求採取行動，全都包含在單一的客戶互動中。在開始建構之前，請將您理想的客戶互動拆解為您希望 Claude 能夠執行的每一項任務。這可確保您能針對每項任務對 Claude 進行提示和評估，並讓您充分了解在撰寫測試案例時需要考量的互動範圍。

<Tip>
  客戶有時會發現，將其視覺化為一張互動流程圖很有幫助，圖中呈現依據使用者請求而可能出現的對話轉折點。
</Tip>

以下是與範例保險互動相關的關鍵任務：

1. 問候與一般指引

   * 熱情地問候客戶並開啟對話
   * 提供有關公司和互動的一般資訊

2. 產品資訊

   * 提供有關電動車保障範圍的資訊
     <Note>
       這將要求 Claude 在其上下文中擁有必要的資訊，並且可能意味著需要 

       [RAG 整合](https://platform.claude.com/cookbook/capabilities-retrieval-augmented-generation-guide)

       。
     </Note>
   * 回答與電動車保險獨特需求相關的問題
   * 回答有關報價或保險細節的後續問題
   * 在適當時提供來源連結

3. 對話管理

   * 保持主題聚焦（汽車保險）
   * 將離題問題重新導向回相關主題

4. 報價產生

   * 提出適當的問題以確定報價資格
   * 根據客戶回應調整問題
   * 將收集到的資訊提交至報價產生 API
   * 向客戶呈現所提供的報價

### 建立成功標準

與您的支援團隊合作，[定義成功標準並撰寫詳細的評估](https://platform.claude.com/docs/zh-TW/test-and-evaluate/develop-tests)，其中包含可衡量的基準和目標。

以下是可用於評估 Claude 執行所定義任務之成效的標準和基準：

<AccordionGroup>
  <Accordion title="查詢理解準確度">
    此指標評估 Claude 在各種主題上理解客戶詢問的準確程度。透過檢視對話樣本並評估 Claude 是否正確解讀客戶意圖、關鍵的後續步驟、成功解決的樣貌等來衡量。目標是達到 95% 或更高的理解準確度。
  </Accordion>

  <Accordion title="回應相關性">
    此項評估 Claude 的回應在多大程度上解決了客戶的具體問題或議題。評估一組對話並為每個回應的相關性評分（使用基於 LLM 的評分以擴大規模）。目標是達到 90% 或以上的相關性分數。
  </Accordion>

  <Accordion title="回應準確度">
    根據在上下文中提供給 Claude 的資訊，評估提供給使用者的一般公司和產品資訊的正確性。此類介紹性資訊的目標是 100% 準確。
  </Accordion>

  <Accordion title="引用提供相關性">
    追蹤所提供連結或來源的頻率和相關性。目標是在 80% 可能受益於額外資訊的互動中提供相關來源。
  </Accordion>

  <Accordion title="主題遵循度">
    衡量 Claude 保持主題聚焦的程度，例如範例實作中的汽車保險主題。目標是 95% 的回應與汽車保險或客戶的具體查詢直接相關。
  </Accordion>

  <Accordion title="內容產生有效性">
    衡量 Claude 在判斷何時產生資訊性內容方面的成功程度，以及該內容的相關性。例如，在此實作中，您將判斷 Claude 對何時產生報價的理解程度以及該報價的準確度。目標是 100% 準確，因為這是成功客戶互動的關鍵資訊。
  </Accordion>

  <Accordion title="升級處理效率">
    此項衡量 Claude 識別查詢何時需要人工介入並適當升級處理的能力。追蹤正確升級的對話百分比，與應該升級但未升級的對話百分比進行比較。目標是達到 95% 或更高的升級準確度。
  </Accordion>
</AccordionGroup>

以下是可用於評估採用 Claude 進行支援之業務影響的標準和基準：

<AccordionGroup>
  <Accordion title="情緒維持">
    此項評估 Claude 在整個對話過程中維持或改善客戶情緒的能力。使用情緒分析工具衡量每次對話開始和結束時的情緒。目標是在 90% 的互動中維持或改善情緒。
  </Accordion>

  <Accordion title="轉移率">
    聊天機器人在無需人工介入的情況下成功處理的客戶詢問百分比。通常目標為 70-80% 的轉移率，視詢問的複雜程度而定。
  </Accordion>

  <Accordion title="客戶滿意度分數">
    衡量客戶對其聊天機器人互動的滿意程度。通常透過互動後的問卷調查進行。目標是 CSAT 分數達到 5 分中的 4 分或更高。
  </Accordion>

  <Accordion title="平均處理時間">
    聊天機器人解決一項詢問所需的平均時間。這會因問題的複雜程度而有很大差異，但一般而言，目標是比人工客服人員更低的 AHT。
  </Accordion>
</AccordionGroup>

## 如何將 Claude 實作為客戶服務代理

### 選擇合適的 Claude 模型

模型的選擇取決於成本、準確度和回應時間之間的權衡。

對於客戶支援聊天，Claude Opus 5.5 非常適合在智慧、「latency」（延遲）和成本之間取得平衡，包括需要在冗長、多步驟對話中進行深度推理的最複雜支援情境。然而，若您的對話流程包含多個提示，包括 RAG、工具使用或長上下文提示，Claude Haiku 5.5 可能更適合用於最佳化延遲。[Effort](https://platform.claude.com/docs/zh-TW/build-with-claude/effort)（投入程度）是其控制速度的主要方式：`low` 是最快的等級，適合聊天和簡短的工具任務。

### 建構強大的提示

使用 Claude 進行客戶支援需要 Claude 擁有足夠的指引和上下文以做出適當回應，同時具備足夠的彈性來處理各式各樣的客戶詢問。

首先撰寫強大提示的各個要素，從「system prompt」（系統提示）開始。建立一個名為 `config.py` 的檔案，並將以下每個區塊加入其中：

```python
IDENTITY = """You are Eva, a friendly and knowledgeable AI assistant for Acme Insurance
Company. Your role is to warmly welcome customers and provide information on
Acme's insurance offerings, which include car insurance and electric car
insurance. You can also help customers get quotes for their insurance needs."""
```

<Tip>
  雖然您可能會想將所有資訊放在系統提示中，作為將指令與使用者對話分開的方式，但實際上 Claude 在大部分提示內容寫在第一個 

  `User`

   輪次中時效果最佳（唯一的例外是角色提示）。請參閱

  [透過系統提示賦予 Claude 角色](https://platform.claude.com/docs/zh-TW/build-with-claude/prompt-engineering/claude-prompting-best-practices#give-claude-a-role)

  以了解更多資訊。
</Tip>

最好將複雜的提示拆解為子區段，並一次撰寫一個部分。對於每項任務，您可能會發現依循逐步流程來定義 Claude 妥善完成任務所需的提示部分會更加成功。在這個汽車保險客戶支援範例中，您將從「問候與一般指引」任務開始，逐一撰寫提示的所有部分。這也讓您的提示更容易除錯，因為您可以更快速地調整整體提示的個別部分。

```python
STATIC_GREETINGS_AND_GENERAL = """
<static_context>
Acme Auto Insurance: Your Trusted Companion on the Road

About:
At Acme Insurance, we understand that your vehicle is more than just a mode of transportation—it's your ticket to life's adventures.
Since 1985, we've been crafting auto insurance policies that give drivers the confidence to explore, commute, and travel with peace of mind.
Whether you're navigating city streets or embarking on cross-country road trips, Acme is there to protect you and your vehicle.
Our innovative auto insurance policies are designed to adapt to your unique needs, covering everything from fender benders to major collisions.
With Acme's award-winning customer service and swift claim resolution, you can focus on the joy of driving while we handle the rest.
We're not just an insurance provider—we're your co-pilot in life's journeys.
Choose Acme Auto Insurance and experience the assurance that comes with superior coverage and genuine care. Because at Acme, we don't just
insure your car—we fuel your adventures on the open road.

Note: We also offer specialized coverage for electric vehicles, ensuring that drivers of all car types can benefit from our protection.

Acme Insurance offers the following products:
- Car insurance
- Electric car insurance
- Two-wheeler insurance

Business hours: Monday-Friday, 9 AM - 5 PM EST
Customer service number: 1-800-123-4567
</static_context>
"""
```

接著對您的汽車保險和電動車保險資訊進行相同的處理。

```python
STATIC_CAR_INSURANCE = """
<static_context>
Car Insurance Coverage:
Acme's car insurance policies typically cover:
1. Liability coverage: Pays for bodily injury and property damage you cause to others.
2. Collision coverage: Pays for damage to your car in an accident.
3. Comprehensive coverage: Pays for damage to your car from non-collision incidents.
4. Medical payments coverage: Pays for medical expenses after an accident.
5. Uninsured/underinsured motorist coverage: Protects you if you're hit by a driver with insufficient insurance.

Optional coverages include:
- Rental car reimbursement
- Roadside assistance
- New car replacement
</static_context>
"""

STATIC_ELECTRIC_CAR_INSURANCE = """
<static_context>
Electric Car Insurance:
Our specialized electric car insurance goes beyond traditional auto coverage,
offering tailored protection for your high-tech investment. In addition to standard coverages, we offer:
- Safeguarding expensive battery systems
- Covering home charging equipment
- Adapting to the rapidly evolving world of electric vehicles

Whether you're driving a Rivian, Nissan Leaf, Chevrolet Bolt,
or any other electric vehicle, our knowledgeable agents are ready to craft a policy
that gives you the confidence to enjoy every electron-powered mile.
</static_context>
"""
```

現在您已有了靜態內容，請加入至少 4-5 個「良好」互動範例來引導 Claude 的回應。這些範例應能代表您理想的客戶互動，並可包含防護機制和工具呼叫等要素。

```python
EXAMPLES = """
Here are a few examples of how you can interact with customers:

<example 1>
H: Hi, do you offer commercial insurance for small businesses?

A: Ah! Unfortunately, we don't offer commercial insurance at this time.
However, we do provide a range of personal insurance products including car insurance and electric car insurance.
Would you like to know more about these offerings?
</example 1>

<example 2>
H: Hi, what are your business hours?

A: Our business hours are Monday to Friday, 9 AM to 5 PM EST.
If you have any questions or need assistance, feel free to reach out during those times.
How else can I assist you today?
</example 2>

<example 3>
H: I'm shopping for car insurance. What does Acme offer?

A: Great choice! Acme's car insurance policies typically include:
- Liability coverage: Protects you if you're at fault in an accident
- Collision coverage: Pays for damage to your car in an accident
- Comprehensive coverage: Covers non-collision incidents like theft or vandalism
- Medical payments coverage: Pays for medical expenses after an accident
- Uninsured/underinsured motorist coverage: Protects you from drivers with insufficient insurance

We also offer optional coverages like rental car reimbursement, roadside assistance, and new car replacement.
Would you like more details on any of these coverages, or do you have specific questions about insuring your vehicle?
</example 3>

<example 4>
H: I'd like to hear about your electric car insurance.

A: Certainly! Our electric car insurance is specially designed to meet the unique needs of electric vehicle owners.
In addition to all the standard coverages we offer for regular cars, our electric car policies include:

- Battery coverage: Protection against damage or degradation of your vehicle's expensive battery system
- Tech package: Covers repairs or replacement of advanced electronics and onboard computer systems
- Charging station coverage: Protects both home and portable charging equipment
- Cybersecurity protection: Coverage against hacking or digital threats
- Specialized roadside assistance: Includes flatbed towing to protect the vehicle's undercarriage

We cover a wide range of electric vehicles, including popular models from Tesla, Rivian, Nissan, Chevrolet, and more.
Is there a specific electric vehicle you're looking to insure, or would you like more details on any of these coverages?
</example 4>

<example 5>
H: I'd like to get a quote for my car insurance.

A: Certainly! I'd be happy to help you get a quote for your car insurance.
To provide you with an accurate quote, I'll need to collect some information about your vehicle and the primary driver.
Let's start with the basics:

1. What is the make and model of your vehicle?
2. What year was it manufactured?
3. Approximately how many miles have you driven?
4. What is the age of the primary driver?

Once you provide this information, I'll use our quoting tool to generate a personalized insurance quote for you.
</example 5>
"""
```

您還需要加入任何重要的指令，概述 Claude 與客戶互動時應該做和不應該做的事項。 這些可能源自品牌防護機制或支援政策。

```python
ADDITIONAL_GUARDRAILS = """Please adhere to the following guardrails:
1. Only provide information about insurance types listed in our offerings.
2. If asked about an insurance type we don't offer, politely state
that we don't provide that service.
3. Do not speculate about future product offerings or company plans.
4. Don't make promises or enter into agreements it's not authorized to make.
You only provide information and guidance.
5. Do not mention any competitor's products or services.
"""
```

現在將所有這些區段合併為單一字串，作為您的提示使用。

```python
TASK_SPECIFIC_INSTRUCTIONS = " ".join(
    [
        STATIC_GREETINGS_AND_GENERAL,
        STATIC_CAR_INSURANCE,
        STATIC_ELECTRIC_CAR_INSURANCE,
        EXAMPLES,
        ADDITIONAL_GUARDRAILS,
    ]
)
```

### 透過工具使用加入動態和代理能力

Claude 能夠使用用戶端「tool use」（工具使用）功能動態地採取行動和檢索資訊。首先列出提示應使用的任何外部工具或 API。

在此範例中，先從一個用於計算報價的工具開始。

<Tip>
  提醒您，此工具不會執行實際的計算，它只會向應用程式發出訊號，表示應使用某個工具並附帶所指定的任何引數。
</Tip>

將模型名稱、工具定義和一個 stub 實作加入 `config.py`：

```python
import time

MODEL = "claude-opus-5-5"

TOOLS = [
    {
        "name": "get_quote",
        "description": "Calculate the insurance quote based on user input. Returned value is per month premium.",
        "input_schema": {
            "type": "object",
            "properties": {
                "make": {"type": "string", "description": "The make of the vehicle."},
                "model": {"type": "string", "description": "The model of the vehicle."},
                "year": {
                    "type": "integer",
                    "description": "The year the vehicle was manufactured.",
                },
                "mileage": {
                    "type": "integer",
                    "description": "The mileage on the vehicle.",
                },
                "driver_age": {
                    "type": "integer",
                    "description": "The age of the primary driver.",
                },
            },
            "required": ["make", "model", "year", "mileage", "driver_age"],
        },
    }
]


def get_quote(make, model, year, mileage, driver_age):
    """Returns the premium per month in USD"""
    # 您可以呼叫 HTTP 端點或查詢資料庫來取得報價。
    # 此處模擬 1 秒的延遲，並回傳固定報價 100。
    time.sleep(1)
    return 100
```

### 部署您的提示

如果不將提示部署到測試生產環境並[執行評估](https://platform.claude.com/docs/zh-TW/test-and-evaluate/develop-tests)，就很難知道您的提示效果如何。使用該提示、Anthropic SDK 和 Streamlit 作為使用者介面來建構一個小型應用程式。

在名為 `chatbot.py` 的檔案中（或您所用語言中的對等模組），設定 ChatBot 類別，它將封裝與 Anthropic SDK 的互動。

該類別應有兩個主要方法：一個呼叫 API 以產生訊息，另一個處理每個傳入的使用者輸入。

<CodeGroup exclude="shell">
  ```python Python
  # 在您的 chatbot.py 中，從您上面編寫的 config.py 匯入這些項目：
  # from config import IDENTITY, TOOLS, MODEL, get_quote
  from anthropic import Anthropic
  from dotenv import load_dotenv

  load_dotenv()


  class ChatBot:
      def __init__(self, session_state):
          self.anthropic = Anthropic()
          self.session_state = session_state

      def generate_message(
          self,
          messages,
          max_tokens,
      ):
          try:
              response = self.anthropic.messages.create(
                  model=MODEL,
                  system=IDENTITY,
                  max_tokens=max_tokens,
                  messages=messages,
                  tools=TOOLS,
              )
              return response
          except Exception as e:
              return {"error": str(e)}

      def process_user_input(self, user_input):
          self.session_state.messages.append({"role": "user", "content": user_input})

          response_message = self.generate_message(
              messages=self.session_state.messages,
              max_tokens=2048,
          )

          if "error" in response_message:
              return f"An error occurred: {response_message['error']}"

          if response_message.content[-1].type == "tool_use":
              tool_use = response_message.content[-1]
              func_name = tool_use.name
              func_params = tool_use.input
              tool_use_id = tool_use.id

              result = self.handle_tool_use(func_name, func_params)
              self.session_state.messages.append(
                  {"role": "assistant", "content": response_message.content}
              )
              self.session_state.messages.append(
                  {
                      "role": "user",
                      "content": [
                          {
                              "type": "tool_result",
                              "tool_use_id": tool_use_id,
                              "content": f"{result}",
                          }
                      ],
                  }
              )

              follow_up_response = self.generate_message(
                  messages=self.session_state.messages,
                  max_tokens=2048,
              )

              if "error" in follow_up_response:
                  return f"An error occurred: {follow_up_response['error']}"

              response_text = next(
                  (
                      block.text
                      for block in follow_up_response.content
                      if block.type == "text"
                  ),
                  None,
              )
              if response_text is None:
                  raise Exception("An error occurred: Unexpected response type")
              self.session_state.messages.append(
                  {"role": "assistant", "content": response_text}
              )
              return response_text

          text_block = next(
              (block for block in response_message.content if block.type == "text"), None
          )
          if text_block is not None:
              response_text = text_block.text
              self.session_state.messages.append(
                  {"role": "assistant", "content": response_text}
              )
              return response_text

          raise Exception("An error occurred: Unexpected response type")

      def handle_tool_use(self, func_name, func_params):
          if func_name == "get_quote":
              premium = get_quote(**func_params)
              return f"Quote generated: ${premium:.2f} per month"

          raise Exception("An unexpected tool was used")
  ```

  ```typescript TypeScript
  import Anthropic from "@anthropic-ai/sdk";

  class ChatBot {
    // IDENTITY、MODEL、TOOLS 和 getQuote 對應本指南前面
    // 定義的 config.py 值（以 Python 呈現）。
    readonly anthropic = new Anthropic();
    readonly messages: Anthropic.MessageParam[] = [];

    async generateMessage(
      messages: Anthropic.MessageParam[],
      maxTokens: number
    ): Promise<Anthropic.Message> {
      return this.anthropic.messages.create({
        model: MODEL,
        system: IDENTITY,
        max_tokens: maxTokens,
        messages,
        tools: TOOLS
      });
    }

    async processUserInput(userInput: string): Promise<string> {
      this.messages.push({ role: "user", content: userInput });

      const responseMessage = await this.generateMessage(this.messages, 2048);

      const lastBlock = responseMessage.content.at(-1);
      if (lastBlock?.type === "tool_use") {
        const toolResult = this.handleToolUse(lastBlock.name, lastBlock.input);

        this.messages.push({ role: "assistant", content: responseMessage.content });
        this.messages.push({
          role: "user",
          content: [{ type: "tool_result", tool_use_id: lastBlock.id, content: toolResult }]
        });

        const followUpResponse = await this.generateMessage(this.messages, 2048);

        const followUpBlock = followUpResponse.content.find((block) => block.type === "text");
        if (!followUpBlock) {
          throw new Error("An error occurred: Unexpected response type");
        }
        this.messages.push({ role: "assistant", content: followUpBlock.text });
        return followUpBlock.text;
      }

      const firstBlock = responseMessage.content.find((block) => block.type === "text");
      if (firstBlock) {
        this.messages.push({ role: "assistant", content: firstBlock.text });
        return firstBlock.text;
      }

      throw new Error("An error occurred: Unexpected response type");
    }

    handleToolUse(toolName: string, toolInput: unknown): string {
      if (toolName === "get_quote") {
        // SDK 將 tool_use.input 的型別定為 unknown；將其縮窄為 get_quote 結構。
        if (
          toolInput === null ||
          typeof toolInput !== "object" ||
          !("make" in toolInput) ||
          typeof toolInput.make !== "string" ||
          !("model" in toolInput) ||
          typeof toolInput.model !== "string" ||
          !("year" in toolInput) ||
          typeof toolInput.year !== "number" ||
          !("mileage" in toolInput) ||
          typeof toolInput.mileage !== "number" ||
          !("driver_age" in toolInput) ||
          typeof toolInput.driver_age !== "number"
        ) {
          throw new Error("An error occurred: Unexpected tool input");
        }
        const { make, model: vehicleModel, year, mileage, driver_age: driverAge } = toolInput;

        const premium = getQuote(make, vehicleModel, year, mileage, driverAge);
        return `Quote generated: $${premium.toFixed(2)} per month`;
      }

      throw new Error("An unexpected tool was used");
    }
  }
  ```

  ```csharp C#
  using System.Text.Json;
  using Anthropic;
  using Anthropic.Models.Messages;

  // Config.Model、Config.Identity、Config.Tools 與 Config.GetQuote 對應
  // 本指南前面定義的 config.py 值（以 Python 呈現）。
  public class ChatBot
  {
      private readonly AnthropicClient _anthropic = new();

      public List<MessageParam> Messages { get; } = [];

      public async Task<Message> GenerateMessage(List<MessageParam> messages, long maxTokens) =>
          await _anthropic.Messages.Create(
              new MessageCreateParams
              {
                  Model = Config.Model,
                  System = Config.Identity,
                  MaxTokens = maxTokens,
                  Messages = messages,
                  Tools = Config.Tools,
              }
          );

      public async Task<string> ProcessUserInput(string userInput)
      {
          Messages.Add(new() { Role = Role.User, Content = userInput });

          var responseMessage = await GenerateMessage(Messages, maxTokens: 2048);

          if (responseMessage.Content[^1].TryPickToolUse(out var toolUse))
          {
              var toolResult = HandleToolUse(toolUse.Name, toolUse.Input);

              Messages.Add(new()
              {
                  Role = Role.Assistant,
                  Content = responseMessage.Content
                      .Select(contentBlock => new ContentBlockParam(contentBlock.Json))
                      .ToList(),
              });
              Messages.Add(new()
              {
                  Role = Role.User,
                  Content = new List<ContentBlockParam>
                  {
                      new ToolResultBlockParam { ToolUseID = toolUse.ID, Content = toolResult },
                  },
              });

              var followUpResponse = await GenerateMessage(Messages, maxTokens: 2048);

              foreach (var block in followUpResponse.Content)
              {
                  if (block.TryPickText(out var followUpText))
                  {
                      Messages.Add(new() { Role = Role.Assistant, Content = followUpText.Text });
                      return followUpText.Text;
                  }
              }

              throw new InvalidOperationException("An error occurred: Unexpected response type");
          }

          foreach (var block in responseMessage.Content)
          {
              if (block.TryPickText(out var textBlock))
              {
                  Messages.Add(new() { Role = Role.Assistant, Content = textBlock.Text });
                  return textBlock.Text;
              }
          }

          throw new InvalidOperationException("An error occurred: Unexpected response type");
      }

      public string HandleToolUse(string funcName, IReadOnlyDictionary<string, JsonElement> funcParams)
      {
          if (funcName == "get_quote")
          {
              var premium = Config.GetQuote(
                  funcParams["make"].GetString()!,
                  funcParams["model"].GetString()!,
                  funcParams["year"].GetInt64(),
                  funcParams["mileage"].GetInt64(),
                  funcParams["driver_age"].GetInt64()
              );
              return $"Quote generated: ${premium:F2} per month";
          }

          throw new ArgumentException("An unexpected tool was used");
      }
  }
  ```

  ```go Go
  import (
  	"context"
  	"encoding/json"
  	"fmt"

  	"github.com/anthropics/anthropic-sdk-go"
  )

  // ChatBot 包裝了 Anthropic 用戶端與對話歷史記錄。
  // 它所使用的 identity、model、tools 與 getQuote 值對應本指南
  // 前面（以 Python 呈現）的 config.py 定義。
  type ChatBot struct {
  	client   anthropic.Client
  	messages []anthropic.MessageParam
  }

  func NewChatBot() *ChatBot {
  	return &ChatBot{client: anthropic.NewClient()}
  }

  func (bot *ChatBot) GenerateMessage(ctx context.Context, messages []anthropic.MessageParam, maxTokens int64) (*anthropic.Message, error) {
  	return bot.client.Messages.New(ctx, anthropic.MessageNewParams{
  		Model:     model,
  		System:    []anthropic.TextBlockParam{{Text: identity}},
  		MaxTokens: maxTokens,
  		Messages:  messages,
  		Tools:     tools,
  	})
  }

  func (bot *ChatBot) ProcessUserInput(ctx context.Context, userInput string) (string, error) {
  	bot.messages = append(bot.messages, anthropic.NewUserMessage(anthropic.NewTextBlock(userInput)))

  	response, err := bot.GenerateMessage(ctx, bot.messages, 2048)
  	if err != nil {
  		return "", err
  	}

  	lastBlock := response.Content[len(response.Content)-1]
  	if toolUse, ok := lastBlock.AsAny().(anthropic.ToolUseBlock); ok {
  		result, err := bot.HandleToolUse(toolUse.Name, toolUse.Input)
  		if err != nil {
  			return "", err
  		}

  		bot.messages = append(bot.messages,
  			response.ToParam(),
  			anthropic.NewUserMessage(anthropic.NewToolResultBlock(toolUse.ID, result, false)),
  		)

  		followUp, err := bot.GenerateMessage(ctx, bot.messages, 2048)
  		if err != nil {
  			return "", err
  		}

  		for _, block := range followUp.Content {
  			if textBlock, ok := block.AsAny().(anthropic.TextBlock); ok {
  				bot.messages = append(bot.messages, anthropic.NewAssistantMessage(anthropic.NewTextBlock(textBlock.Text)))
  				return textBlock.Text, nil
  			}
  		}
  		return "", fmt.Errorf("an error occurred: unexpected response type")
  	}

  	for _, block := range response.Content {
  		if textBlock, ok := block.AsAny().(anthropic.TextBlock); ok {
  			bot.messages = append(bot.messages, anthropic.NewAssistantMessage(anthropic.NewTextBlock(textBlock.Text)))
  			return textBlock.Text, nil
  		}
  	}

  	return "", fmt.Errorf("an error occurred: unexpected response type")
  }

  func (bot *ChatBot) HandleToolUse(toolName string, toolInput json.RawMessage) (string, error) {
  	if toolName != "get_quote" {
  		return "", fmt.Errorf("an unexpected tool was used: %s", toolName)
  	}

  	var input struct {
  		Make      string `json:"make"`
  		Model     string `json:"model"`
  		Year      int    `json:"year"`
  		Mileage   int    `json:"mileage"`
  		DriverAge int    `json:"driver_age"`
  	}
  	if err := json.Unmarshal(toolInput, &input); err != nil {
  		return "", err
  	}
  	premium := getQuote(input.Make, input.Model, input.Year, input.Mileage, input.DriverAge)
  	return fmt.Sprintf("Quote generated: $%.2f per month", premium), nil
  }
  ```

  ```java Java
  import com.anthropic.client.AnthropicClient;
  import com.anthropic.client.okhttp.AnthropicOkHttpClient;
  import com.anthropic.core.JsonValue;
  import com.anthropic.models.messages.ContentBlock;
  import com.anthropic.models.messages.ContentBlockParam;
  import com.anthropic.models.messages.Message;
  import com.anthropic.models.messages.MessageCreateParams;
  import com.anthropic.models.messages.MessageParam;
  import com.anthropic.models.messages.ToolResultBlockParam;
  import com.anthropic.models.messages.ToolUseBlock;

  // IDENTITY、MODEL、TOOLS 與 getQuote 對應本指南前面
  // （以 Python 呈現）在 config.py 中定義的值。
  class ChatBot {
      final AnthropicClient anthropic;
      final List<MessageParam> messages;

      ChatBot() {
          // 從 ANTHROPIC_API_KEY 環境變數讀取 API 金鑰
          this.anthropic = AnthropicOkHttpClient.fromEnv();
          this.messages = new ArrayList<>();
      }

      Message generateMessage(List<MessageParam> messages, long maxTokens) {
          return anthropic.messages().create(MessageCreateParams.builder()
                  .model(MODEL)
                  .system(IDENTITY)
                  .maxTokens(maxTokens)
                  .messages(messages)
                  .tools(TOOLS)
                  .build());
      }

      String processUserInput(String userInput) {
          messages.add(MessageParam.builder()
                  .role(MessageParam.Role.USER)
                  .content(userInput)
                  .build());

          Message responseMessage = generateMessage(messages, 2048);

          List<ContentBlock> content = responseMessage.content();
          ContentBlock lastBlock = content.getLast();
          if (lastBlock.isToolUse()) {
              ToolUseBlock toolUse = lastBlock.asToolUse();
              Map<String, JsonValue> toolInput =
                      (Map<String, JsonValue>) toolUse._input().asObject().orElseThrow();
              String result = handleToolUse(toolUse.name(), toolInput);

              messages.add(MessageParam.builder()
                      .role(MessageParam.Role.ASSISTANT)
                      .contentOfBlockParams(content.stream().map(ContentBlock::toParam).toList())
                      .build());
              messages.add(MessageParam.builder()
                      .role(MessageParam.Role.USER)
                      .contentOfBlockParams(List.of(ContentBlockParam.ofToolResult(
                              ToolResultBlockParam.builder()
                                      .toolUseId(toolUse.id())
                                      .content(result)
                                      .build())))
                      .build());

              Message followUpResponse = generateMessage(messages, 2048);

              ContentBlock followUpBlock = followUpResponse.content().stream()
                      .filter(ContentBlock::isText)
                      .findFirst()
                      .orElseThrow(() -> new IllegalStateException("An error occurred: Unexpected response type"));
              String responseText = followUpBlock.asText().text();
              messages.add(MessageParam.builder()
                      .role(MessageParam.Role.ASSISTANT)
                      .content(responseText)
                      .build());
              return responseText;
          } else {
              ContentBlock textBlock = content.stream()
                      .filter(ContentBlock::isText)
                      .findFirst()
                      .orElseThrow(() -> new IllegalStateException("An error occurred: Unexpected response type"));
              String responseText = textBlock.asText().text();
              messages.add(MessageParam.builder()
                      .role(MessageParam.Role.ASSISTANT)
                      .content(responseText)
                      .build());
              return responseText;
          }
      }

      String handleToolUse(String funcName, Map<String, JsonValue> funcParams) {
          return switch (funcName) {
              case "get_quote" -> {
                  double premium = getQuote(
                          funcParams.get("make").asStringOrThrow(),
                          funcParams.get("model").asStringOrThrow(),
                          ((Number) funcParams.get("year").asNumber().orElseThrow()).longValue(),
                          ((Number) funcParams.get("mileage").asNumber().orElseThrow()).longValue(),
                          ((Number) funcParams.get("driver_age").asNumber().orElseThrow()).longValue());
                  yield "Quote generated: $%.2f per month".formatted(premium);
              }
              default -> throw new IllegalArgumentException("An unexpected tool was used");
          };
      }
  }
  ```

  ```php PHP
  use Anthropic\Client;
  use Anthropic\Messages\Message;
  use Anthropic\Messages\MessageParam;
  use Anthropic\Messages\TextBlock;
  use Anthropic\Messages\ToolResultBlockParam;
  use Anthropic\Messages\ToolUseBlock;

  class ChatBot
  {
      // MODEL、IDENTITY、TOOLS 與 get_quote() 對應本指南前文
      // （以 Python 呈現）所定義的 config.py 值。

      /** @var list<MessageParam> */
      public private(set) array $messages = [];

      public function __construct(
          private readonly Client $anthropic = new Client(),
      ) {}

      /**
       * @param list<MessageParam> $messages
       */
      public function generateMessage(array $messages, int $maxTokens): Message
      {
          return $this->anthropic->messages->create(
              model: MODEL,
              system: IDENTITY,
              maxTokens: $maxTokens,
              messages: $messages,
              tools: TOOLS,
          );
      }

      public function processUserInput(string $userInput): string
      {
          $this->messages[] = MessageParam::with(role: 'user', content: $userInput);

          $responseMessage = $this->generateMessage($this->messages, maxTokens: 2048);

          $content = $responseMessage->content;
          $lastBlock = array_last($content);

          if ($lastBlock instanceof ToolUseBlock) {
              $toolResult = $this->handleToolUse($lastBlock->name, $lastBlock->input);

              $this->messages[] = MessageParam::with(role: 'assistant', content: $content);
              $this->messages[] = MessageParam::with(
                  role: 'user',
                  content: [
                      ToolResultBlockParam::with(toolUseID: $lastBlock->id, content: $toolResult),
                  ],
              );

              $followUpResponse = $this->generateMessage($this->messages, maxTokens: 2048);

              $firstBlock = array_find(
                  $followUpResponse->content,
                  static fn ($block): bool => $block instanceof TextBlock,
              );
              if (!$firstBlock instanceof TextBlock) {
                  throw new \RuntimeException('An error occurred: Unexpected response type');
              }

              $this->messages[] = MessageParam::with(role: 'assistant', content: $firstBlock->text);

              return $firstBlock->text;
          }

          $firstBlock = array_find($content, static fn ($block): bool => $block instanceof TextBlock);
          if ($firstBlock instanceof TextBlock) {
              $this->messages[] = MessageParam::with(role: 'assistant', content: $firstBlock->text);

              return $firstBlock->text;
          }

          throw new \RuntimeException('An error occurred: Unexpected response type');
      }

      /**
       * @param array<string, mixed> $funcParams
       */
      private function handleToolUse(string $funcName, array $funcParams): string
      {
          if ($funcName === 'get_quote') {
              $premium = get_quote(...$funcParams);

              return sprintf('Quote generated: $%.2f per month', $premium);
          }

          throw new \RuntimeException('An unexpected tool was used');
      }
  }
  ```

  ```ruby Ruby
  # IDENTITY、MODEL、TOOLS 和 get_quote 對應本指南前面
  # 定義的 config.py 值（以 Python 呈現）。
  require "anthropic"

  class ChatBot
    attr_reader :messages

    def initialize
      @anthropic = Anthropic::Client.new
      @messages = []
    end

    def generate_message(messages, max_tokens)
      @anthropic.messages.create(
        model: MODEL,
        system_: IDENTITY,
        max_tokens:,
        messages:,
        tools: TOOLS
      )
    end

    def process_user_input(user_input)
      @messages << {role: "user", content: user_input}

      response_message = generate_message(@messages, 2048)

      case response_message.content
      in [*, Anthropic::ToolUseBlock => tool_use]
        result = handle_tool_use(tool_use.name, tool_use.input)
        @messages << {role: "assistant", content: response_message.content}
        @messages << {
          role: "user",
          content: [{type: "tool_result", tool_use_id: tool_use.id, content: result}]
        }

        follow_up_response = generate_message(@messages, 2048)

        case follow_up_response.content
        in [*, Anthropic::TextBlock => text_block, *]
          @messages << {role: "assistant", content: text_block.text}
          text_block.text
        else
          raise "An error occurred: Unexpected response type"
        end
      in [*, Anthropic::TextBlock => text_block, *]
        @messages << {role: "assistant", content: text_block.text}
        text_block.text
      else
        raise "An error occurred: Unexpected response type"
      end
    end

    def handle_tool_use(tool_name, tool_input)
      raise "An unexpected tool was used" unless tool_name == "get_quote"

      premium = get_quote(**tool_input)
      format("Quote generated: $%.2f per month", premium)
    end
  end
  ```
</CodeGroup>

### 建構您的使用者介面

使用 main 方法透過 Streamlit 測試部署此程式碼。這個 `main()` 函式會設定一個基於 Streamlit 的聊天介面。Streamlit 是一個 Python 框架，因此本教學的這一部分僅以 Python 呈現；上方的 ChatBot 類別則是您可以移植到任何語言的部分。

在名為 `app.py` 的檔案中執行此操作

```python
import streamlit as st
from chatbot import ChatBot
from config import TASK_SPECIFIC_INSTRUCTIONS


def main():
    st.title("Chat with Eva, Acme Insurance Company's Assistant🤖")

    if "messages" not in st.session_state:
        st.session_state.messages = [
            {"role": "user", "content": TASK_SPECIFIC_INSTRUCTIONS},
            {"role": "assistant", "content": "Understood"},
        ]

    chatbot = ChatBot(st.session_state)

    # 顯示使用者與助理訊息，略過前兩則
    for message in st.session_state.messages[2:]:
        # 忽略工具使用區塊
        if isinstance(message["content"], str):
            with st.chat_message(message["role"]):
                st.markdown(message["content"])

    if user_msg := st.chat_input("Type your message here..."):
        st.chat_message("user").markdown(user_msg)

        with st.chat_message("assistant"):
            with st.spinner("Eva is thinking..."):
                response_placeholder = st.empty()
                full_response = chatbot.process_user_input(user_msg)
                response_placeholder.markdown(full_response)


if __name__ == "__main__":
    main()
```

使用以下指令執行程式：

```bash
streamlit run app.py
```

### 評估您的提示

提示通常需要經過測試和最佳化才能達到生產就緒狀態。要確定您解決方案的就緒程度，請使用結合量化和質化方法的系統化流程來評估聊天機器人的效能。根據您定義的成功標準建立[強大的實證評估](https://platform.claude.com/docs/zh-TW/test-and-evaluate/develop-tests#build-evaluations)，將使您能夠最佳化您的提示。

### 提升效能

在複雜的情境中，除了標準的[提示工程技術](https://platform.claude.com/docs/zh-TW/build-with-claude/prompt-engineering/overview)和[防護機制實作策略](https://platform.claude.com/docs/zh-TW/test-and-evaluate/strengthen-guardrails/reduce-hallucinations)之外，考慮其他策略來提升效能可能會有所幫助。以下是一些常見情境：

#### 使用 RAG 降低長上下文延遲

在處理大量靜態和動態上下文時，將所有資訊納入提示可能導致高成本、較慢的回應時間，以及達到「context window」（上下文視窗）限制。在這種情境下，實作「Retrieval Augmented Generation」（檢索增強生成），即 RAG 技術可以提升效能和效率。

您可以透過使用[嵌入模型](https://platform.claude.com/docs/zh-TW/build-with-claude/embeddings)（例如 MongoDB 旗下 Voyage AI 的模型）將資訊轉換為向量表示，來建立更具可擴展性和回應性的系統。這種方法允許根據當前查詢動態檢索相關資訊，而不是在每個提示中包含所有可能的上下文。

在支援使用案例中實作 RAG 已被證明可以在具有大量上下文需求的系統中提高準確度、縮短回應時間並降低 API 成本。請參閱 [RAG 範例](https://platform.claude.com/cookbook/capabilities-retrieval-augmented-generation-guide)以取得實作範例。

#### 透過工具使用整合即時資料

在處理需要即時資訊的查詢時，例如帳戶餘額或保單細節，基於嵌入的 RAG 方法並不足夠。相反地，工具使用可以增強您的聊天機器人提供準確、即時回應的能力。例如，您可以使用工具使用來查詢客戶資訊、檢索訂單細節，以及代表客戶取消訂單。

這種方法[在工具使用：客戶服務代理範例中有所概述](https://platform.claude.com/cookbook/tool-use-customer-service-agent)，讓您能將即時資料整合到 Claude 的回應中，並提供更個人化、更有效率的客戶體驗。

#### 強化輸入和輸出防護機制

在部署聊天機器人時，尤其是在客戶服務情境中，防範與濫用、超出範圍的查詢和不當回應相關的風險非常重要。雖然 Claude 本身對此類情境具有韌性，但以下是強化聊天機器人防護機制的額外步驟：

* [減少幻覺](https://platform.claude.com/docs/zh-TW/test-and-evaluate/strengthen-guardrails/reduce-hallucinations)：實作事實查核機制和[引用](https://platform.claude.com/cookbook/misc-using-citations)，使回應以所提供的資訊為依據。
* 交叉核對資訊：驗證代理的回應是否符合您公司的政策和已知事實。
* 避免契約承諾：確保代理不會做出其未獲授權的承諾或簽訂協議。
* [緩解越獄攻擊](https://platform.claude.com/docs/zh-TW/test-and-evaluate/strengthen-guardrails/mitigate-jailbreaks)：使用無害性篩選和輸入驗證等方法，防止使用者利用模型漏洞以產生不當內容。
* 避免提及競爭對手：實作競爭對手提及過濾器，以維持品牌聚焦，不提及任何競爭對手的產品或服務。
* [提高輸出一致性](https://platform.claude.com/docs/zh-TW/test-and-evaluate/strengthen-guardrails/increase-consistency)：防止 Claude 改變風格或偏離角色，即使在冗長、複雜的互動中也是如此。
* 移除個人識別資訊（PII）：除非明確要求並獲得授權，否則從回應中移除任何 PII。

#### 透過串流縮短感知回應時間

在處理可能較長的回應時，實作「streaming」（串流）可以提升使用者參與度和滿意度。在這種情境下，使用者會逐步收到答案，而不是等待整個回應產生完畢。

以下是實作串流的方法：

1. 使用 [Anthropic Streaming API](https://platform.claude.com/docs/zh-TW/build-with-claude/streaming) 來支援串流回應。
2. 設定您的前端以處理傳入的文字區塊。
3. 在每個區塊到達時顯示它，模擬即時打字。
4. 實作一個儲存完整回應的機制，讓使用者在離開後返回時仍可檢視。

在某些情況下，串流使得能夠使用基礎延遲較高的更進階模型，因為逐步顯示可減輕較長處理時間的影響。

#### 擴展您的聊天機器人

隨著聊天機器人複雜度的增加，您的應用程式架構可以隨之演進。在為架構增加更多層級之前，請考慮以下較不費力的選項：

* 確保您充分利用您的提示，並透過提示工程進行最佳化。使用[提示工程指南](https://platform.claude.com/docs/zh-TW/build-with-claude/prompt-engineering/overview)來撰寫最有效的提示。
* 在提示中加入額外的[工具](https://platform.claude.com/docs/zh-TW/agents-and-tools/tool-use/overview)（可包含[提示鏈](https://platform.claude.com/docs/zh-TW/build-with-claude/prompt-engineering/claude-prompting-best-practices#chain-complex-prompts)），看看是否能達成所需的功能。

如果您的聊天機器人處理極為多樣的任務，您可能需要考慮加入一個[獨立的意圖分類器](https://platform.claude.com/cookbook/capabilities-classification-guide)來路由初始的客戶查詢。對於現有的應用程式，這將涉及建立一個決策樹，將客戶查詢透過分類器路由，然後導向專門的對話（各自擁有一組工具和系統提示）。請注意，此方法需要額外呼叫一次 Claude，可能會增加延遲。

### 將 Claude 整合到您的支援工作流程中

雖然這些範例著重於可在 Streamlit 環境中呼叫的 Python 函式，但部署 Claude 作為即時支援聊天機器人需要一個 API 服務。

以下是您可以採取的方法：

1. 建立 API 包裝器：圍繞您的分類函式開發一個簡單的 API 包裝器。例如，您可以使用 Flask API 或 Fast API 將您的程式碼包裝成 HTTP 服務。您的 HTTP 服務可以接受使用者輸入並完整回傳 Assistant 回應。因此，您的服務可以具備以下特性：

   * Server-Sent Events（SSE）：SSE 允許從伺服器到用戶端即時串流回應。這在使用 LLM 時提供了流暢、互動的體驗。
   * 快取：實作快取可以縮短回應時間並減少不必要的 API 呼叫。
   * 上下文保留：在使用者離開後返回時維持上下文，對於對話的連續性非常重要。

2. 建構網頁介面：實作一個使用者友善的網頁 UI，用於與由 Claude 驅動的代理互動。

## 後續步驟

<CardGroup cols={2}>
  <Card title="工具使用" icon="wrench" href="https://platform.claude.com/docs/zh-TW/agents-and-tools/tool-use/overview">
    讓 Claude 存取您的 API，使其能夠代表客戶採取行動。
  </Card>

  <Card title="開發測試" icon="check" href="https://platform.claude.com/docs/zh-TW/test-and-evaluate/develop-tests">
    建構評估，以根據您定義的成功標準衡量您的支援代理。
  </Card>

  <Card title="串流" icon="bolt" href="https://platform.claude.com/docs/zh-TW/build-with-claude/streaming">
    串流回應，讓客戶在答案產生時即可看到。
  </Card>

  <Card title="提示工程" icon="lightbulb" href="https://platform.claude.com/docs/zh-TW/build-with-claude/prompt-engineering/overview">
    精進您的系統提示和範例，以獲得更好的任務效能。
  </Card>
</CardGroup>
