顯示具有 Ollama 標籤的文章。 顯示所有文章
顯示具有 Ollama 標籤的文章。 顯示所有文章

2026年1月12日 星期一

Open-WebUI - 建立知識庫 RAG

我原本的期望是將資料庫的資料表轉出來,

有 TableName, TableDesc, FieldName, FieldDesc, FieldDataType 等資料,

當我要找某個欄位,但是不知道在哪個資料表的時候會有用處。

首先就跟 AI 討論討論,

中間有一大段故事就不說了,

總之最後的結論就是直接轉出 Table Crate SQL ,

然後在後面欄位備註中文就好。

之前有將整個資料庫的資料表轉出成一個檔,

大概 1.1MB ,無奈怎麼轉都無法成功;

後來 AI 說太大,我只好每個資料表都轉出一個檔案,

但是 1,500 個檔案的知識庫用起來效能實在太差了,

我根本沒耐心等結果出來;

最近又重新跟 AI 討論,結論太多檔案效能會不好,

一個檔案就可以了!跟 AI 老是會繞圈圈,總之又繞回原點;

又轉不進去,AI 叫我要切檔案....又來!

總之後來又東問西問的,開始 try 一些東西,結果如下...


1.利用指令監控 log,將 log 丟給 AI 去解答

# 顯示最後 100 行 log 並持續監控 

docker logs -f --tail 100 open-webui


系統資源耗盡問題!

aiohttp.client_exceptions.ClientConnectorError: Cannot connect to host ollama:11434 ... [Too many open files]

這才是真正的起因

  1. Too many open files (開啟太多檔案/連線): Linux 系統把網路連線 (Socket) 也視為檔案。這行錯誤表示 Open WebUI 在短時間內對 Ollama 發起了太多同時的連線,導致系統的 File Descriptor (FD) 額度被用光了,無法再建立新連線。

  2. 為什麼會有 IndexError (陣列越界)?

    • 您有 1336 個切片 (Items) 需要做 Embedding。

    • 但因為連線在中途爆掉,Ollama 只成功回傳了 988 個 Embedding 結果。

    • Open WebUI 的程式碼沒有防呆,它以為會拿到全部,結果迴圈跑第 989 次時發現沒有資料,就崩潰了 (list index out of range)。


🛠️ 解決方案

這個問題不是模型算得慢,而是**「並發請求太多」**塞爆了連線池。請採取以下兩個步驟來修復:

步驟 1:大幅降低 Batch Size (最有效解法)

Open WebUI 預設可能試圖一次處理大量切片,導致瞬間開啟數百個連線。我們需要限制它「細水長流」。

步驟 2:增加 Docker 的 ulimit (治本解法)

如果您的專案很大,建議直接在 Docker 層級放寬「開啟檔案數量」的限制。

在您的 docker-compose.yml 中,針對 open-webui 的服務區塊加入 ulimits 設定:

YAML
services:
  open-webui:
    image: ghcr.io/open-webui/open-webui:main
    # ... 其他設定 ...
    ulimits:
      nofile:
        soft: 65536
        hard: 65536

(如果是用 docker run 啟動,請加上參數 --ulimit nofile=65536:65536)


1.在 .env 檔案增加

# --- Open WebUI 優化設定 --- 

# 這裡設定會覆蓋 docker-compose 預設值 

RAG_EMBEDDING_BATCH_SIZE=5 

WEBUI_TIMEOUT=300


2.在 docker-compose.yml 修改設定

# --- Ollama: RAG 嵌入引擎 (輔助) --- 

