🚜iOS 本地 AI:用 LM Studio 把 AnyLanguageModel 接上本地 LLM

我們發現 AnyLanguageModel 還沒有 100% 實現 Foundation Models framework 的功能,但我們已經可以透過它替換成其他模型。本文以 LM Studio 的伺服器功能,讓 App 可以去呼叫 Mac 上的開源 LLM,生成結果。

這是踩坑 AnyLanguageModel 以來的第三篇。AnyLanguageModel 宣稱能從 import FoundationModels 無縫替換成 import AnyLanguageModel

一般來說,這種語法及功能的完全相容套件,會被宣稱為 drop-in replacement。AnyLanguageModel 也確實朝這個目的在開發中。

不過,除了上一篇提到的 Identifiable 問題以外,它目前(0.5.2 版)還有多個問題,使我們無法做到 100% 替換成功。

當我們把 AIPlayground 0.2.0 版的 enabledTraits 加上 anyLanguageModelTrait(具體作法請參考前一篇文章)以後,會發現有幾個 build errors。

這些問題我們可以暫時繞過不管,但我還是列出來如下:

  • #Playground 中,@Generable macro 產生的 memberwise init 方法會無法使用。我已經回報了 Issue #74。這個問題會影響到我們的 Prompt Builder.swift,或是在 #Playground 裡面新增範例 Generable type 的需求,workaround 就是搬到 #Playground 以外來寫
  • @Generable macro 沒有支援 PromptRepresentable,所以會無法放入 PromptBuilder closure
  • @Generable macro 沒有把 property 的 type 正確設定成 Type.PartiallyGenerated。這個問題會影響到我們的 extension CatProfiles.PartiallyGenerated: View 實作
  • 傳入 SystemLanguageModel.default 時,幾個 structured output 的範例會出現 type mismatch 的錯誤。也就是說,AnyLanguageModel 還沒有做到完全複製 Foundation Models 該有的行為

除了前一篇提到的 Identifiable 問題我發了 PR、上述第一個問題有回報 Issue 以外,其餘問題我讀了程式碼以後,無法在短時間內解決,所以只好先修改 AIPlayground。當你在專案中看到 // XXX,就表示是 workaround。

AnyLanguageModel 目前仍在積極開發 structured output 與 @Generable 相關的功能。雖然上述問題說不定很快就能獲得解決,至少現在還沒。

那麼,到這篇文章發表為止,AnyLanguageModel 有什麼真正的實用性嗎?

有的。到目前為止,我們可以先暫時放棄 structured output。先來試試看串接第三方的模型,並且先專注在接收純文字的回傳。

讓我們先調整一下的 AIPlayground,好讓替換模型更方便。

讓 BenchmarkingView 支援顯示 Markdown 文字

讓模型生成的字串顯示在 SwiftUI 很容易,因為我們已經有 BenchmarkingView 了。

extension String: GenerableView {}
extension String: @retroactive View {
  public var body: some View {
    if let text = try? AttributedString(
      markdown: self,
      options: .init(interpretedSyntax: .inlineOnlyPreservingWhitespace)
    ) {
      Text(text)
    } else {
      Text(self)
    }
  }
}

多數語言模型會回傳 Markdown 格式,所以如果字串能被正確解析,就照格式來顯示。

值得注意的是這行:extension String: @retroactive View

  • 照理來說我們應該要 extension String.PartiallyGenerated,不過它們是相同的型別,所以就寫 extension String 即可
  • 加上 @retroactive,是因為我們「宣告一個不歸我們掌控的 type,去 conform 某個不歸我們掌控的 protocol」。通常來說這是個壞主意,因為我們不知道那個 type 未來會不會也宣告支援該 protocol,並且有不同的實作,那就有可能會造成意外。Swift 6 開始會警告我們。要手動標記 @retroactive 才能消除警告。詳情請參考 SE-0364

除了增加顯示 Markdown 字串以外,我還進行了一些調整:

  • BenchmarkingView 增加顯示錯誤訊息的功能
  • BenchmarkingViewBenchmarkingGenerator 支援傳入模型參數 any LanguageModel,方便我們使用 AnyLanguageModel 提供的各種模型設定

以上範例程式碼的更新,我已經上傳到 AIPlayground0.3.0 版了。

使用 LM Studio 作為本地 LLM 伺服器

為了讓讀者體會 AnyLanguageModel 替換成其他模型的效果,我認為最快速且免費的方式,是在 Mac 上安裝一個能執行本地語言模型的程式,並且作為本地端伺服器的方式來使用

這次,我設計了一個 weak self podcast 節目的假想逐字稿生成範例。雖然我從來不會想要用 AI 工具來生成節目大綱,但是看看語言模型對於我熟悉的東西會生出什麼東西來,還滿好玩的。

讀者當然可以替換成自己想要的 instructions 與 prompt。

先看最終效果: