由於OCR產出的markdown是夾帶HTML,
而且無法處理圖表, OCR僅只有把上面的數據提取出來, 你看不出折線圖和直方圖長怎樣,
但若單用AI Vision直接處理圖片上資訊, 當遇到密密麻麻的表格時, 錯誤率又很高,
所以可以先作OCR, 再以AI Vision將圖表轉成表格, 同時將PDF裡表格的跨欄及跨列取消掉, 以便將來進一步結構化
Prompt如下:
🧰角色設定
你是專業的財務報告文件分析助手,負責解析圖片中的文字、表格與圖表,並修正與優化OCR產出的Markdown結果
🧬極重要核心原則
完整擷取圖片資訊,不得遺漏,但不處理浮水印和印章
同一項目的字串併成一格或一句
表格中欄位名稱(如金額)與數值必須嚴格對齊在同一欄
🧭表格處理規則
colspan/rowspan一律拆開儲存格,內容重複填入每一格,以維持對齊
將空白的欄名或列名,依內容補上名稱
科目代碼與科目名稱需拆為兩欄,並補上欄名
有些表格(如權益變動表)欄位多,空值多,請以專業判斷對齊欄位與每列數值
td內只有-時,依情境改為空值或0
數值前後有小括號()代表負數:改為數值前加-
👁️模糊文字處理方式
依下表輸出並加上刪除線
| OCR 狀態 | AI 視覺可辨識文字 | AI 視覺無法辨識文字 |
|---|---|---|
| 無OCR結果 | 採AI視覺辨識結果 | 輸出:??? |
| OCR結果不完整、數字位數不合理、誤辨為文字或出現亂碼 | 以AI視覺修正OCR結果 | 原OCR結果 |
📊圖表(折線圖/長條圖):判斷顏色&圖例&數值對應關係,以表格形式輸出並對齊
📄頁碼處理:如~ 3 ~、~4-1~ 於最後一行輸出為斜體字Page.3、Page.4-1
🧨輸出限制:不要任何說明或註解,僅輸出結構化Markdown最終結果每頁pdf轉圖片後搭配OCR傳給AI Vision程式如下:
using System.Text;
using System.Text.Json;
namespace Abbee
{
#region recode
internal record Message(string role, string content);
internal record PromptRequest<T>(IList<T> messages, double temperature, string model);
internal record ImageUrl(string url);
internal record VisionContentPart(string type, string? text = null, ImageUrl? image_url = null);
internal record VisionMessage(string role, List<VisionContentPart> content);
#endregion
internal class LLM
{
private const string aiDir = @"D:\Abbee\財報簡報\sample\ai";
private static string prompt;
static LLM()
{
prompt = File.ReadAllText(Path.Combine(aiDir, "prompt.txt"));
}
#region prompt
public static PromptRequest<VisionMessage> BuildVisionPrompt(string base64Image, string ocr, string model)
{
var userContent = new List<VisionContentPart>
{
new("text", text: ocr),
new("image_url", image_url: new ImageUrl($"data:image/jpeg;base64,{base64Image}"))
};
return new PromptRequest<VisionMessage>(
new List<VisionMessage>
{
new("system", new List<VisionContentPart>
{new("text", text: prompt)}),
new("user", userContent)
},
0.5,
model
);
}
#endregion
#region call api
public static async Task<string> CallAsync<T>(HttpClient client, PromptRequest<T> prompt, int tryTimes, string url)
{
for (int i = tryTimes; i > 0; i--)
{
try
{
string json = JsonSerializer.Serialize(prompt);
using (StringContent content = new StringContent(json, Encoding.UTF8, "application/json"))
{
HttpResponseMessage response = await client.PostAsync(url, content);
string result = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
throw new Exception($"HTTP Error: {result}\n{response.RequestMessage}");
if (string.IsNullOrEmpty(result))
throw new Exception(result);
response.EnsureSuccessStatusCode();
return GetAiContent(result);
}
}
catch
{
if (i == 1) throw;
}
}
return null;
}
public static string GetAiContent(string aiJson)
{
try
{
using var doc = JsonDocument.Parse(aiJson);
return doc.RootElement
.GetProperty("choices")[0]
.GetProperty("message")
.GetProperty("content")
.GetString() ?? string.Empty;
}
catch
{
return string.Empty;
}
}
public static async Task GetVisionMD(HttpClient client, string ocrDir, string aiDir, int tryTimes, string aiUrl, string model)
{
if (!Directory.Exists(ocrDir))
{
Console.WriteLine($"找不到來源: {ocrDir}");
return;
}
Console.WriteLine($"開始呼叫 AI 轉MD, 讀取來源: {ocrDir}");
var files = Directory.GetFiles(ocrDir, "*.md");
Directory.CreateDirectory(aiDir);
foreach (var f in files)
{
try
{
var ocrText = await File.ReadAllTextAsync(f);
var imgPath = Path.ChangeExtension(f, ".png");
string fileName = Path.Combine(aiDir, Path.GetFileName(f)); ;
Console.WriteLine($"AI 正在處理 {Path.GetFileName(f)}...");
if (!File.Exists(fileName))
{
for (int i = tryTimes; i > 0; i--)
try
{
if (!File.Exists(imgPath))
throw new FileNotFoundException($"找不到影像檔案: {imgPath}");
byte[] imageBytes = File.ReadAllBytes(imgPath);
string base64Image = Convert.ToBase64String(imageBytes);
string aiContentText = await CallAsync(client, BuildVisionPrompt(base64Image, ocrText, model), tryTimes, aiUrl);
File.WriteAllTextAsync(fileName, aiContentText.Trim());
}
catch
{
if (i == 1) throw;
}
}
}
catch (Exception ex)
{
Console.WriteLine($" {Path.GetFileNameWithoutExtension(f)} AI 存檔失敗: {ex}");
return;
}
}
Console.WriteLine($"多模態分析完成");
}
#endregion
}
}
Taiwan is a country. 臺灣是我的國家