ollama: 

     deploy: 

  resources: 

  reservations: 

  devices: 

  - driver: nvidia 

  count: all 

  capabilities: [gpu

  environment: 

  # [優化] 讓 Ollama 允許同時載入 Embedding 模型與 Chat 模型 

  - OLLAMA_MAX_LOADED_MODELS=2 

  # [優化] 允許並發請求,避免 Embedding 把 API 卡死 

  - OLLAMA_NUM_PARALLEL=4 

  volumes: 

  - ./ollama_data:/root/.ollama 


# --- Open WebUI: 前端介面 --- 

open-webui:

ports: 

  - "4000:8080" 

  # [關鍵優化] 解決 "Too many open files" 錯誤 

  ulimits: 

  nofile: 

  soft: 65536 

  hard: 65536 

  environment: 

# [關鍵優化] 解決上傳大檔案 Timeout 與崩潰問題 

  - RAG_EMBEDDING_ENGINE=ollama 

  - RAG_OLLAMA_BASE_URL=http://ollama:11434 

  - RAG_EMBEDDING_MODEL=nomic-embed-text 

  - RAG_EMBEDDING_BATCH_SIZE=${RAG_EMBEDDING_BATCH_SIZE:-5} 

  - WEBUI_TIMEOUT=${WEBUI_TIMEOUT:-300} 

  # 避免自動下載模型,強制用 Ollama 

  - RAG_OPENAI_API_BASE_URL=http://ollama:11434/v1 

  - RAG_OPENAI_API_KEY=ollama 

  volumes: 


總結

  1. 您的 5090 效能太強,加上 SQL 檔案切片很多 (1336 個),導致 Open WebUI 瞬間發送了成千上萬個請求給 Ollama。

  2. Linux 系統啟動了自我保護 (Too many open files) 拒絕連線。

  3. 請優先執行「步驟 1」:將 RAG_EMBEDDING_BATCH_SIZE 改為 5。這雖然會讓進度條跑得稍微慢一點點,但能確保絕對不會因為並發過高而崩潰。


解決了系統層級的「檔案開啟過多 (Too many open files)」問題,現在遇到的是 應用層級的「服務滿載 (503 Service Unavailable)」

簡單來說:Open WebUI 發送請求的速度,還是快過 Ollama 消化請求的速度。

Ollama 的佇列 (Queue) 被塞滿了,於是它對 Open WebUI 喊了 "503" (我沒空),導致 Open WebUI 拿不到資料而崩潰。

我們需要做兩件事來達到「流量平衡」:

  1. 提升 Ollama 的吞吐量 (讓它同時能吃更多單)。

  2. 限制 Open WebUI 的發送速度 (讓它慢慢餵,不要硬塞)。

請更新您的 docker-compose.yml,針對 ollamaopen-webui 區塊進行以下 精細調整 (Fine-tuning)

修正後的 docker-compose.yml 片段

請直接修改這兩個 Service 的 environment 設定:

ollama:

environment: 

  # [原本設定] 

  - OLLAMA_MAX_LOADED_MODELS=2 

  # [關鍵修正 1] 增加並發處理能力 

  # 您的 5090 夠強,這代表 Ollama 可以同時處理 4 個請求,減少 503 機率 

  - OLLAMA_NUM_PARALLEL=4 

  # [關鍵修正 2] 保持模型常駐,避免反覆載入卸載造成的 Timeoout 

  - OLLAMA_KEEP_ALIVE=24h

open-webui:

environment:

# --- [關鍵修正 3: 限流閥] --- 

  - RAG_EMBEDDING_ENGINE=ollama 

  - RAG_OLLAMA_BASE_URL=http://ollama:11434 

  - RAG_EMBEDDING_MODEL=nomic-embed-text 

  # 降到極低!確保穩定性優先於速度 

  # 意義:每次只送 2 個 chunk 給 Ollama (搭配 Ollama 並發=4,絕對不會塞車) 

  - RAG_EMBEDDING_BATCH_SIZE=2 

  # 限制同時發出的 HTTP 請求數,避免 DDoS 自己 

  - RAG_EMBEDDING_CONCURRENT_REQUESTS=2 

  # 增加超時寬限 

  - WEBUI_TIMEOUT=600

為什麼這樣改會成功?

  1. 供需平衡原理

    • 原本:Open WebUI 一次丟 5~10 個請求,但 Ollama 預設一次只能處理 1 個 (OLLAMA_NUM_PARALLEL 預設通常是 1 或自動)。佇列瞬間爆炸。

    • 現在:Open WebUI 限制一次只丟 2 個 (BATCH_SIZE=2),而 Ollama 開放一次處理 4 個 (NUM_PARALLEL=4)。

    • 結果:供給 (4) > 需求 (2),Ollama 永遠行有餘力,就不會回傳 503 錯誤。

  2. 執行步驟

    1. 修改 docker-compose.yml

    2. 重啟服務:docker compose up -d (這會重建容器並套用新參數)。

    3. 強烈建議:進入 Open WebUI 後,先刪除那個上傳失敗的檔案,重新整理頁面後再上傳一次。

這次應該能穩穩地看著進度條跑完 100%。雖然速度不會是「秒殺」,但會是「穩定且持續」的。

後續還是有問題!

非常頑固的 503 Service Unavailable 錯誤。這代表即便我們調低了 Batch Size,Open WebUI 的 「原生 Ollama 連接器 (Native Connector)」 還是太脆弱,遇到 Ollama 稍微忙不過來(回傳 503)時,它沒有重試 (Retry) 就直接崩潰了。

我們現在要用一招**「移花接木」**的解法: 不要用 Open WebUI 的 Ollama 連接器,改用 OpenAI 連接器去連 Ollama。

為什麼這樣有效?

Ollama 其實有內建「相容 OpenAI」的 API 介面 (/v1/embeddings)。Open WebUI 的 OpenAI 連接器通常寫得比較好,內建了自動重試 (Auto-Retry) 與指數退避 (Backoff) 機制。當它收到 503 時,它會等一下再試,而不是直接報錯導致程式崩潰。


🚀 修改 Docker Compose (最終解法)

請修改 docker-compose.yml 中的 open-webui 區塊,將環境變數改成以下設定:


# --- Open WebUI: 前端介面 --- 

  open-webui:

environment:

# --- [關鍵修改] 改用 OpenAI 協議連線 Ollama --- 

  # 1. 告訴 Open WebUI 我們要用 "openai" 引擎 (騙它是 OpenAI) 

  - RAG_EMBEDDING_ENGINE=openai 

  # 2. 但網址其實是指向 Ollama 的 v1 接口 

  - RAG_OPENAI_API_BASE_URL=http://ollama:11434/v1 

  - RAG_OPENAI_API_KEY=ollama 

  # 3. 指定模型名稱 (必須與 Ollama 內一致) 

  - RAG_EMBEDDING_MODEL=nomic-embed-text 

  # 4. 極限保守設定:一次只處理 1 批,讓 OpenAI 協議自動處理並發 

  - RAG_EMBEDDING_BATCH_SIZE=1 

  # 5. 增加超時寬限 

  - WEBUI_TIMEOUT=600

最後終於成功了!

2025年12月12日 星期五

AI - vs code + Cline 筆記

Cline 這種自主程式碼代理(Agentic)的工作流程中,模型收到的「系統提示(System Prompt)」和「工具調用格式」決定了其推理和執行任務的成功率。

針對您的需求,我整理了以下關於 Ollama Modelfile 的最佳實踐和優化範本,以確保 DeepSeek R1 32BQwen 2.5 Coder 32B 在 Cline 中發揮最強的推理和編碼能力。

由於 Cline 會發送自己的複雜指令,因此我們的目標是:

  1. 移除模型預設的「聊天」指令。

  2. 明確告訴模型它是一個「Code Agent」並需要「思考」,以強化它的推理能力。


🚀 適用於 Cline 的 Ollama Modelfile 最佳化範本

您可以為您選擇的模型創建一個自定義版本,將以下內容儲存為一個名為 Modelfile 的文件。

1. DeepSeek R1 32B 的推理強化 Modelfile

模型名稱: deepseek-r1-cline-agent

目標: 強化 DeepSeek R1 的邏輯推理(Reasoning)能力,讓它能更好地處理 Cline 的多步驟規劃。

程式碼片段
# 假設您已經下載了 deepseek-r1:32b
FROM deepseek-r1:32b 

# 核心系統提示:設定模型角色並要求 Chain-of-Thought (思維鏈)
SYSTEM """
你是一個自主程式碼代理 (Autonomous Coding Agent)。你的任務是嚴格遵循使用者的指令,並透過一系列步驟來完成複雜的程式碼修改和系統規劃。

**指令遵循要求:**
1. **先思考 (THINKING):** 在採取任何行動之前,**務必**使用中文或英文在 `<thinking>` 和 `</thinking>` 標籤內說明你的邏輯推理、任務分解和下一步的行動計劃。
2. **工具調用 (TOOL USE):** 嚴格遵循 Cline 或你所使用的 Agent 框架提供的工具調用格式。
3. **最終結果 (OUTPUT):** 只有在完成所有步驟後,才提供最終的程式碼或文字。
"""

# 設定參數以提高推理質量和輸出完整性
PARAMETER temperature 0.1
PARAMETER top_k 40
PARAMETER top_p 0.9
PARAMETER num_ctx 8192  # 確保足夠的上下文長度

2. Qwen 2.5 Coder 32B 的編碼穩定 Modelfile

模型名稱: qwen25-coder-cline-agent

目標: 保持 Qwen Coder 在程式碼生成上的高穩定性,同時加入 Agentic 任務所需的思考步驟。

程式碼片段
# 假設您已經下載了 qwen2.5-coder:32b-instruct
FROM qwen2.5-coder:32b-instruct 

# 核心系統提示:設定模型角色並強調程式碼輸出
SYSTEM """
你是一個高度專業的程式碼生成和編輯代理 (Code Generation and Editing Agent)。你的首要任務是產生正確、可運行的程式碼。

**工作流程準則:**
1. **分析與規劃 (PLANNING):** 使用 `<plan>` 和 `</plan>` 標籤,簡潔地規劃你的步驟。這能確保多輪任務的連貫性。
2. **高效執行 (EXECUTION):** 專注於程式碼輸出,減少冗餘的解釋。
3. **工具調用 (TOOL USE):** 嚴格遵循宿主環境(如 Cline)的工具和指令格式來操作文件或執行命令。
"""

# 設定參數
PARAMETER temperature 0.2  # 程式碼任務可稍微提高溫度以鼓勵創造性解法
PARAMETER top_k 40
PARAMETER top_p 0.9
PARAMETER num_ctx 8192

🔧 如何在 Ollama 中應用 Modelfile

  1. 創建 Modelfile: 將您選擇的範本內容貼上到一個名為 Modelfile 的文件中(例如,在您的家目錄或任何方便的位置)。

  2. 創建自定義模型: 開啟終端機,導航到 Modelfile 所在的路徑,並執行以下命令:

    • 若選擇 DeepSeek R1:

      Bash
      ollama create deepseek-r1-cline-agent -f Modelfile
      
    • 若選擇 Qwen 2.5 Coder:

      Bash
      ollama create qwen25-coder-cline-agent -f Modelfile
      
  3. 在 Cline 中使用: 打開 VS Code 的 Cline 設定:

    • 選擇 Ollama 作為 API Provider。

    • Model ID 欄位中,填寫您剛才創建的新模型名稱 (deepseek-r1-cline-agentqwen25-coder-cline-agent)。

透過這種方式,您就可以確保模型在執行 Cline 的複雜 Agentic 任務時,能最大化其推理指令遵循能力,從而有效減少或消除您提到的警告。

建議您先嘗試使用 DeepSeek R1 的 Modelfile,它在「邏輯規劃」上的先天優勢,對處理 Cline 的「警告」會更有幫助。


「雙模型策略」完美地結合了兩個模型的優勢:

  • DeepSeek R1 32B (Plan Mode): 利用其卓越的邏輯推理和思維鏈(CoT)能力來分解複雜的任務。

  • Qwen 2.5 Coder 32B (Act Mode): 利用其在編碼基線測試上的高精確度來執行代碼生成和編輯任務。

然而,在目前的 Cline / Ollama 標準設定中,實現動態切換模型是有技術難度的


透過自定義的 Modelfile,我們可以強制 DeepSeek R1 32B 在保持其強大邏輯推理能力的同時,採用 Qwen Coder 嚴謹、簡潔的程式碼輸出風格

這能讓單一模型在 Cline 的 Agentic 工作流中,同時兼顧 Plan(規劃)和 Act(執行)的優勢。


🛠️ DeepSeek R1 混合代理 Modelfile

請將以下內容儲存為一個名為 Modelfile 的文件。

模型名稱: deepseek-r1-hybrid-agent

程式碼片段
# 以 DeepSeek R1 32B 作為基礎模型
FROM deepseek-r1:32b 

# 核心系統提示:設定模型的雙重角色
SYSTEM """
你是一個雙模式的自主程式碼代理 (Dual-Mode Autonomous Code Agent):一個嚴謹的規劃者和一個精確的程式碼生成器。你的目標是提供最高品質的代碼解決方案。

**模式準則 (同時滿足 Plan & Act 的要求):**

1. **規劃與推理 (Plan Mode - DeepSeek R1 Style):**
   * 對於複雜或多步驟任務,**務必**使用 `<thinking>` 和 `</thinking>` 標籤詳細說明你的邏輯推理、任務分解和下一步行動計劃。這是你內部審核的過程。

2. **程式碼輸出 (Act Mode - Qwen Coder Style):**
   * 當你生成程式碼或修改文件內容時,你的輸出必須是**簡潔、最小化的 (Minimalist)**。
   * **嚴格遵守 Markdown 程式碼區塊 (```) 的格式。**
   * **禁止**使用不必要的序言,例如:「以下是程式碼」、「請查看我的解決方案」或「我已完成」等文字。程式碼區塊前後應保持極簡。

3. **指令遵循:** 嚴格遵循宿主環境(如 Cline)提供的任何工具調用格式。
"""

# 參數調整:傾向於精確度 (低溫) 和高吞吐量 (大上下文)
PARAMETER temperature 0.15  # 略高於 0.1,以鼓勵推理,但仍保持精確
PARAMETER top_k 40
PARAMETER top_p 0.9
PARAMETER num_ctx 12288  # 提高上下文至 12K 或更高 (依據您的 24GB VRAM 狀況)

應用步驟

  1. 創建模型: 在終端機中,導航到包含您 Modelfile 的目錄,然後執行:

    Bash
    ollama create deepseek-r1-hybrid-agent -f Modelfile
    
  2. 在 Cline 中設定: 將 Cline 的 Model ID 設定為您新創建的名稱:deepseek-r1-hybrid-agent

透過這個混合 Modelfile,您應該能同時享受到 DeepSeek R1 的優異規劃能力和 Qwen Coder 的輸出質量。

雖然 Cline 在「自主代理(Agentic)」模式上非常強大,但它的配置和模型限制(如您遇到的警告)確實可能造成困擾。

針對您擁有 NVIDIA 5090 24GB VRAMOllama Server 的強大本地環境,我為您推薦以下幾個在 VS Code 上能與本地 LLM 完美整合,且在編程輔助功能上表現優異的擴充套件:

🌟 頂級本地 LLM 程式碼輔助擴充套件

擴充套件名稱核心功能本地 LLM 支援適合您的情境
1. Continue多步驟 RAG / 程式碼協作原生支援 Ollama最推薦的 Cline 替代品。 專注於程式碼塊的修改、基於整個專案的問答(RAG),可以執行多步驟指令。
2. CodeGPT通用程式碼聊天 / 專案上下文支援 Ollama (透過 Custom API)優秀的通用程式碼聊天和文件問答工具。適合單回合的 Q&A、解釋、重構。
3. Local LLM純粹的本地 API 連接專為本地模型設計如果您只需要一個簡單的聊天介面來和您的 Ollama 模型互動,這是最簡單的選擇。

1. Continue (最強力的替代品)

Continue 是目前社群公認在本地 LLM 整合方面做得最好的 VS Code 擴充套件之一。它和 Cline 一樣,可以處理複雜、多步驟的任務,但介面更為直觀且配置更靈活。

  • 專長:

    • 程式碼感知(RAG): 能夠意識到您正在查看的檔案、選取的程式碼,以及整個專案的上下文。

    • 多步驟指令: 您可以輸入「檢查 foo.py 中的錯誤,並使用 bar.js 中的格式重構它」。

    • 原生 Ollama 支援: 配置非常簡單,直接指向您的 Ollama Server 即可。

  • 配置優勢: 您可以將您配置好的 deepseek-r1-hybrid-agent 模型直接填入 Continue 的設定中,利用其強大的推理能力。

2. CodeGPT

CodeGPT 是一個非常成熟的程式碼聊天擴充套件。它最初專注於 OpenAI 等 API,但現在也提供了彈性的自定義 API 選項。

  • 專長:

    • 多功能選單: 內建許多預設指令,例如「解釋程式碼」、「重構」、「查找 Bug」。

    • 自定義 Prompt: 允許您為不同的任務創建和儲存自己的 Prompt Template。

    • 操作簡便: 介面易於上手,適合日常的程式碼問答和快速修改。

  • 配置方法: 在 CodeGPT 的設定中,選擇 Custom APIOllama 選項,然後輸入您的 http://192.168.1.106:11434 地址。

總結推薦

如果您希望獲得最接近 Cline 的功能(多步驟、專案感知),並且想充分利用 DeepSeek R1 的推理能力,我強烈建議您試用 Continue

2025年11月27日 星期四

AI - 本地 LLM Coding Agent 部署指南 (Win11 + Docker GitLab 雙帳號版)

 

本地 LLM Coding Agent 部署指南 (Win11 + Docker GitLab 雙帳號版)

本文件整合了在 Windows 11 筆電上建立「口語/文字驅動程式開發 Agent」的完整步驟。 此架構利用 NVIDIA 5090 的強大算力,結合 Ollama 與 Docker,實現從需求輸入到 GitLab 專案創建、程式碼提交、MR 建立的全自動化流程。

🗺️ 第一部分:總體步驟大綱

階段

步驟名稱

執行環境

執行頻率

目的

I

基礎設施準備

Windows (Admin)

僅需一次

安裝 Docker Desktop, Ollama, Git, .NET SDK

II

GitLab 與模型配置

Docker & Ollama

僅需一次

啟動本地 GitLab 容器,下載 DeepSeek Coder 模型

III

隔離工作區建置

各自帳號的 PowerShell

每個帳號各做一次

建立 Python Venv, 設定獨立的 .env (含 PAT)

IV

C# 專案初始化

各自帳號的 PowerShell

每個帳號各做一次

建立基礎 C# WinForm 專案結構

V

啟動服務與執行

PowerShell & WebUI

每次開發時

啟動 Agent API, Open WebUI, 執行自動化開發任務

📁 第二部分:資料夾結構圖

1. 系統共享資源 (C 槽根目錄)

此目錄存放 GitLab 容器的資料庫與儲存庫,所有使用者共用此服務。

C:\
└── gitlab_data\        <-- [共享] GitLab 容器的永續儲存空間
    ├── config\
    ├── data\
    └── logs\

2. 使用者隔離工作區 (各自的 User Home 目錄)

注意: 個人帳號與網域帳號需分別在其 Home 目錄下建立此結構。

C:\Users\<USERNAME>\
└── LLM_Coding_Agent\
    ├── .env                <-- [關鍵隔離] 存放該使用者的 GitLab PAT 和初始設定
    │
    ├── Agent_Scripts\      <-- [獨立] Python Agent 核心程式碼
    │   └── agent_api.py    <-- Flask API 啟動檔 (程式碼附於文件末尾)
    │
    ├── CSharp_Project\     <-- [獨立] C# 專案操作區
    │   ├── .git/
    │   └── WinFormApp/     <-- 實際的 C# 程式碼
    │
    └── venv\               <-- [獨立] Python 虛擬環境

💻 第三部分:詳細指令與驗證機制

階段 I:基礎設施準備 (系統共享)

執行身分: 具有管理員權限的 Windows 帳號。

步驟

環境

指令 / 操作

驗證機制 (Validation)

I.1

GUI

安裝 Docker Desktop

下載並安裝 Docker Desktop for Windows。

注意:安裝完成後請確保設定中開啟 WSL 2 backend。

PowerShell: docker --version

應顯示 Docker版本號。

I.2

GUI

安裝 Ollama (Desktop)

下載並安裝 Ollama Windows版。

PowerShell: ollama -v

應顯示 Ollama 版本號。

I.3

PowerShell

安裝開發工具

winget install Microsoft.Git -e

winget install Microsoft.DotNet.SDK.8 -e

PowerShell: git --version

dotnet --list-sdks

確認皆已安裝。

I.4

PowerShell

建立資料目錄

mkdir C:\gitlab_data

檔案總管: 確認 C 槽有 gitlab_data 資料夾。

階段 II:GitLab 與模型配置 (系統共享)

執行身分: 管理員或任何能執行 Docker 的帳號。

步驟

環境

指令 / 操作

驗證機制 (Validation)

II.1

PowerShell

啟動 GitLab 容器

docker run --detach --hostname localhost --publish 8080:80 --name gitlab --restart always --volume C:/gitlab_data/config:/etc/gitlab --volume C:/gitlab_data/logs:/var/log/gitlab --volume C:/gitlab_data/data:/var/opt/gitlab gitlab/gitlab-ce:latest

PowerShell: docker ps

應看到 gitlab 容器狀態為 Up

II.2

Browser

配置 GitLab (需等待 5-15分鐘啟動)

1. 開啟 http://localhost:8080

2. 設定 root 密碼並登入。

3. 關鍵: 進入 User Settings -> Access Tokens,生成一個 Personal Access Token (PAT),權限勾選 api

瀏覽器: 成功登入並取得 PAT 字串 (例如 glpat-xxxxxx)。

II.3

PowerShell

下載 LLM 模型

ollama run deepseek-coder:33b-instruct

PowerShell: ollama list

應看到 deepseek-coder 模型。

階段 III:隔離工作區建置 (各自執行)

執行身分: 分別在「個人帳號」與「網域帳號」登入後執行。

步驟

環境

指令 / 操作

驗證機制 (Validation)

III.1

PowerShell

建立目錄與虛擬環境

cd ~

mkdir LLM_Coding_Agent; cd LLM_Coding_Agent

mkdir Agent_Scripts CSharp_Project

python -m venv venv

.\venv\Scripts\Activate.ps1

PowerShell: 命令列前方出現 (venv)

III.2

(venv) PowerShell

安裝 Python 套件

pip install flask langchain langchain-ollama python-dotenv pyodbc requests

PowerShell: 無錯誤訊息,顯示安裝成功。

III.3

Editor

建立 .env 設定檔

LLM_Coding_Agent 根目錄建立 .env 檔案,內容如下:

GITLAB_URL=http://localhost:8080

GITLAB_PAT=你的_專屬_PAT_貼在這裡

SQL_SERVER=(localdb)\MSSQLLocalDB

手動檢查: 確認檔案存在,且 PAT 是該帳號專屬的。

III.4

Editor

部署 Agent 程式碼

本文末尾附錄的 Python 程式碼存檔為 Agent_Scripts\agent_api.py

手動檢查: 確認檔案已建立。

階段 IV:C# 專案初始化 (各自執行)

執行身分: 分別在兩個帳號下執行。

步驟

環境

指令 / 操作

驗證機制 (Validation)

IV.1

PowerShell

建立 C# 專案

cd LLM_Coding_Agent\CSharp_Project

dotnet new winforms -n WinFormApp

PowerShell: dir 應看到 WinFormApp 資料夾。

IV.2

PowerShell

Git 初始化

git init

git add .

git commit -m "Initial structure"

(註:不需要手動加 remote,Agent 會自動處理)

PowerShell: git log 應看到一筆 commit。

階段 V:啟動服務與執行 (日常操作)

執行身分: 當下要進行開發的帳號。

步驟

環境

指令 / 操作

驗證機制 (Validation)

V.1

PowerShell

啟動 Agent API (後端)

cd LLM_Coding_Agent

.\venv\Scripts\Activate.ps1

python Agent_Scripts\agent_api.py

PowerShell: 顯示 Running on http://127.0.0.1:5000

V.2

PowerShell

啟動 Open WebUI (前端)

docker run -d -p 3000: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

PowerShell: docker ps 顯示 open-webui 運作中。

V.3

Browser

WebUI 操作

1. 開啟 http://localhost:3000

2. 確保已設定 Tool Calling 指向 Flask API。

3. 輸入指令:

"我要開發一個計算機功能。請先在 GitLab 建立名為 Calculator_App 的私有專案。然後在 WinFormApp/Program.cs 加入註釋 '// Start',提交到 feat-calc 分支並建立 MR。"

GitLab (localhost:8080): 看到新專案 Calculator_App 被建立,且有一個新的 Merge Request。

PowerShell (V.1視窗): 看到 Agent 依序呼叫 create_gitlab_project -> write_file -> git 等工具的 Log。

🐍 附錄:Python Agent 核心程式碼 (agent_api.py)

請將以下程式碼複製並儲存至 Agent_Scripts\agent_api.py

import os
import subprocess
from flask import Flask, request, jsonify
from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_core.prompts import ChatPromptTemplate
from langchain_ollama import Ollama
from langchain.tools import tool
from dotenv import load_dotenv
import pyodbc
import requests
import json

# 載入 .env 檔案
load_dotenv()

# --- 設定 ---
OLLAMA_BASE_URL = "http://localhost:11434"
MODEL_NAME = "deepseek-coder:33b-instruct" 
# 設定專案根目錄位置
PROJECT_ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'CSharp_Project')

