Logo
Published on

第一次學 Kotlin Koog AI 就上手 Day 11:讓 AI 讀懂文件:文件內容處理

在 Day 10 我們探索了 Koog 框架的圖像處理能力,學會了如何讓 AI 理解和分析圖片內容。今天我們將把焦點轉向另一個重要的多媒體領域:文件處理。在現代工作環境中,我們每天都會接觸到大量的 PDF 檔案、文字文件和報告,如何讓 AI 協助我們快速理解和分析這些文件內容,已經成為提升工作效率的關鍵技能。本篇將深入介紹 Koog 的文件處理功能,讓您學會如何建立 AI 的文件分析工具

為什麼選擇文件處理?

在 AI 應用的發展過程中,文件處理扮演著非常重要的角色。無論是法律文件分析、學術論文摘要、商業報告解讀,還是技術文檔的快速理解,都需要 AI 能夠準確地讀取和理解文件內容

Koog 框架提供了簡潔而強大的文件處理 API,讓開發者可以輕鬆地將各種格式的文件傳遞給 AI 模型進行分析

核心概念 - attachments 語法

在 Koog 框架中,處理文件需要使用 attachments 區塊配合 binaryFile 函數,並指定正確的 MIME 類型

user {
    text("請分析這份文件")
    attachments {
        binaryFile(Path(filePath), mimeType) // 核心語法
    }
}

MIME 類型對應

正確的 MIME 類型設定對於文件處理至關重要

/**
 * 根據文件副檔名取得對應的 MIME 類型
 * @param filePath 文件路徑
 * @return 對應的 MIME 類型字串
 */
fun getMimeType(filePath: String): String {
    val extension = kotlin.io.path.Path(filePath).fileName.toString().lowercase().substringAfterLast('.')
    return when (extension) {
        "pdf" -> "application/pdf"
        "txt" -> "text/plain"
        "md" -> "text/markdown"
        else -> "application/octet-stream"
    }
}

支援的檔案格式

Koog 目前支援以下文件格式

  • PDF (.pdf):最常見的文件格式,支援文字和圖像混合內容
  • 純文字 (.txt):基本的文字文件
  • Markdown (.md):技術文檔常用的標記語言格式

實作 DocumentSummarizer 文件分析器

讓我們建立一個實用的 DocumentSummarizer 類別,提供多種文件分析功能

class DocumentSummarizer(private val client: GoogleLLMClient) {

    /**
     * 根據文件副檔名取得對應的 MIME 類型
     * @param filePath 文件路徑
     * @return 對應的 MIME 類型字串
     */
    private fun getMimeType(filePath: String): String {
        val extension = kotlin.io.path.Path(filePath).fileName.toString().lowercase().substringAfterLast('.')
        return when (extension) {
            "pdf" -> "application/pdf"
            "txt" -> "text/plain"
            "md" -> "text/markdown"
            else -> "application/octet-stream"
        }
    }

    /**
     * 產生文件摘要
     * @param filePath 文件路徑
     * @return 文件摘要內容
     */
    suspend fun summarizeDocument(filePath: String): String {
        val response = client.execute(
            prompt = prompt {
                user {
                    text("請為這份文件提供簡潔的摘要,重點包括:")
                    text("1. 主要主題和目的")
                    text("2. 核心內容概述")
                    text("3. 重要結論或建議")
                    text("請用繁體中文回答,字數控制在 300 字以內。")
                    attachments {
                        binaryFile(Path(filePath), getMimeType(filePath))
                    }
                }
            },
            model = GoogleModels.Gemini2_5Flash
        )

        return response.first().content
    }

    /**
     * 提取文件關鍵要點
     * 從指定文件中提取 5-10 個最重要的關鍵要點,以條列式呈現
     * @param filePath 文件路徑,支援 PDF、TXT、MD 等格式
     * @return 格式化的關鍵要點列表字串
     */
    suspend fun extractKeyPoints(filePath: String): String {
        val response = client.execute(
            prompt = prompt {
                user {
                    text("請從這份文件中提取 5-10 個最重要的關鍵要點。")
                    text("請以條列式呈現,每個要點簡潔明確。")
                    text("格式:")
                    text("• 要點 1")
                    text("• 要點 2")
                    text("...")
                    attachments {
                        binaryFile(Path(filePath), getMimeType(filePath))
                    }
                }
            },
            model = GoogleModels.Gemini2_5Flash
        )

        return response.first().content
    }

    /**
     * 分析文件結構
     * 分析文件的組織方式、章節結構、資訊邏輯順序等
     * @param filePath 文件路徑,支援各種格式的結構化文件
     * @return 包含文件類型、結構分析的詳細描述
     */
    suspend fun analyzeStructure(filePath: String): String {
        val response = client.execute(
            prompt = prompt {
                user {
                    text("請分析這份文件的結構和組織方式,包括:")
                    text("1. 文件類型和性質")
                    text("2. 主要章節或段落結構")
                    text("3. 資訊的邏輯順序")
                    text("4. 是否包含圖表、表格等特殊元素")
                    attachments {
                        binaryFile(Path(filePath), getMimeType(filePath))
                    }
                }
            },
            model = GoogleModels.Gemini2_5Flash
        )

        return response.first().content
    }

