返回文章列表
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