蒼時弦也
蒼時弦也
資深軟體工程師
發表於

也許你的 Claude Code 該檢查自己的實作

原本預定要釋出八月份在 COSCUP 演講內容應用的技巧所產出的工具,然而發現開發要考慮的問題很多,實際使用也會有門檻

決定先從比較簡單、能夠立即上手的角度切入,最近終於將 ccharness 最基礎的功能實作完畢,可以從另一種角度來改善 Claude Code 的產出品質

ReAct

在演講中,我以 ReAct(Reasoning and Acting)為基礎,將 Through、Action、Observation 替換為 Evaluation、Feedback、Optimization 的循環,來自動改寫 System Prompt 去提高後續生成的品質

這是從 TextGrad 這篇論文獲得的靈感,利用 TextLoss 的機制,以文字描述評估(Evaluation)方法,讓 LLM(大型語言模型)能夠理解產出和目標的差距,就可以當作參考資訊,讓 LLM 以此為基礎改寫輸入(如:System Prompt)

這種做法也是一種 Meta Prompting 的技巧,過程跟 ReAct 在做的事情也非常類似,不過 ReAct 對 Reasoning 更注重一些,因為輸出更多相關的 Token 能夠保證後續有更高的機率輸出我們想要的內容

目前 Reasoning 類型的技巧,大多是用消耗更多成本(Token 花費)去換取更好的品質

套用在 Agent 的情境,就是在使用工具之後,LLM 會確認工具輸出來決定下一個動作,反覆這樣的過程,理論上就能保持不錯的輸出品質,目前大部分 Coding Agent 也是以此為基礎設計

Context Engineering

如果 ReAct 本身就能穩定的高品質輸出,那麼 Claude Code 有時候還是無法表現得很好呢?這篇文章的主要假設,是我們在整個開發過程中仍然缺少足夠的脈絡,那麼 Context Engineering 就能用來改善問題

單純以我接收到的資訊來看,雖然我們開始對 Context Engineering 有概念,但大部分針對 Claude Code 的調整和改進還是以調整 System Prompt 的方式為主,都是嘗試在「開頭」提供完整、詳細的任務指示

這個方案確實能一定程度提升品質,像 AWS 的 SDD(Spec-Driven Development)就是一個很好的案例,準備 COSCUP 的內容時我也是以此為基礎思考,然而這並沒有應用到 Context Engineering 最核心的一個想法「在對的地方出現對的資訊」

我們可以思考一個問題,在 Coding Agent 出現之前,人類在實作一個功能的流程是怎樣的?

  • 理解需求
  • 實作功能
  • 提交實作

然而我們在實作之後真的「完全不檢查程式碼」嗎?我相信大部分的人都還是會確認過再進行提交,這表示 Code Review 在整個開發階段並不只發生一次,而是在實作過程中隱含多次的「反覆思考」

要加入這個步驟,一種是在 System Prompt 中要求「當編輯檔案後,檢查實作符合專案要求」這樣的提示,不過這會受限於注意力機制的特性,大多數情況能夠拿到最大的注意力會是最近的 User Prompt 所以並不一定能保證每次都會實行

另一種則是利用 Claude Code 提供的 Hook 機制,我們在 PostToolUse 觸發時,在下一個動作之前加入新的 System Prompt 來強制的對實作 Code Review 進一步的提升 Claude Code 輸出的品質

因此,我們從 ReAct 跟 Context Engineering 的角度思考,一個 Coding Agent 開發流程會變成這樣

  • 理解需求
  • 實作功能
    • Tool Response(Observation - 1)
    • Code Review(Observation - 2)
  • 修正實作
  • 提交實作

原本 ReAct 的 Observation 是「修改成功」但沒有確認「實作正確」這就造成需要在停止後才能修正有問題的地方,然而 Hook 機制允許我們「阻止(Block)」後續動作,就大大的提高「強制檢查」的成功率

在我的設計中,我會根據修改的檔案提供不同的標準,這也能夠更精細的處理不同情境的檢查標準

Evaluation

加入 Hook 對大部分人來說並不難,難的是設計好的評估方式

評估有兩種方式,一種是 Claude Code 官方文件就有的範例,將 Rubocop、Prettier、Ruff 這類 Linter、Formatter 加入進去,另一種要寫 Evaluation Prompt 相對就變得困難不少,因為很看 Prompt Engineering 和對專案、軟體工程的理解程度

單純依靠 Linter Hook 很難保證品質,我相信大多數人在 Claude Code 已經設定過,但用起來總是差那麼一點點,這也是為什麼還需要進一步做 Evaluation Prompt 的 Hook 來加強這部分的弱點

這邊我推薦的做法是使用 G-Eval 的方式來設計,這是我最早接觸到的評估方法,也是一種能讓 LLM 和人類有接近的評分方式,比較不會被 LLM 原本的偏好影響

假設 LLM 產生的實作不是我們想要的,直接讓他自己評分他也不一定會認為有問題,這也是為什麼設計 Evaluation Prompt 困難的地方,不論是太嚴格或者太寬鬆都可能跑偏

G-Eval 可以用製作評量表(Rubric)的方式去理解,下方是一個簡單的例子:

 1# Rails Controller
 2
 3This document outlines the criteria for evaluating the quality of rails controller.
 4
 5## Criteria
 6
 7Following are the criteria used to evaluate the design and implementation of the rails controller. Review step by step and give reasoning to explain why the implementation can get the score.
 8
 9### Before Actions over in-line Code (1 points)
10
11When defining a action in a rails controller, ensure the common logic is extracted to before actions.
12
13```ruby
14class ReviewsController < ApplicationController
15  before_action :set_review, only: [:show, :edit, :update, :destroy]
16
17  def show
18    # @review is already set by the before_action
19  end
20
21  def edit
22    # @review is already set by the before_action
23  end
24
25  # ...
26
27  private
28
29  def set_review
30    @review = Review.find(params[:id])
31  end
32end
33```
34
35## Scoring
36
37Each criterion only get the point when it is fully satisfied, otherwise get 0 point.

當團隊鼓勵多使用 before_action 時,我們就在評量表中檢查這件事情,因為使用範例 Claude Code 會優先參考,可以用來確保實作都會「非常類似」同時也是避免給分標準因為文字過於模糊

如果仔細思考,評量表是團隊中互相 Code Review 的基準文件化,過去沒有文件的時期,很容易因為個人理解差異在 Code Review 的過程中有不同見解造成不同判斷

但是現在文件化後,基準就會變得相對清晰,文件有寫的就是硬性的「看到就必須調整」沒有寫的則類似於「個人偏好」的軟性要求,對 Coding Agent 就變成是「知道」跟「不知道」的差異,跟新團隊成員剛加入,我們會不斷提醒一樣

總而言之,我們在開發過程中的實際流程會像這樣

  • 理解需求
  • 實作功能
  • 檢查產出
  • 提交成果

大部分資深的工程師都會自然的「檢查自己寫的程式碼」來確保提交的品質是穩定的,那麼使用 Coding Agent 竟然沒有考慮,是不是反而比較奇怪?

雖然 Claude Code 提供 Hook 背後原始的意圖無法得知,至少 Linter Hook 這種應用情境,在解決提交品質的問題上剛好是可以利用的地方,這可能是其他 Coding Agent 很難取代 Claude Code 的一部分原因

受限於篇幅,背後還有許多考量跟技巧,但這篇文章提供大家一個可以嘗試的方向,希望之後能看到從不同角度切入來提升輸出品質的應用