自訂環境 LLM Coding Agent 部署指南 (Win11 Host + WSL2 Docker 混合架構)
本文件專為您的特定混合環境設計。在此架構中,運算核心 (Ollama) 與開發核心 (Agent/C#) 運行於 Windows Host,而服務設施 (GitLab/WebUI) 運行於 WSL2 Docker。
您的環境配置確認:
Host IP:
192.168.1.106(DHCP 動態,建議使用自動解析策略)Ollama: Windows Native (
:11434)GitLab: WSL2 Docker (
:8929)Open WebUI: WSL2 Docker (
:4000)Agent (本腳本): 將運行於 Windows Native (以便操作 C# SDK 與檔案系統)
🗺️ 第一部分:總體步驟大綱
階段 | 步驟名稱 | 執行環境 | 目的 |
|---|---|---|---|
0 | IP 連線策略配置 | PowerShell/Docker | (關鍵) 決定並設定 IP 自動解析機制,解決 DHCP 變動問題 |
I | 環境連線檢查 | PowerShell (Win) | 確認 Windows 能連線到 WSL2 中的 GitLab 與 WebUI |
II | GitLab 驗證配置 | 瀏覽器/PowerShell | (必需) 取得 PAT 以進行 API 呼叫,並選擇 Git 操作的驗證方式 |
III | Agent 工作區建置 | PowerShell (Win) | 在 Windows 上建立 Python Venv 與 C# 專案目錄 |
IV | 部署 Agent 服務 | PowerShell (Win) | 設定 |
V | 整合測試 | WebUI & PowerShell | 設定 WebUI Tool Calling 並執行端對端測試 |
📁 第二部分:資料夾結構圖
1. 系統服務 (WSL2 Ubuntu 內部)
這部分由您的 Docker 管理,Windows 端只需知道連接埠。
GitLab:
localhost:8929Open WebUI:
localhost:4000
2. 使用者開發工作區 (Windows C 槽使用者目錄)
注意: Agent 必須運行在 Windows 上,才能直接呼叫 dotnet CLI 並修改 Windows 檔案系統上的 C# 專案。
C:\Users\<USERNAME>\
└── LLM_Coding_Agent\
├── .env <-- [設定] 指向 localhost:8929 (GitLab) 和 192.168.1.106 (Ollama)
│
├── Agent_Scripts\ <-- [核心] Python Agent 程式碼
│ └── agent_api.py <-- Flask API (需設定 host='0.0.0.0')
│
├── Code_Project\ <-- [專案] C# WinForm 原始碼
│ ├── .git/
│ └── WinFormApp/
│
└── venv\ <-- [環境] Python 虛擬環境
💻 第三部分:詳細指令與驗證機制
階段 0:動態 IP 連線策略配置 (關鍵步驟)
針對 DHCP 環境,請選擇以下一種方式來確保連線穩定,避免 IP 變動導致服務中斷。
方式 A:自動解析 (強烈推薦)
利用 Docker 的 Host Gateway 功能,讓容器透過固定名稱連線到 Windows Host。
修改 Open WebUI 的啟動指令: 在 WSL2 Ubuntu 中啟動 Docker 容器時,必須加入
--add-host=host.docker.internal:host-gateway參數。# (在 WSL2 Ubuntu 中執行) (WSL2 Bash) $ docker run -d -p 4000:8080 \ --add-host=host.docker.internal:host-gateway \ -v open-webui:/app/backend/data \ --name open-webui \ --restart always \ ghcr.io/open-webui/open-webui:main連線設定變更: 以後在 Open WebUI 設定連線時,一律使用:
Ollama:
http://host.docker.internal:11434Agent API:
http://host.docker.internal:5000
方式 B:手動修改 (僅作備用)
若不重新建立容器,則需在每次 IP 變動時執行:
在 Windows PowerShell 執行
ipconfig查詢新 IP (例如192.168.1.X)。進入 Open WebUI -> 設定 -> 更新連線 URL 為新 IP。
階段 I:環境連線檢查
執行身分: Windows 使用者 (PowerShell)
步驟 | 指令 / 操作 | 意圖與參數說明 | 驗證機制 (Validation) |
|---|---|---|---|
I.1 | 檢查 Ollama
(若報錯請用 | 意圖: 檢查 Ollama 服務是否在 Windows Host 上運行。 參數: | 回傳包含 "Ollama is running" 的訊息。 |
I.2 | 檢查 GitLab (Docker)
| 意圖: 檢查 Windows Host 是否能連線到 WSL2 Docker 中的 GitLab 服務並取得 HTTP 狀態碼。 | 回傳狀態碼 |
I.3 | 檢查 WebUI (Docker)
| 意圖: 檢查 Windows Host 是否能連線到 WSL2 Docker 中的 Open WebUI 服務並取得 HTTP 狀態碼。 | 回傳狀態碼 |
階段 II:GitLab 驗證配置
執行身分: 瀏覽器操作
步驟 | 說明 | 驗證機制 (Validation) |
|---|---|---|
II.1 (必需) | 生成 Personal Access Token (PAT) 原因:PAT 絕對必需用於 Agent 呼叫 GitLab REST API (創建專案、MR)。 開啟 點擊頭像 -> Preferences -> Access Tokens。 新增 Token,Scope 勾選 複製此 Token (例如 | 取得 Token 字串。 |
II.2 (可選) | 設定 Git 操作驗證方式 您的 Agent 預設使用 PAT 搭配 HTTP/S 進行 若堅持使用 SSH: 您必須確保 Windows Host 的 Git 環境已正確設定 SSH Key,並在 Agent 執行前手動修改 | 確認您已將 PAT 貼入 |
階段 III:Agent 工作區建置
執行身分: Windows 使用者 (PowerShell)
步驟 | 指令 / 操作 | 意圖與參數說明 | 驗證機制 (Validation) |
|---|---|---|---|
III.1 |
|
|
|
III.2 |
|
|
|
III.3 |
|
|
|
階段 IV:部署 Agent 服務
執行身分: Windows 使用者 (PowerShell)
1. 建立設定檔 .env
在 LLM_Coding_Agent 根目錄建立檔案 .env。
注意: Agent 運行在 Windows 上,它看 Windows 服務用 localhost,它看 WSL2 服務也用 localhost (因為 Windows localhost 會自動轉發到 WSL2)。
# Agent (Win) 連線到 GitLab (WSL2 Docker)
# 在 Windows 端存取 WSL2 服務可直接用 localhost:8929
GITLAB_URL=http://localhost:8929
GITLAB_PAT=你的_glpat_token_貼在這裡
# Agent (Win) 連線到 Ollama (Win)
OLLAMA_BASE_URL=http://localhost:11434
# 資料庫 (可選)
SQL_SERVER=(localdb)\MSSQLLocalDB
2. 部署程式碼 agent_api.py
將文件末尾的 附錄:Python Agent 核心程式碼 存檔至 Agent_Scripts\agent_api.py。
3. 啟動 API
# (若您當前不在 LLM_Coding_Agent 根目錄,請先 cd)
(venv) PS C:\Users\<USERNAME>\LLM_Coding_Agent> .\venv\Scripts\Activate.ps1
# 意圖:再次確保 Python 虛擬環境已啟用。
# 參數:無。
(venv) PS C:\Users\<USERNAME>\LLM_Coding_Agent> python Agent_Scripts\agent_api.py
# 意圖:執行 Agent 的 Flask API 程式,啟動服務。
# 參數:Agent_Scripts\agent_api.py 是要執行的 Python 腳本路徑。
import osimport subprocessfrom flask import Flask, request, jsonify
# === LangChain 導入修正 (修正 AgentExecutor ImportError) ===# 錯誤原因:在某些較新的 LangChain 版本中,AgentExecutor 被移至 langchain.agents.basefrom langchain.agents import create_tool_calling_agentfrom langchain.agents.base import AgentExecutor# =========================================================
from langchain_core.prompts import ChatPromptTemplatefrom langchain_ollama import Ollamafrom langchain.tools import toolfrom dotenv import load_dotenvimport pyodbcimport requestsimport json
# 載入 .envload_dotenv()
# --- 網路設定 ---# 讀取環境變數,若無則使用預設值OLLAMA_BASE_URL = os.getenv("OLLAMA_BASE_URL", "http://localhost:11434")MODEL_NAME = "deepseek-coder:33b-instruct"GITLAB_URL = os.getenv("GITLAB_URL", "http://localhost:8929")GITLAB_PAT = os.getenv("GITLAB_PAT")SQL_SERVER = os.getenv("SQL_SERVER")
# 專案路徑 (Windows 格式)PROJECT_ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'Code_Project')
app = Flask(__name__)current_project_id = None
# --- 工具集 (Tools) ---
@tooldef create_gitlab_project(name: str, description: str = "", visibility: str = "private") -> str: """創建 GitLab 專案並更新本地 Git Remote。PAT 用於 API 驗證。""" global current_project_id if not GITLAB_PAT: return "錯誤:缺少 GITLAB_PAT (PAT 必需用於 API 呼叫)" url = f"{GITLAB_URL}/api/v4/projects" headers = {'Private-Token': GITLAB_PAT, 'Content-Type': 'application/json'} data = {'name': name, 'description': description, 'visibility': visibility, 'namespace_id': 1} try: # Agent 在 Windows 上,可以直接連 localhost:8929 response = requests.post(url, headers=headers, json=data) response.raise_for_status() info = response.json() current_project_id = str(info['id']) # 取得 Repo URL raw_url = info['http_url_to_repo'] # 確保 URL 是 Windows Host 可以連線的格式 (使用 localhost:8929) # 此處是 PAT + HTTP 方式進行 Git Push/Pull repo_url = raw_url.replace("[http://gitlab.example.com](http://gitlab.example.com)", "http://localhost:8929") if "localhost:8929" not in repo_url: # 如果 raw_url 已經是正確的 ip/port 則不需替換,若不是則嘗試修正 pass
# 將 PAT 塞入 URL (Http Basic Auth) # 格式: http://oauth2:TOKEN@localhost:8929/root/repo.git auth_repo_url = repo_url.replace("http://", f"http://oauth2:{GITLAB_PAT}@")
# Git 操作:設定 Remote URL try: # 嘗試修改 remote subprocess.run(['git', 'remote', 'set-url', 'origin', auth_repo_url], cwd=PROJECT_ROOT, check=True, capture_output=True) except: # 若無 remote 則新增 subprocess.run(['git', 'remote', 'add', 'origin', auth_repo_url], cwd=PROJECT_ROOT, check=True, capture_output=True) return f"專案建立成功 ID: {current_project_id}" except Exception as e: return f"建立失敗: {e}"
@tooldef execute_local_git_command(command: str) -> str: """執行本地 Git 指令""" try: res = subprocess.run(['git'] + command.split(), cwd=PROJECT_ROOT, capture_output=True, text=True, check=True) return f"Git 輸出: {res.stdout}" except subprocess.CalledProcessError as e: return f"Git 錯誤: {e.stderr}"
@tooldef push_to_gitlab_remote(branch_name: str) -> str: """Push 到遠端""" try: res = subprocess.run(['git', 'push', 'origin', branch_name], cwd=PROJECT_ROOT, capture_output=True, text=True, check=True) return f"Push 成功: {res.stdout}" except subprocess.CalledProcessError as e: return f"Push 失敗: {e.stderr}"
@tooldef write_file_to_project(filepath: str, content: str) -> str: """寫入檔案""" path = os.path.join(PROJECT_ROOT, filepath) os.makedirs(os.path.dirname(path), exist_ok=True) with open(path, 'w', encoding='utf-8') as f: f.write(content) return f"寫入: {filepath}"
@tooldef create_gitlab_merge_request(title: str, source_branch: str, target_branch: str = "main") -> str: """建立 MR。PAT 必需用於 API 驗證。""" if not current_project_id: return "無專案 ID" url = f"{GITLAB_URL}/api/v4/projects/{current_project_id}/merge_requests" headers = {'Private-Token': GITLAB_PAT, 'Content-Type': 'application/json'} data = {'source_branch': source_branch, 'target_branch': target_branch, 'title': title} try: res = requests.post(url, headers=headers, json=data) return f"MR 建立成功: {res.json().get('web_url', 'unknown')}" except Exception as e: return f"MR 失敗: {e}"
@tooldef execute_sql_script(sql_query: str) -> str: """連接 SQL Server 執行查詢""" if not SQL_SERVER: return "錯誤:未設定 SQL_SERVER。" conn_str = f"Driver={{ODBC Driver 17 for SQL Server}};Server={SQL_SERVER};Database=master;Trusted_Connection=yes;" try: conn = pyodbc.connect(conn_str) cursor = conn.cursor() cursor.execute(sql_query) if sql_query.strip().upper().startswith('SELECT'): rows = cursor.fetchall() conn.close() return f"查詢結果: {rows}" conn.commit() conn.close() return "SQL 執行成功。" except Exception as e: return f"SQL 錯誤: {e}"
# --- Agent 啟動 ---def create_agent(): llm = Ollama(model=MODEL_NAME, base_url=OLLAMA_BASE_URL) tools = [create_gitlab_project, execute_local_git_command, push_to_gitlab_remote, write_file_to_project, create_gitlab_merge_request, execute_sql_script] prompt = ChatPromptTemplate.from_messages([ ("system", "你是 C# 開發 Agent。若需新專案請先調用 create_gitlab_project。"), ("user", "{input}"), ("placeholder", "{agent_scratchpad}") ]) agent = create_tool_calling_agent(llm, tools, prompt) return AgentExecutor(agent=agent, tools=tools, verbose=True)
agent_executor = create_agent()
@app.route('/run_task', methods=['POST'])def run_task(): data = request.json user_input = data.get('prompt') if not user_input: return jsonify({"status": "error"}), 400 try: res = agent_executor.invoke({"input": user_input}) return jsonify({"status": "success", "result": res['output']}) except Exception as e: return jsonify({"status": "error", "message": str(e)}), 500
if __name__ == '__main__': print(f"Agent 監聽中: 0.0.0.0:5000 (允許來自 WSL2 容器連線)") # 關鍵:host='0.0.0.0' 允許外部 (包含 WSL2 容器) 連線 app.run(host='0.0.0.0', port=5000)
沒有留言:
張貼留言