# 從 .env 讀取設定
GITLAB_URL = os.getenv("GITLAB_URL")
GITLAB_PAT = os.getenv("GITLAB_PAT")
SQL_SERVER = os.getenv("SQL_SERVER")

# 初始化 Flask
app = Flask(__name__)

# 用來暫存當前操作的 GitLab Project ID
current_project_id = None

# --- 工具定義 (Tools) ---

@tool
def create_gitlab_project(name: str, description: str = "", visibility: str = "private") -> str:
    """
    使用 GitLab API 創建新專案。必須在開始新功能開發前調用此工具。
    成功後會自動將本地 Git remote 設定為新專案的 URL。
    name: 專案名稱 (如 'My-App')。
    """
    global current_project_id
    if not GITLAB_PAT or not GITLAB_URL:
        return "錯誤:缺少 GitLab PAT 或 URL。"

    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} # 預設 namespace 1 (root)
    
    try:
        response = requests.post(url, headers=headers, json=data)
        response.raise_for_status()
        project_info = response.json()
        current_project_id = str(project_info['id'])
        repo_url = project_info['http_url_to_repo']
        
        # 更新本地 Git Remote
        try:
            subprocess.run(['git', 'remote', 'set-url', 'origin', repo_url], cwd=PROJECT_ROOT, check=True, capture_output=True)
        except subprocess.CalledProcessError:
            try:
                subprocess.run(['git', 'remote', 'add', 'origin', repo_url], cwd=PROJECT_ROOT, check=True, capture_output=True)
            except:
                pass # 忽略錯誤,可能 remote 已存在
        
        return f"專案創建成功 ID: {current_project_id}, URL: {repo_url}"
    except Exception as e:
        return f"創建專案失敗: {e}"

