Back to Blogs
agent

Deep Dive Into AI Agent

隨著 LLM 越來越聰明,所支援的 Context Window 越來越大,相關的應用開始從單次的 LLM chat 到現在每間公司都在嘗試產品化的 AI Agent。 Agent 本身到底是什麼呢?

Aaron

對話

從最小的 LLM 對話開始

from google import genai

client = genai.Client(...)
user_prompt = input("> ")
response = client.models.generate_content_stream(
        model="gemini-3.1-flash-lite", 
        contents=user_prompt
    )

for chunk in response:
	print(chunk.text, end="", flush=True)

多輪對話

Agent 的最底流程其實也就是一個 While True 的 loop, 裡面會把每次 User 的 prompt 以及 LLM 的 Response 不停累積在 History 的 List 裡面 (每次可能都會存在 DB 或是其他 file 裡面)

from google import genai

history = []

client = genai.Client(...)

while True:
	
	# Get User Prompt
	user_prompt = input("> ")
    if not user_prompt.strip():
        continue
    if user_prompt.strip() == "/exit":
        print("Happy Coding!!")
        break
	# Add User Prompt to History
	history.append(Message(role="user", text=user_prompt))
	contents = message_to_contents(history)
	
	# Ask LLM
	response = client.models.generate_content_stream(
        model="gemini-3.1-flash-lite", 
        contents=contents
    )
    
	text = response.text or ""
	
	# Add LLM Response to History
    history.append(Message(role="assistant", text=text))
    
    # Show Result to User
	print(text)

如何使用工具呢?

LLM chat 非常有局限性,例如:

  1. 時效性: LLM 模型不可能每天都把資料即時的訓練 LLM 模型,現在使用的模型可能是去年訓練完 freeze 的, 那如果想詢問 up to date 的問題呢?
  2. 互動性: LLM 模型如何跟外部的系統做互動呢?
  3. 不確定性:LLM 模型是 non-deterministic 的, 例如問他 strawberry 有幾個 r 有時候也會錯誤

這時候就可以讓 LLM 去使用我們寫好的 function 去做 web search, call api 或是執行 deterministic 的邏輯

Function Calling

現在的 LLM 模型基本上都支援可以傳一個 tool List 做使用, tool 本身包含

  1. name: function 的名字,可以用來作 dispatch
  2. description: 用來讓 LLM 知道什麼時候可以用以及怎麼用這個 tool
  3. function args: 讓 LLM 知道要傳入哪些參數
...

response = client.models.generate_content_stream(
        model="gemini-3.1-flash-lite", 
        contents="1 + 2 = ?",
        config=types.GenerateContentConfig(
                tools=[types.Tool(function_declarations=[
	                types.FunctionDeclaration(
		                name="add",
		                description="Add Two numbers",
		                parameters_json_schema={ 
			                'properties': { 
				                'num1': { 
					                'title': 'Num1', 
					                'type': 'integer' 
					            }, 
					            'num2': { 
						            'title': 'Num2', 
						            'type': 'integer' 
						        } 
						    }, 
						    'required': ['num1', 'num2'], 
						    'title': 'add', 
						    'type': 'object' 
						}
	                )
                ])]
            ),
    )
...
candidate_content = None
function_calls = []
if response.candidates:
	candidate_content = response.candidates[0].content
	if candidate_content:
		parts = candidate_content.parts or []
        function_calls = [p.function_call for p in parts if p.function_call]

...

for fc in function_calls:
	print(fc.name) # add
	print(fc.args) # {num1: 1, num2: 2}               

Skill 是什麼?

當我們有越來越多種任務想交給 Agent 去做之後, 將所有的 Domain Knowhow 或是工作流程都寫在 System Prompt 會導致 Context 的浪費,於是傾向使用漸進式揭露的 Skill 注入資訊到 Context 內。

progressive disclosure

而 Skill 本身其實就是一個可以去讀取某個資料夾內文件內容及執行 script 的 tool。 會多注入一個 Tool,例如 get_skill_instruction, 且 System Prompt 裡面會有如何去使用 Skill 的指令,例如

