標籤: Compiled AI

  • Compiled AI:把提示變成可審計工作流

    📌 本文重點

    • 用 LLM 在編譯階段產生狹義程式碼,執行期不再依賴 LLM
    • 業務邏輯可測試、可審計,安全面積與成本大幅下降
    • 特別適合高要求、高流量且規則穩定的企業級工作流

    在多數專案裡,我們會每個請求都叫一次模型,把非結構化的 prompt 丟給 LLM,期待它「大致照規則」行事。問題是:結果不穩、難回溯、成本高,還容易被 prompt injection 玩壞。Compiled AI 要解的是這個痛點:

    把原本寫在 prompt 里的業務邏輯,在編譯階段用 LLM 產生狹義程式碼,透過型別檢查、靜態分析與測試驗收,一旦通過就當成普通程式部署,執行期不再依賴 LLM

    對開發者的直接好處:

    • 結果可預測且可審計:輸出由 deterministic code 決定,不是每次抽樣
    • 安全面縮小:執行期不再接受任意自然語言指令
    • 成本與延遲大幅下降:大量交易共用同一份已編譯邏輯,token 成本被攤薄

    💡 關鍵: Compiled AI 把「每次都叫 LLM」改成「先編譯一次,再重複執行」,大幅提升穩定性並攤平成本。


    重點說明

    1. 架構:LLM 只在「編譯階段」出現

    典型 Compiled AI 架構可以拆成三層:

    1. 模板/SDK 層(受控框架)
    2. 你先定義好已驗證的 workflow 模板與 SDK,例如:HTTP 呼叫、DB 查詢、RAG 檢索、權限檢查等
    3. LLM 只能在模板內部空洞生成狹義業務邏輯函式,例如 select_customer_segment()build_rag_query()

    4. 編譯管線(有 LLM)

    5. Step 1 – Prompt 設計:描述任務、提供 API 規格 & 範例,要求輸出符合特定語言/框架(如 TypeScript + 自家 SDK)
    6. Step 2 – 產生 code artifact:LLM 產生一份或多份候選程式碼
    7. Step 3 – 謝與測試:型別檢查、靜態分析、單元測試/合約測試,不過關就自動 loop 回 LLM 要求修正
    8. Step 4 – 人工審核 + 簽章(視風險):例如醫療或金流流程

    9. 執行期(無 LLM)

    10. 線上請求只執行已編譯出的 code artifact,全程 deterministic,可以完整 log & replay

    核心概念:限制 LLM 的自由度,換取可測試、可版本控管的業務邏輯程式碼


    2. 工作流設計:把一個傳統 RAG / Agent 重構成 Compiled AI

    以一個典型 RAG QA 流程為例:

    使用者問問題 → LLM 根據 prompt 決定如何搜尋 → 呼叫向量庫 → LLM 基於檢索結果生成答案

    在傳統設計中,LLM 負責檢索策略 + 答案生成,兩段都高度隨機。重構成 Compiled AI,可以這樣拆:

    1. 編譯階段
    2. 用 LLM 生成一個狹義函式:build_search_plan(question: string) => SearchPlan,被嵌在已驗證的 RAG 模板中
    3. SearchPlan 僅包含:要查哪個 index、使用哪些 filter、topK、是否需要 fallback 等
    4. 再用 LLM 生成:synthesize_answer(question, contexts) => string,但這個函式需遵守強約束(例如:不可編造、必須引用 context id)

    5. 執行階段

    6. 每次 QA 時:
      • 用 deterministic code 呼叫 build_search_plan() → 打 DB / 向量庫
      • 把檢索到的 contexts 丟給 synthesize_answer()(也是普通函式)
    7. 整個過程再也不直接丟自然語言給 LLM,所有 decision path 都是可測試、可覆盤的程式碼

    8. 版本控管與灰度發布

    9. 每次重新編譯:產出新版本如 rag_workflow_v3.ts
    10. 用 Git tag & CI pipeline 把版本與測試報告綁在一起
    11. 線上:用 routing 或 feature flag 灰度流量,對比 v2 / v3 的成功率、平均延遲、查詢成本

    3. 工程實務:與「每請求叫一次模型」的差異

    基於現有研究與業界實測,Compiled AI 在幾個維度的典型差異:

    • 可靠性(確定性 vs 隨機)
    • 傳統:每次請求都用 sampling(temperature > 0),同一輸入結果可能不同
    • Compiled AI:執行期只跑 deterministic code + DB/RAG,相同輸入必然同樣輸出

    • 安全

    • 傳統:使用者輸入直接進 prompt,prompt injection 面積很大
    • Compiled AI:執行期輸入只餵進已定義的函式參數(string / enum / id 等),無法直接改變控制流

    • 成本與延遲

    • 編譯階段比較貴,但只做一次,之後可在大量請求上攤平
    • 實務上常見:十幾次交易後就開始比傳統架構便宜,長期 token 使用量可降數十倍

    💡 關鍵: 只要請求次數夠多,執行期省下的 token 成本與延遲,會很快抵消一次性的編譯開銷。


    實作範例

    以下用 TypeScript + 假想 SDK 示範一個簡化版 pipeline。

    1. 已驗證模板/SDK

    // sdk.ts - 由你維護的安全 SDK
    
    export type SearchPlan = {
      index: 'faq' | 'policy' | 'logs';
      topK: number;
      filters?: Record<string, string>;
    };
    
    export async function runRagWorkflow(
      question: string,
      buildSearchPlan: (q: string) => SearchPlan,
      synthesizeAnswer: (q: string, ctxs: string[]) => string
    ): Promise<string> {
      const plan = buildSearchPlan(question);
      const contexts = await vectorSearch(plan.index, question, plan.topK, plan.filters);
      return synthesizeAnswer(question, contexts);
    }
    
    async function vectorSearch(
      index: string,
      query: string,
      topK: number,
      filters?: Record<string, string>
    ): Promise<string[]> {
      // 已驗證的檢索實作
      /* ... */
      return [];
    }
    

    2. LLM 產生的狹義業務邏輯(編譯產物)

    在編譯階段,你用一個 prompt 要求模型只實作兩個函式:

    // generated_v3.ts - 由 LLM 產生,但要通過型別檢查與測試
    
    import { SearchPlan } from './sdk';
    
    export function buildSearchPlan(question: string): SearchPlan {
      const q = question.toLowerCase();
    
      if (q.includes('退款') || q.includes('billing') || q.includes('invoice')) {
        return { index: 'policy', topK: 5, filters: { category: 'refund' } };
      }
    
      if (q.includes('錯誤') || q.includes('error code')) {
        return { index: 'logs', topK: 10 };
      }
    
      return { index: 'faq', topK: 8 };
    }
    
    export function synthesizeAnswer(question: string, contexts: string[]): string {
      // 嚴格規定:
      // 1. 不得回答與 contexts 無關內容
      // 2. 必須在文末列出引用的 context 索引
      const summary = summarizeWithRules(question, contexts); // 你事先實作好的工具
      const citations = contexts
        .map((_, i) => `[#${i + 1}]`)
        .join(' ');
    
      return `${summary}\n\n引用來源:${citations}`;
    }
    

    3. 編譯 pipeline(CI 裡跑)

    // compile.ts - 只在 CI / 開發環境執行
    
    import { z } from 'zod';
    import { callLLM } from './llm_client';
    import { execSync } from 'child_process';
    
    const schema = z.object({
      code: z.string(),
    });
    
    async function generateCode() {
      const prompt = `
    你是一個 TypeScript AI,請只輸出一個檔案內容,實作:
    - export function buildSearchPlan(question: string): SearchPlan
    - export function synthesizeAnswer(question: string, contexts: string[]): string
    
    必須符合已存在的型別定義:
    - SearchPlan { index: 'faq' | 'policy' | 'logs'; topK: number; filters?: Record<string, string> }
    
    禁止:
    - 呼叫任何未在註解中允許的函式
    - 使用 eval / new Function
    - 動態匯入
      `;
    
      const raw = await callLLM({
        model: '**gpt-4.1**',
        temperature: 0,
        response_format: { type: 'json_schema', schema },
        messages: [{ role: 'user', content: prompt }],
      });
    
      const { code } = schema.parse(JSON.parse(raw));
      return code;
    }
    
    async function main() {
      const code = await generateCode();
      require('fs').writeFileSync('generated_v3.ts', code, 'utf8');
    
      // 1) 型別檢查
      execSync('npx tsc --noEmit', { stdio: 'inherit' });
    
      // 2) 靜態分析
      execSync('npx eslint generated_v3.ts', { stdio: 'inherit' });
    
      // 3) 測試
      execSync('npm test -- generated_v3.test.ts', { stdio: 'inherit' });
    }
    
    main().catch((err) => {
      console.error(err);
      process.exit(1);
    });
    

    搭配 CI/CD:

    • CI:觸發 compile.ts → 產出 generated_v3.ts → 跑測試 → 產出報表
    • CD:若通過,打 tag rag_workflow_v3,並更新配置讓 5% 流量導到 v3;監控成功率、latency、成本指標再逐步擴大

    💡 關鍵: 把 LLM 產物納入 CI/CD(型別檢查、靜態分析、測試與灰度發佈),就能像管理普通程式碼一樣管理 AI 邏輯。


    建議與注意事項

    1. 業務規則變更頻率

    Compiled AI 適合:

    • 規則相對穩定、但正確性要求高的流程(客服 QA、金融函數呼叫、醫療文件處理)

    不適合:

    • 每天都在改規則、或依賴即時實驗的場景,因為每次改都要走「重新編譯 + 測試」流程
    • 高度探索型、open-ended 任務(創作、策略 brainstorm、UX 研究等)

    實務建議:

    • 把流程拆成兩層:核心決策邏輯用 Compiled AI,外層的探索與創意仍然用即時 LLM

    2. 測試覆蓋不足 = 把錯誤「編進系統」

    Compiled AI 的風險是:一旦編譯出的邏輯有 bug,它會一直穩定地錯

    必做:

    • 為生成函式設計合約測試:同一輸入 → 必須產出指定的 SearchPlan / 函式呼叫序列
    • 對關鍵場景建立 golden QA 測試集,每次編譯都跑完整 regression
    • 對模型產物加上防呆檢查(如:topK 不可大於 50,index 只能是白名單)

    3. 安全與最小權限

    • 模板 / SDK 層要實施 最小權限
    • 不給生成函式直接打 DB 連線,只能呼叫封裝好的 queryCustomerById(id) 等高階 API
    • 不允許檔案寫入、外網 request 等敏感操作
    • 透過靜態分析檢查:是否有 eval、動態 import、直接執行 shell 等 pattern

    4. 成本模型與「何時值得編譯」

    可以用簡單估算:

    • 編譯一次成本:C_compile(幾萬 token)
    • 傳統架構每次請求成本:C_online
    • Compiled AI 每次請求成本:C_compiled(通常 ≪ C_online

    只要請求數 N 滿足:

    C_compile + N * C_compiled < N * C_online

    就值得改成 Compiled AI。研究與實務常見:十幾到幾十次請求後就開始回本,高流量業務會很划算。

    5. 與現有專案整合的落地步驟

    1. 先挑一個穩定且高流量的子流程(例如:FAQ RAG 檢索策略、金融 function calling 的參數整理)
    2. 為它抽象出一個窄介面函式(例如 buildSearchPlanprepareFunctionCallParams
    3. 建立 minimal 的編譯 pipeline(LLM → 生成 code → tsc + 測試)
    4. 先 offline 對比:新老流程在測試集上的正確率 / 成本
    5. 小流量灰度上線,配合 metrics 監控,逐步拓展到更多流程

    結論:Compiled AI 的核心不是「用 LLM 寫程式」,而是把 prompt 中模糊的規則,轉成可測試、可簽章、可審計的程式碼工件。對需要穩定性、安全與成本控制的企業工作流,特別是 RAG、function calling、金融與醫療場景,是非常值得實驗的架構升級方向。

    🚀 你現在可以做的事

    • 在現有系統中挑一段高流量、規則穩定的 RAG 或 function calling 流程,先抽象出一個窄介面函式如 buildSearchPlan
    • 建一個最小可行的編譯 pipeline:用 LLM 產生 TypeScript 函式 → 跑 tsc + 單元/合約測試 → 輸出 generated_v1.ts
    • 用 feature flag 灰度導入新流程,監控正確率、延遲與 token 成本,評估轉為 Compiled AI 的回本點