iT邦幫忙

2026 iThome 鐵人賽

DAY 17
0

每個軟體甚至於系統,都會有「設定」這個功能。
設定內的每個項目,一開始會有「預設」的設定值,如果沒有額外改動,就依照這個預設值去運作,而當然,也會有接口可以去更改設定,今天這篇,我們來實作設定檔這個功能。

設定檔案的部分,這裡選擇以 JSON 的格式儲存,好處是它可以跟 Pydantic 做很好的配合。

檔案架構的部分,分成了負責讀寫 JSON 設定檔的 config_manager.py 以及管理設定項目的 config_schema.py 和恢復檔案的 config_repair.py(下一篇會提到,用來自主修復設定檔的錯誤):

src/meowgent/
 ├── ...
 └── config/
      ├── __init__.py
      ├── config_schema.py    # 定義設定項目
      ├── config_manager.py   # 讀寫設定檔
      └── config_repair.py    # 恢復設定檔

__init__.py 的部分:

from .config_schema import MeowgentConfig
from .config_manager import ConfigManager, CONFIG_FILE, CONFIG_DIR
from .config_repair import repair

接下來的四篇,我們會專注於處理「設定檔」這件事上,分別是:

  1. 建立好格式以及讀取部分 -> 本篇。
  2. 套用設定項目、設定檔的修復 -> Day 18。
  3. 更改設定檔 -> Day 19、20。
    本篇先來處理第一點,後面兩點,交給後面的三天來處理。

定義設定項目

這裡使用的是 Pydantic 的「資料模型 - BaseModel」來定義設定項目,
我們先簡單給定兩個類別的設定項目,分別是與模型相關的及與 Agent 行為相關的兩種,先簡單做這幾個選項就好:

  1. 模型相關:
    1. 預設模型。
    2. 溫度係數。
  2. Agent 行為相關:
    1. 每輪的最大工具調用次數。
    2. 工具審核模式。

在這裡 ModelsConfig、AgentConfig 被稱為「模型」,需要繼承 BaseModel,而底下的項目被稱為「欄位」。
看到下面程式碼的部分:
每個欄位上我們都明確的「標註上型別」(就算之後傳入時誤把 "12" 這樣的字串傳入 int 時也可以自動轉換,要是沒標註型別,就會繼續錯下去),
定義好欄位的型別後要對欄位進行「約束」(也就是 Field()),每個欄位都會要有一個「預設值 default」以及「描述 description」,在數值的部分還可以設定數值範圍。

gt(greater than -> 大於);ge(greater equal -> 大於等於)。
lt(less than -> 小於);le(less equal -> 小於等於)。

# src/meowgent/config/config_schema.py
from pydantic import BaseModel, Field
from typing import Literal

class ModelsConfig(BaseModel):
    """ 模型相關 """

    default_model: str = Field(
        default="qwen3.8",
        description="模型"
    )
    temperature: float = Field(
        default=0.1,
        ge=0, le=1.5,
        description="溫度係數(0~1.5)"
    )

class AgentConfig(BaseModel):
    """ Agent 行為 """

    max_turns: int = Field(
        default=20,
        ge=1,
        description="每輪的最大工具調用次數"
    )
    tool_approval_mode: Literal[
	    "always ask", "default", "approval_all"
    ] = Field(
        default="default",
        description="工具審核模式"
    )

而兩個模型建立好後,最外層再用一個模型打包成一個整體:
在 Field() 的部分,這裡是用 default_factory 傳入前面定義的模型。

default_factory 的本質就是「傳入一個 callable(函式或類別),且每次建立時動態呼叫它生出全新物件」:

  1. 避免共用可變記憶體:包含 list、dict,以及繼承 BaseModel 的子模型,也就是這裡用 default_factory 的原因。
  2. 獲取即時動態資料:例如當前時間(datetime.now)。
# src/meowgent/config/config_schema.py
...
class MeowgentConfig(BaseModel):

    models: ModelsConfig = Field(default_factory=ModelsConfig)

    agent: AgentConfig = Field(default_factory=AgentConfig)

而我們來看一下,現在這個 MeowgentConfig 轉為 JSON 會長什麼樣:

Python 程式碼                                產出的 JSON 檔案
─────────────────────────────────────────────────────────────────────────────
class MeowgentConfig(BaseModel):      ───>  {   (最外層大括號)
                                            
    models: ModelsConfig              ───>    "models": {
        default_model = "..."         ───>      "default_model": "qwen3.8",
        temperature = 0.1             ───>      "temperature": 0.1
                                              },
                                            
    agent: AgentConfig                ───>    "agent": {
        max_turns = 20                ───>      "max_turns": 20,
        tool_approval_mode = "..."    ───>      "tool_approval_mode": "default"
                                              }
                                            }

而接下來,我們馬上來把 MeowgentConfig 模型轉為 JSON 儲存吧!


儲存設定檔

剛剛,我們定義好了設定檔上有哪些項目可以被設定、預設值是怎麼樣的,現在,首先我們先來做儲存的邏輯。

第一步,我們先把這個設定檔要存在哪給定好,路徑為 ~/.meowgent/config.json。

資料夾的部分,有注意到名字前面有一個點嗎?
這代表它是「隱藏資料夾」,正常打開 Finder 或檔案總管不會被顯示出來(用 cmd + shift + . 或 win 上的 檢視 - 顯示 - 勾選 隱藏的項目 來顯示)。
用隱藏資料夾的原因是為了保持使用者家目錄的簡潔、防止誤刪。