<available_skills>
	You have access to a set of skills listed below. Each skill is shown only as a
	short name and description — the FULL instructions live in a separate file and
	are NOT included in this prompt.
	
	...
	
	<procedure>
		1. Read every <skill_description> below.
		2. For each, ask: could this skill plausibly apply to the user's task? (1% counts.)
		3. If yes → call `get_skill_instruction(name=...)` with that skill's exact name.
		4. Follow the returned instructions exactly. Do not paraphrase or shortcut them.
		5. If no skill applies, proceed normally.
	</procedure>
	
	Never guess or invent a skill's behavior from its description alone — the
	description is only a hint for matching. The real instructions are only
	available via `get_skill_instruction`.
	
	<skills>
		<skill_name> Foo Skill </skill_name>
		<skill_description> When to use this Foo Skill and when not to </skill_description>
		
		<skill_name> Bar Skill </skill_name>
		<skill_description> When to use this Bar Skill and when not to </skill_description>
	</skills>
</available_skills>

他解決了 Context Window 有限的問題, 當需要 Skill 的時候才會把需要的訊息載入,也可以使用定義好的 Script 去得到某個 deterministic 的 sub-result。 然而 Skill 本身也有局限

  1. 本質上也只是把 prompt inject 到 context 裡面, 隨著任務進行 Skill 本身的資訊就可能被稀釋掉 (Lost in the Middle) Lost in the middle
  2. 使用的前提是 LLM Model 判斷要使用這個 Skill 才會載入,相反的如果 LLM 沒有載入的話就沒用

Context Engineering

我們先大致看一個 Agent 的 Flow


history = []

while True:
	
	# Get User Prompt
	user_prompt = input("> ")
	# Add User Prompt to History
	history.append(Message(role="user", text=user_prompt))
	contents = message_to_contents(history)
	
	for _ in range(MAX_TOOL_ITERATION):
		# Ask LLM
		response = client.models.generate_content_stream(
	        model="gemini-3.1-flash-lite", 
	        contents=contents
	    )
    
		if not function_calling(response):
			print(response.text)
			# Add LLM Response to History
		    history.append(Message(role="assistant", text=text))
		    break
		
		# get tool response and send to LLM in next iteration
		tool_response_parts = []
		for fc in function_calling(response):
		    func = tools_registry[fc.name]["func"]
			result = func(**dict(fc.args or {}))
            tool_response_parts.append(
				types.Part.from_function_response(
					name=fc.name or "",
					response={"result": result},
				)
			)
		contents.append(types.Content(role="user", parts=tool_response_parts))	

這邊我們先關注 contents 裡面包含了哪些東西。

  1. history
  2. system_prompt
  3. user_prompt
  4. tool result ( 這邊可能包含了 tool, RAG, Skill 等等 )

這個 contents 就是 LLM 的 Context。 而問題是該放哪些東西給 LLM 能夠讓它在任務上表現得最好呢? 直覺上隨著 LLM 模型支援的 Context 越來越大,我們可以將所有東西都餵給 LLM 並祈禱它表現良好。 但是事實上隨著 Context Window 中 token 數量的增加,模型準確回憶上下文資訊的能力會下降 (Context Rot), 並且隨著任務的進行許多細節的注意力也會逐漸被稀釋 (Lost in the Middle),所以如何決定有哪些東西要進 Context 就是 Context Engineering。 大致有以下幾種面向考慮

Write

把資訊寫到 context 外部,如 memory 或是檔案,需要時再讀回。

write context

Select

在每次任務前要怎麼將有用的資訊放進 Context 內呢? 主要有 3 種

  1. Rule-Base Select: 例如預先規定一定要載入 CLAUDE.md
  2. Model-Base Select: 依任務挑選要載入的 skill
  3. Retrieval-based Select: 使用相似度分析從外部資料庫拿相關資訊

Compress

當 Context 越來越大時, 我們可以對其內容進行壓縮,以最小的 token 數保留盡可能多的資訊。 或者對內容進行剪枝。 然而風險是可能有些細節會被 LLM 給丟失導致上下文缺失,並且根據 Compress 或是剪枝的方式也會導致 KV Cache 失效

Compact

Isolation

當任務可能會消耗大量 Context 時可以委託其他 Context 互相隔離的 Agent 去執行並且得到壓縮過後的 Summary 以達到減少 Main Agent 的 Context 消耗。然而風險一樣是細節可能被 Sub Agent 給丟失掉,並且會有較高的延遲以及總 token 消耗。

sub agent

Reference