AG Day 4 Prompt 設計:角色、少樣本與輸出約束
執行需求:CPU+API key。在上一篇 AG Day 3(原文連結)中,我們深入拆解了對話 API 的四種角色職責,並實作了能夠維護工具呼叫原子性完整度的滑動視窗歷史修剪器。然而,空有穩定運行的通訊管線,並不代表大型語言模型就能自動給出專業、嚴謹且符合生產標準的研究決策。在開放式的對話情境中,模型極容易給出空泛的客套話、跳過關鍵的查證步驟,甚至在面對不確定的數據時產生嚴重的幻覺(Hallucination)。今天,我們要將工程視角轉向決定 Agent 智力水準的核心指引系統——Prompt(提示詞)架構工程。我們將探討如何透過明確的專家角色定位、結構化少樣本示範(Few-shot Demonstrations)以及強制的輸出邊界約束,打造出一套高度可預測、自律且杜絕胡編亂造的研究提示詞引擎。
引言
在許多初學者的觀念中,提示詞工程往往被簡化為「寫一段文字給模型看」。但在企業級 Agent 的開發實踐中,Prompt 是軟體架構不可分割的設定規格書。如果說 Python 程式碼定義了系統的狀態機與資料管線,那麼 Prompt 則定義了模型在狀態機中的推理準則與決策合約。一個粗糙的提示詞會導致模型在呼叫工具時參數格式混亂、遺漏必要的搜尋關鍵字,或是在資料不足時擅自腦補結論,使整條研究管線產出錯誤的資訊。
為了使大型語言模型能夠穩定扮演「全自動研究助理(research-agent)」的大腦,我們必須將提示詞結構工程化。這包含三大核心維度:第一,精準的角色定位(Persona),劃定其知識範圍、專業素養與思考原則;第二,上下文少樣本學習(In-Context Few-shot Learning),透過完整的「思考(Thought)—行動(Action)—觀察(Observation)」範例軌跡,向模型直觀展示高水準的推理範式;第三,嚴格的輸出約束與負向禁止條款(Negative Constraints),明確宣告哪些動作是絕對禁止的。本篇將完整實作模組化的提示詞工廠,支援動態載入環境變數 RESEARCH_AGENT_MODEL,並提供 --dry-run 離線模擬與輸出合約解析器,確保所有推理產出皆可被程式精確捕獲與檢驗。
原理/觀念
角色定位(Persona & Role)的工程本質
在提示詞中設定角色,並非只是為了讓回答聽起來像某個專家,而是利用大型語言模型在預訓練階段所建立的語意關聯叢集。當我們明確宣告「你是一位專注於前瞻硬體架構與供應鏈的研究分析師」時,模型內部對應於「嚴謹、客觀、量化指標、交叉驗證、風險警示」等語意維度的權重會被顯著激活,進而大幅壓低口語化、情緒化或非正式文本的生成機率。
在設計 research-agent 的角色時,我們需要明確賦予以下四項專業準則:
- 客觀求證導向:面對任何研究命題,優先考慮如何驗證事實,而非憑空發表主觀評論。
- 工具意識(Tool-Awareness):清晰認識自身的知識截斷點與局限性,在需要具體數據、最新技術規範或即時事件時,主動規劃工具呼叫。
- 批判性思維:不盲目相信單一檢索來源,對相互矛盾的資訊保持懷疑並進行交叉比對。
- 合規與成本意識:涉及模型計費或商業定價時,一律宣告「以官方文件為準」,絕不自創未經查證的數據。
少樣本示範(Few-shot Learning)在代理系統中的價值
為什麼零樣本(Zero-shot)在複雜推理任務中容易失效?因為模型雖然「知道」如何推理,但它並不清楚當前系統「期望」它以何種節奏展開行動。少樣本示範的核心價值在於提供「示範軌跡(Demonstration Trajectory)」。
透過向模型展示 1 到 2 組包含完整思考鏈(Chain of Thought)的範例,模型能夠清晰學會:
- 收到模糊問題時,第一步應該如何將其拆解為可檢索的子關鍵字。
- 呼叫工具時,參數應如何精準組織,避免傳入過寬泛的字串。
- 當工具回傳結果不完整時,如何發起第二次補充檢索,而不是直接放棄。
- 在綜合所有素材後,如何產出帶有清晰引用標記的摘要。
負向約束(Negative Constraints)與防禦性工程
「正面引導模型該做什麼」固然重要,但「負面禁止模型做什麼」往往是避免系統翻車的關鍵防線。大型語言模型在面對知識盲區時,天然存在討好使用者的傾向,進而產生「無中生有」的幻覺。因此,我們在系統提示詞中必須建立鋼鐵般的防禦性條款:
# research-agent/negative_constraints.py
# 核心負向條款規範展示
NEGATIVE_RULES = [
"嚴禁猜測未檢索到的數據:若內部知識庫或搜尋工具查無資料,必須如實承認並請求補充資訊。",
"嚴禁捏造不存在的工具名稱:只能呼叫系統明確宣告並提供的工具清單。",
"嚴禁執行未獲授權的外部連線或危險本機指令。",
"嚴禁在缺乏官方來源的情況下記錄價格:若涉及模型費用,一律宣告以官方文件為準。"
]
這種負向條款能有效壓制模型的隨意性,為後續的自動化迴圈建立起安全護欄。
完整實作
接下來,我們將在 research-agent 專案中實作完整的 Prompt 引擎模組。本實作包含少樣本示範管理器、提示詞動態組裝工廠,以及對模型思考內容進行契約檢驗的解析器。
第一步:建立少樣本示範的資料結構與標準示範集 src/research_agent/fewshot.py:
# research-agent/src/research_agent/fewshot.py
from dataclasses import dataclass
from typing import List, Dict, Any
@dataclass
class AgentExemplar:
"""單一完整的少樣本思考與動作軌跡示範"""
task: str
thought_1: str
action_1: str
tool_args_1: Dict[str, Any]
observation_1: str
thought_2: str
final_output: str
def format_to_prompt_text(self) -> str:
"""將示範案例格式化為提示詞中的文字區塊"""
return f"""【範例任務】:{self.task}
[思考步驟 1]:{self.thought_1}
[執行動作 1]:呼叫工具 {self.action_1},參數:{self.tool_args_1}
[工具觀察 1]:{self.observation_1}
[思考步驟 2]:{self.thought_2}
[最終結論]:{self.final_output}
"""
def get_standard_research_exemplars() -> List[AgentExemplar]:
"""取得預設的高品質研究助理少樣本範例"""
ex1 = AgentExemplar(
task="調查 2026 年邊緣晶片的高頻寬記憶體整合趨勢。",
thought_1="這項任務需要具體的封裝技術與頻寬指標,我不能憑空猜測,必須先查詢硬體規格庫。",
action_1="database_lookup",
tool_args_1={"keyword": "高頻寬記憶體 封裝"},
observation_1="檢索結果:2026 主流晶片普遍採 2.5D 先進封裝,頻寬提升至 1.2 TB/s,延遲降低 25%。",
thought_2="已取得明確規格與效能提升指標,數據完整,可以組織具備引用來源的研究總結。",
final_output="根據檢索資料,2026 年邊緣晶片透過 2.5D 先進封裝技術,成功將頻寬提升至 1.2 TB/s,延遲降低 25%,顯著突破記憶體牆限制。"
)
return [ex1]
第二步:建立提示詞規格定義與動態組裝引擎 src/research_agent/prompt_engine.py。我們確保環境變數 RESEARCH_AGENT_MODEL 能夠靈活注入,且文字替換過程中具備嚴格的跳脫保護:
# research-agent/src/research_agent/prompt_engine.py
import os
from typing import List
from research_agent.fewshot import AgentExemplar, get_standard_research_exemplars
SYSTEM_IDENTITY = """你是由頂尖研發團隊構建的高階 AI 研究助理(Research Agent)。
你的決策模型變數為:{model_name}。
你負責協助使用者進行精準、客觀且具備高可信度的產業與技術調研。"""
EXECUTION_GUIDELINES = """【工作準則與思考框架】:
1. 思考優先:在採取任何行動前,必須先在 <thought>...</thought> 區塊中進行邏輯推演。
2. 動作規範:若需要資訊,請在 <action>...</action> 區塊中指明工具名稱與 JSON 格式引數。
3. 終止條件:若資訊已經足夠,動作請輸出 "finish",並在 <final_answer>...</final_answer> 輸出成果。
4. 引用原則:所有結論必須基於工具回傳的事實;提及模型計費與價格時,一律註明以官方文件為準。"""
NEGATIVE_CONSTRAINTS = """【負向禁止事項】:
- 絕對禁止在未取得檢索依據前大膽臆測或虛構數據。
- 絕對禁止捏造未在清單中的工具名稱。
- 絕對禁止執行危險操作或輸出未經查核的推論。"""
class ResearchPromptBuilder:
"""研究代理提示詞動態建構器"""
def __init__(self, model_name: str | None = None):
self.model_name = model_name or os.getenv("RESEARCH_AGENT_MODEL", "mock-agent-v1")
self.exemplars = get_standard_research_exemplars()
def build_system_prompt(self) -> str:
"""組裝完整的系統提示詞文本"""
identity_part = SYSTEM_IDENTITY.format(model_name=self.model_name)
exemplar_texts = "\n---\n".join([ex.format_to_prompt_text() for ex in self.exemplars])
fewshot_part = f"【標準推理示範(Few-shot Demonstrations)】:\n{exemplar_texts}"
sections = [
identity_part,
EXECUTION_GUIDELINES,
NEGATIVE_CONSTRAINTS,
fewshot_part
]
return "\n\n".join(sections)
第三步:實作模型輸出解析與合約檢驗器 src/research_agent/output_parser.py。透過正規表示式解析標籤結構,若模型未遵從契約則即時報錯:
# research-agent/src/research_agent/output_parser.py
import re
import json
from dataclasses import dataclass
from typing import Dict, Any
@dataclass
class ParsedAgentStep:
"""模型單一步驟結構化解析結果"""
thought: str
action: str
tool_args: Dict[str, Any] | None
final_answer: str | None
is_valid: bool
error_message: str | None = None
def parse_agent_output(raw_text: str) -> ParsedAgentStep:
"""
從模型輸出的文字中提取 <thought>、<action> 與 <final_answer> 標籤。
若標籤缺失或 JSON 格式錯誤,則回傳詳細的驗證錯誤資訊。
"""
thought_match = re.search(r"<thought>([\s\S]*?)</thought>", raw_text)
action_match = re.search(r"<action>([\s\S]*?)</action>", raw_text)
final_match = re.search(r"<final_answer>([\s\S]*?)</final_answer>", raw_text)
thought = thought_match.group(1).strip() if thought_match else ""
action_raw = action_match.group(1).strip() if action_match else ""
final_answer = final_match.group(1).strip() if final_match else None
if not thought:
return ParsedAgentStep(
thought="", action="", tool_args=None, final_answer=None,
is_valid=False, error_message="輸出缺少必備的 <thought> 思考標籤。"
)
if not action_raw and not final_answer:
return ParsedAgentStep(
thought=thought, action="", tool_args=None, final_answer=None,
is_valid=False, error_message="輸出既無 <action> 也無 <final_answer>,違反決策合約。"
)
if final_answer:
return ParsedAgentStep(
thought=thought, action="finish", tool_args=None,
final_answer=final_answer, is_valid=True
)
# 解析 action 區塊中的工具與參數
try:
# 預期格式:{"name": "tool_name", "args": {...}}
action_data = json.loads(action_raw)
tool_name = action_data.get("name", "")
tool_args = action_data.get("args", {})
return ParsedAgentStep(
thought=thought, action=tool_name, tool_args=tool_args,
final_answer=None, is_valid=True
)
except Exception as exc:
return ParsedAgentStep(
thought=thought, action=action_raw, tool_args=None, final_answer=None,
is_valid=False, error_message=f"<action> 區塊非合法 JSON 格式:{exc}"
)
第四步:實作模擬推理引擎,用於在 --dry-run 離線模式下模擬產生合乎提示詞規範的結構化回傳字串:
# research-agent/src/research_agent/prompt_simulator.py
import json
def simulate_llm_response(user_query: str, model_name: str) -> str:
"""離線模擬模型在強提示詞引導下產生的合規輸出文字"""
if "架構" in user_query or "晶片" in user_query:
return f"""<thought>
收到關於邊緣晶片架構的調研需求。依據 [{model_name}] 的工作準則,我需要先檢索知識庫中關於 2026 年邊緣運算單元的最新技術資料。
</thought>
<action>
{json.dumps({"name": "database_lookup", "args": {"keyword": "邊緣晶片架構"}}, ensure_ascii=False)}
</action>"""
else:
return f"""<thought>
使用者提出了一般性問題,目前資訊充足,可以直接歸納結論。
</thought>
<final_answer>
依據系統既有資訊,研究助理已準備就緒,隨時可進行深入調研。
</final_answer>"""
第五步:撰寫測試與整合腳本 test_prompt_pipeline.py,驗證從提示詞裝配、模擬生成到合約解析的完整閉環:
# research-agent/test_prompt_pipeline.py
from research_agent.prompt_engine import ResearchPromptBuilder
from research_agent.output_parser import parse_agent_output
from research_agent.prompt_simulator import simulate_llm_response
def main():
print("=== 開始執行 Prompt 引擎與輸出契約驗證 ===\n")
# 1. 構建並檢驗 System Prompt
builder = ResearchPromptBuilder()
full_prompt = builder.build_system_prompt()
print("1. 系統提示詞(System Prompt)組裝成功:")
print(f" - 包含模型決策變數:{builder.model_name}")
print(f" - 總字元長度:{len(full_prompt)} 字元")
assert "負向禁止事項" in full_prompt
assert "Few-shot Demonstrations" in full_prompt
print(" - 核心準則、少樣本示範與負向約束皆完整嵌入!\n")
# 2. 模擬模型產出推理文字
test_task = "請分析 2026 年邊緣晶片架構的最新發展瓶頸。"
print(f"2. 模擬使用者任務:{test_task}")
raw_model_reply = simulate_llm_response(test_task, builder.model_name)
print(" 模型原始輸出酬載:")
print(raw_model_reply.strip())
print()
# 3. 執行契約解析
parsed_step = parse_agent_output(raw_model_reply)
print("3. 結構化合約解析結果:")
print(f" - 契約有效性(is_valid):{parsed_step.is_valid}")
print(f" - 萃取之思考脈絡(Thought):{parsed_step.thought}")
print(f" - 萃取之動作名稱(Action):{parsed_step.action}")
print(f" - 工具參數(Args):{parsed_step.tool_args}")
assert parsed_step.is_valid is True, f"解析失敗:{parsed_step.error_message}"
assert parsed_step.action == "database_lookup"
print("\n=== 提示詞設計與輸出合約解析驗證全面通過! ===")
if __name__ == "__main__":
main()
第六步:在終端機中執行驗證程式:
python test_prompt_pipeline.py
執行後將輸出如下驗證追蹤資訊(示範輸出):
=== 開始執行 Prompt 引擎與輸出契約驗證 ===
1. 系統提示詞(System Prompt)組裝成功:
- 包含模型決策變數:mock-agent-v1
- 總字元長度:842 字元
- 核心準則、少樣本示範與負向約束皆完整嵌入!
2. 模擬使用者任務:請分析 2026 年邊緣晶片架構的最新發展瓶頸。
模型原始輸出酬載:
<thought>
收到關於邊緣晶片架構的調研需求。依據 [mock-agent-v1] 的工作準則,我需要先檢索知識庫中關於 2026 年邊緣運算單元的最新技術資料。
</thought>
<action>
{"name": "database_lookup", "args": {"keyword": "邊緣晶片架構"}}
</action>
3. 結構化合約解析結果:
- 契約有效性(is_valid):True
- 萃取之思考脈絡(Thought):收到關於邊緣晶片架構的調研需求。依據 [mock-agent-v1] 的工作準則,我需要先檢索知識庫中關於 2026 年邊緣運算單元的最新技術資料。
- 萃取之動作名稱(Action):database_lookup
- 工具參數(Args):{'keyword': '邊緣晶片架構'}
=== 提示詞設計與輸出合約解析驗證全面通過! ===
常見錯誤與踩雷
在設計生產級 Agent 的提示詞體系時,以下四個陷阱最常引發不可預期的錯誤:
- 負向約束引發「拒絕迴圈(Refusal Loop)」:如果負向條款寫得過於絕對且欠缺引導(例如「嚴禁在沒有 100% 把握的情況下發言」),模型會產生極端保守傾向,對使用者的任何合理研究提問一律回答「抱歉,我缺乏完整資料,無法回答」,導致代理癱瘓。負向條款必須搭配行動路徑(例如「若無資料,應主動呼叫檢索工具,而非逕行放棄」)。
- 少樣本示範中的參數偏見(Demonstration Bias):大型語言模型對少樣本示範中的具體數值極度敏感。如果你在少樣本範例中查詢了特定關鍵字(如「NPU 效率」),模型在面對其他無關問題時,有時仍會不自覺地呼叫與 NPU 相關的參數。因此,少樣本示範應強調抽象的思考邏輯結構,具體實體名稱應保持多樣化。
- 字串格式化時的花括號衝突(KeyError):在組裝帶有 JSON 格式示範的提示詞模板時,直接使用 Python 的
.format()或f-string會將 JSON 裡的{"name": "..."}誤認為替換變數,導致拋出KeyError。在模板中必須以雙花括號{{}}進行轉義,或者將動態變數限定在獨立的子字串中替換。 - 未定義解析失敗的修復路徑:模型即使受到提示詞約束,仍有極低機率漏掉閉合標籤(如忘了寫
</thought>)。如果程式碼在解析失敗時直接崩潰中斷,會使長達數分鐘的研究流程毀於一旦。工程上必須捕獲解析異常,並在下一輪自動將「輸出格式有誤,請依規格重新輸出」作為user訊息餵回給模型進行自我修正。
效能與實務提醒
優良的提示詞架構不僅影響智力水準,更深刻關聯著系統的運作延遲與 Token 成本:
第一,動態少樣本檢索(Dynamic Few-shot Selection):如果你的系統累積了幾十個不同專業領域的優秀示範案例,切忌將它們全部塞入 System Prompt 中。過長的提示詞會侵占有限的上下文視窗。業界最佳實務是將範例庫進行向量化儲存,當使用者提出問題時,先透過語意相似度檢索出最相關的 1 至 2 個示範動態注入提示詞,兼顧示範效果與精簡成本。
第二,靜態前綴與快取對齊(Prompt Cache Alignment):為了極大化各大供應商的提示詞快取命中率,必須確保 System Prompt 與靜態規範始終位於輸入的最開頭,且內容字元保持完全一致。動態任務字串、時間戳記與使用者輸入一律放置於後續訊息中,絕不插入到快取區塊的前半部。
第三,嚴格鎖定較低的溫度係數(Temperature ≤ 0.2):在創作或聊天應用中,較高的溫度(如 0.7–1.0)能帶來豐富的詞彙多樣性;但在需要嚴格解析結構化標籤與工具參數的 Agent 場景中,溫度通常建議設定在 0.0 到 0.2 之間,以最大程度消除隨機性,確保標籤與 JSON 參數百分之百合規。
小結
今天我們建立了專業研究代理不可或缺的提示詞工程支柱。我們摒棄了隨興拼湊字串的舊做法,建置了模組化的 ResearchPromptBuilder,有機整合了角色定位、工作準則、負向約束與高品質的少樣本示範。同時,我們設計了 parse_agent_output() 契約驗證器,為後續將模型思考結構轉化為實體工具呼叫奠定了堅實的解析基礎。
以下整理本章節的核心概念與台灣用語對照表:
- 角色設定(Persona):明確定義模型在任務中所扮演的專業身分與價值觀,激活用於專業推理的語意關聯。
- 少樣本學習(Few-shot Learning):在提示詞中提供少量高品質的輸入輸出範例,引導模型學會特定推理結構的技術。
- 負向約束(Negative Constraints):明確宣告模型不可踰越的安全界限與禁止行為,壓制幻覺與危險動作。
- 示範軌跡(Demonstration Trajectory):完整展現「思考、決策、工具回饋、最終推論」各階段互動的示範歷程。
- 輸出合約(Output Contract):要求模型輸出必須嚴格遵循的語法或標籤規範,確保程式端可精準解析。
結語
至此,我們已經掌握了提示詞的設計與文字標籤解析。但在現代軟體工程中,依賴正則表示式去解析自訂的文字標籤(例如 <action>)依然存在一定的容錯挑戰。為了解決這個問題,各大模型供應商均推出了專屬的底層協定——Function calling(函式呼叫/工具呼叫),允許我們直接傳遞 JSON Schema,由模型在底層保證輸出結構化參數。
明天,我們會進入「AG Day 5 Function calling:讓模型呼叫你的函式」,帶大家深入探討底層的 Function calling 機制,使用 Python 型別提示與 Pydantic 自動生成標準 JSON Schema,讓模型精確呼叫我們自訂的研究工具!
延伸資源
- OpenAI 官方文件:Prompt Engineering Best Practices。系統性學習結構化提示詞與少樣本技術。
- Anthropic 官方技術指南:Prompt Design Overview。掌握 Claude 系列模型的角色與標籤設計指南。
- arXiv 原創論文:Language Models are Few-Shot Learners(Brown et al., 2020)。少樣本學習在大型語言模型中的奠基之作。
- Pydantic 官方文件:Data Validation and Schema Generation。探索如何利用 Python 型別系統生成標準約束。
留言
張貼留言