而為什麼是存在使用者家目錄裡,而不是像專案資料夾內呢?
最主要的原因是,當我們未來把專案打包後,在一般使用者電腦裡將不再有「專案資料夾」這個概念,而且通常軟體的安裝目錄是唯讀的,無法隨意寫入。
所以放在使用者家目錄是業界 CLI 軟體普遍的資料存放點。

# src/meowgent/config/config_manager.py
from pathlib import Path
from .config_schema import MeowgentConfig
from typing import Tuple

# 定義存放位置
CONFIG_DIR = Path.home() / ".meowgent"
CONFIG_FILE = CONFIG_DIR / "config.json"

接著我們進入到儲存函數的部分,這裡把它設為「靜態方法」:

靜態方法是一個不依賴實體物件(self)或類別屬性(如類別方法 @classmethod 接收的 cls))的方法(類別中的函數)。
簡單來說,它和類別外的函數沒什麼區別,只是為了邏輯分類或可讀性,把它「歸類」在一個類別之中。

而如果是「類別方法」,它專門處理不需實體物件的事情,用來從「整個類別的視角」對整個類別做統籌管理。

進到函數裡,首先先建立好隱藏資料夾,要加上 exist_ok=True 來避免已存在時出錯。
接著,把打包好的 MeowgentConfig 模型序列化(轉換)成 JSON 格式字串,這裡設定縮排為 2,不然會是沒有縮排的單行字串。

這裡 MeowgentConfig 裡設定內容就是我們目前要存檔的內容了,後續會做邏輯來處理更改 MeowgentConfig。

轉換成字串後,就可以將它寫入 ~/.meowgent/config.json 檔案中,編碼要設定好,防止出現亂碼。

# src/meowgent/config/config_manager.py
...

class ConfigManager():

    @staticmethod # 靜態方法
    def save_config(config: MeowgentConfig):

        CONFIG_DIR.mkdir(parents=True, exist_ok=True) # 建立目錄

        json_data = config.model_dump_json(indent=2)
        # 將模型物件導出(序列化)為格式化的 JSON 字串

        CONFIG_FILE.write_text(json_data, encoding="utf-8") # 寫入設定檔

看完代碼,你可能會覺得,這看起來只能存預設樣式的設定,其實不是這樣的!
舉個例子,如果我們對模型進行了更改,會在外部做以下的邏輯(下一篇來做)進行以下動作:

..., config = ConfigManager.load_config() # 等等要做的讀檔邏輯
  
config.models.default_model = "qwen-2.5" # 在這裡修改

ConfigManager.save_config(config) # 這裡把修改的儲存

可以理解為:

  1. 讀取 JSON 格式,轉為 MeowgentConfig 模型物件。
  2. 對物件做修改。
  3. 將修改後的物件儲存為 JSON。
    也就是說,修改其實「主要是對資料模型物件」,對物件改好後再利用 save_config() 存為 JSON 檔案。

讀取設定檔

存檔搞定了,那我們要怎麼把存好的設定拿出來使用呢?
這時我們就要來做負責讀檔的「靜態方法」:
回傳的部分用元組,布林值的部分是用來表示是否有正確讀取到已儲存的設定檔。

# src/meowgent/config/config_manager.py
...
class ConfigManager():
	...
	
	@staticmethod
	def load_config() -> Tuple[bool, MeowgentConfig]:

這邊下面會有三種情況出現:

  1. 設定檔不存在(還未被建立或誤刪)-> 直接拿預設的設定存一份起來,並回傳 True 及預設情況的 MeowgentConfig 模型。
    # src/meowgent/config/config_manager.py
    ...
    class ConfigManager():
    	...
    	@staticmethod
    	def load_config() -> ...:
    		if not CONFIG_FILE.exists(): # 設定檔不存在 -> 建立設定檔
    
    			config = MeowgentConfig() # 獲取預設設定
    			ConfigManager.save_config(config)
    
    			return (True, config)
    
  2. 設定檔存在且被正確讀取 -> 回傳 True 及讀取到的 MeowgentConfig 模型。
    # src/meowgent/config/config_manager.py
    ...
    class ConfigManager():
    	...
    	@staticmethod
    	def load_config() -> ...:
    		if ...:
    			...
    
    		else: # 設定檔存在
    			try:
    				content = CONFIG_FILE.read_text(encoding="utf-8")
    
    				config = MeowgentConfig.model_validate_json(content)
    				# 讀取到的 JSON(字串形式)載入模型(MeowgentConfig)
    
    				ConfigManager.save_config(config) # 若有新設定更新進 JSON 檔
    
    				return (True, config)
    
  3. 設定檔存在但讀取錯誤(格式有誤等)-> 回傳 False 及預設情況的 MeowgentConfig 模型。
    先「暫時」使用預設的設定,main.py 收到 False 會去調用模型,讓模型自主檢查設定檔的部分是出了什麼問題(未來會實作的部分)。
    # src/meowgent/config/config_manager.py
    ...
    class ConfigManager():
    	...
    	@staticmethod
    	def load_config() -> ...:
    		...
    		else:
    			try:
    				...
    			except Exception: # 讀取錯誤時
    				return (False, MeowgentConfig())
    

今天這篇,我們處理好了設定檔的格式、讀取,下一篇,我們要來把設定項目給套用!


上一篇
Day 16 - 工作目錄的選擇
下一篇
Day 18 - 設定檔 - 下
系列文
手刻 AI Agent!大一新生的 Python 實戰筆記 共 18 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言