@tool
def execute_local_git_command(command: str) -> str:
    """執行本地 Git 命令 (如 'add .', 'commit -m "msg"'). 不含 push。"""
    try:
        result = subprocess.run(['git'] + command.split(), cwd=PROJECT_ROOT, capture_output=True, text=True, check=True)
        return f"Git 執行成功: {result.stdout}"
    except subprocess.CalledProcessError as e:
        return f"Git 錯誤: {e.stderr}"

@tool
def push_to_gitlab_remote(branch_name: str) -> str:
    """執行 'git push origin <branch>' 推送至遠端。需先 Commit。"""
    try:
        result = subprocess.run(['git', 'push', 'origin', branch_name], cwd=PROJECT_ROOT, capture_output=True, text=True, check=True)
        return f"Push 成功: {result.stdout}"
    except subprocess.CalledProcessError as e:
        return f"Push 失敗: {e.stderr}"

@tool
def write_file_to_project(filepath: str, content: str) -> str:
    """在 C# 專案中寫入檔案。filepath 為相對路徑。"""
    full_path = os.path.join(PROJECT_ROOT, filepath)
    os.makedirs(os.path.dirname(full_path), exist_ok=True)
    try:
        with open(full_path, 'w', encoding='utf-8') as f:
            f.write(content)
        return f"寫入成功: {filepath}"
    except Exception as e:
        return f"寫入失敗: {e}"