    /**
     * 問答功能
     * 基於文件內容回答用戶提出的特定問題
     * @param filePath 作為知識來源的文件路徑
     * @param question 用戶的具體問題
     * @return 基於文件內容的 AI 回答,如無相關資訊會明確說明
     */
    suspend fun askQuestion(filePath: String, question: String): String {
        val response = client.execute(
            prompt = prompt {
                user {
                    text("請根據這份文件回答以下問題:")
                    text(question)
                    text("請確保回答基於文件內容,如果文件中沒有相關資訊,請明確說明。")
                    attachments {
                        binaryFile(Path(filePath), getMimeType(filePath))
                    }
                }
            },
            model = GoogleModels.Gemini2_5Flash
        )

        return response.first().content
    }
}

實際使用範例

現在讓我們看看如何使用 DocumentSummarizer 來處理不同類型的文件

下面所使用的檔案,是我拿這篇網路的文章 Perplexity offers to buy Chrome for billions more than it’s raised 整理而成

suspend fun main() {

    // 初始化 AI 客戶端
    val client = GoogleLLMClient(ApiKeyManager.googleApiKey!!)

    // 建立文件分析器
    val documentSummarizer = DocumentSummarizer(client)

    // 文件路徑範例
    val pdfPath = "/Users/cash/Downloads/file.pdf"
    val txtPath = "/Users/cash/Downloads/file.txt"
    val mdPath = "/Users/cash/Downloads/file.md"

    try {
        // 產生 PDF 文件摘要
        println("=== PDF 文件摘要 ===")
        val pdfSummary = documentSummarizer.summarizeDocument(pdfPath)
        println(pdfSummary)

        // 提取文字文件關鍵要點
        println("\n=== 文字文件關鍵要點 ===")
        val txtKeyPoints = documentSummarizer.extractKeyPoints(txtPath)
        println(txtKeyPoints)

        // 分析 Markdown 文件結構
        println("\n=== Markdown 文件結構分析 ===")
        val mdStructure = documentSummarizer.analyzeStructure(mdPath)
        println(mdStructure)

        // 針對文件提問
        println("\n=== 問答 ===")
        val answer = documentSummarizer.askQuestion(
            pdfPath,
            "這份報告的主要建議是什麼?"
        )
        println(answer)

    } catch (e: Exception) {
        println("處理文件時發生錯誤:${e.message}")
        e.printStackTrace()
    }
}

執行 AI 回應內容

目前因為模型會有出現 java.lang.IllegalArgumentException: Model gemini-2.5-flash does not support documents exception,經過我翻原始碼和 Github 發現,這是一個 bug,目前好像沒有任何模型可以 support document,我也看到有人有發了一個 PR 要 fix 掉 AnthropicGoogle 的模型增加可以使用 document

批量文件處理

當我們需要處理多個文件時,可以建立批量處理功能

/**
 * 批量文件處理器
 * 提供批量處理多個文件的功能,支援批量摘要產生和錯誤處理
 */
class BatchDocumentProcessor(private val summarizer: DocumentSummarizer) {

    /**
     * 批量產生文件摘要
     * 依序處理多個文件並產生摘要,包含錯誤處理和 API 頻率限制控制
     * @param filePaths 要處理的文件路徑列表
     * @return 以檔案名稱為鍵、摘要內容為值的對應表,處理失敗的文件會包含錯誤訊息
     */
    suspend fun batchSummarize(filePaths: List<String>): Map<String, String> {
        val results = mutableMapOf<String, String>()

        filePaths.forEach { filePath ->
            try {
                val fileName = File(filePath).name
                println("正在處理:$fileName")

                val summary = summarizer.summarizeDocument(filePath)
                results[fileName] = summary

                println("✓ $fileName 處理完成")

                // 避免 API 頻率限制
                delay(3000)

            } catch (e: Exception) {
                println("✗ 處理 $filePath 時發生錯誤:${e.message}")
                results[filePath] = "處理失敗:${e.message}"
            }
        }

        return results
    }
}

注意事項

在本篇的範例中,為了簡單方便,我們並沒有考慮到使用者上傳文件時可能遇到的各種錯誤情況。在正式的程式碼中,建議一併考慮並實作以下錯誤處理

  • 檔案驗證:檢查文件檔案是否存在
  • 格式檢查:確認檔案格式是否支援(pdf、txt、md 等)
  • 大小限制:確保檔案大小未超過 API 限制
  • MIME 類型設定:正確設定文件的 MIME 類型
  • API 調用錯誤:處理 AI 服務的回應錯誤

總結

今天我們深入探索了 Koog 框架的文件處理功能,學會了如何使用 attachments 區塊配合 binaryFile 來讓 AI 理解各種格式的文件內容。我們了解了 MIME 類型的重要性,並透過建立 DocumentSummarizer 類別實現了文件摘要、關鍵點提取、結構分析和問答等實用功能。這些技能將大大提升我們處理文件的效率,無論是在學術研究、商業分析還是日常工作中都能發揮重要作用

雖然現在的 API 有 Bug,不過,我覺得它下一個版本應該就會修正了吧 😂

在下一篇 Day 12 中,我們將繼續探索 Koog 的多媒體處理能力,學習如何讓 AI 理解和分析音訊內容

參考資料


支持創作

如果這篇文章對您有幫助,歡迎透過 贊助連結 支持我持續創作優質內容。您的支持是我前進的動力!


圖片來源:AI 產生