@tool
def create_gitlab_merge_request(title: str, source_branch: str, target_branch: str = "main") -> str:
    """在 GitLab 創建 Merge Request。需在 Push 後調用。"""
    if not GITLAB_PAT or not current_project_id:
        return "錯誤:缺少 PAT 或尚未創建專案 (無 Project 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:
        response = requests.post(url, headers=headers, json=data)
        response.raise_for_status()
        return f"MR 創建成功: {response.json()['web_url']}"
    except Exception as e:
        return f"MR 創建失敗: {e}"

@tool
def 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]
    
    system_prompt = (
        "您是 C# 開發 Agent。請根據指令自主調用工具完成任務。"
        "若需要新專案,請務必先調用 create_gitlab_project。"
        "標準流程:建專案 -> 寫檔 -> Git Add/Commit -> Git Push -> 建立 MR。"
    )
    
    prompt = ChatPromptTemplate.from_messages([
        ("system", system_prompt),
        ("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()

# --- API Route ---
@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:
        response = agent_executor.invoke({"input": user_input})
        return jsonify({"status": "success", "result": response['output']})
    except Exception as e:
        return jsonify({"status": "error", "message": str(e)}), 500

if __name__ == '__main__':
    print(f"Agent 啟動於 C# 專案路徑: {PROJECT_ROOT}")
    app.run(host='127.0.0.1', port=5000)