using System.IO; using System.Text.Json; namespace AxCopilot.Services.Agent; /// /// 이미지를 분석하여 내용 설명, 텍스트 추출, 차트 데이터 해석을 수행하는 도구. /// LLM 멀티모달 API를 활용합니다. /// public class ImageAnalyzeTool : IAgentTool { public string Name => "image_analyze"; public string Description => "Analyze an image using LLM multimodal vision. " + "Tasks: describe (general description), extract_text (OCR-like text extraction), " + "extract_data (extract structured data like tables/charts from image), " + "compare (compare two images and describe differences)."; public ToolParameterSchema Parameters => new() { Properties = new() { ["image_path"] = new() { Type = "string", Description = "Path to the image file (.png, .jpg, .jpeg, .bmp, .gif, .webp)." }, ["task"] = new() { Type = "string", Description = "Analysis task: describe, extract_text, extract_data, compare. Default: describe", Enum = ["describe", "extract_text", "extract_data", "compare"] }, ["compare_path"] = new() { Type = "string", Description = "Path to second image for comparison (only used with task=compare)." }, ["question"] = new() { Type = "string", Description = "Optional specific question about the image." }, ["language"] = new() { Type = "string", Description = "Response language: ko (Korean), en (English). Default: ko" }, }, Required = ["image_path"] }; public async Task ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct) { var imagePath = args.GetProperty("image_path").GetString() ?? ""; var task = args.TryGetProperty("task", out var taskEl) ? taskEl.GetString() ?? "describe" : "describe"; var question = args.TryGetProperty("question", out var qEl) ? qEl.GetString() ?? "" : ""; var language = args.TryGetProperty("language", out var langEl) ? langEl.GetString() ?? "ko" : "ko"; var fullPath = FileReadTool.ResolvePath(imagePath, context.WorkFolder); if (!context.IsPathAllowed(fullPath)) return ToolResult.Fail($"경로 접근 차단: {fullPath}"); if (!File.Exists(fullPath)) return ToolResult.Fail($"파일 없음: {fullPath}"); // 지원 이미지 형식 확인 var ext = Path.GetExtension(fullPath).ToLowerInvariant(); if (!IsImageExtension(ext)) return ToolResult.Fail($"지원하지 않는 이미지 형식: {ext}"); // 이미지를 base64로 인코딩 var imageBytes = await File.ReadAllBytesAsync(fullPath, ct); var base64 = Convert.ToBase64String(imageBytes); var mimeType = ext switch { ".png" => "image/png", ".jpg" or ".jpeg" => "image/jpeg", ".gif" => "image/gif", ".webp" => "image/webp", ".bmp" => "image/bmp", _ => "image/png" }; // 파일 크기 제한 (10MB) if (imageBytes.Length > 10 * 1024 * 1024) return ToolResult.Fail("이미지 크기가 10MB를 초과합니다."); // 비교 모드: 두 번째 이미지 string? compareBase64 = null; string? compareMime = null; if (task == "compare" && args.TryGetProperty("compare_path", out var cpEl)) { var comparePath = FileReadTool.ResolvePath(cpEl.GetString() ?? "", context.WorkFolder); if (File.Exists(comparePath) && context.IsPathAllowed(comparePath)) { var compareBytes = await File.ReadAllBytesAsync(comparePath, ct); compareBase64 = Convert.ToBase64String(compareBytes); var compareExt = Path.GetExtension(comparePath).ToLowerInvariant(); compareMime = compareExt switch { ".png" => "image/png", ".jpg" or ".jpeg" => "image/jpeg", ".gif" => "image/gif", ".webp" => "image/webp", _ => "image/png" }; } } // 프롬프트 구성 var langPrompt = language == "en" ? "Respond in English." : "한국어로 응답하세요."; var prompt = task switch { "extract_text" => $"이 이미지에서 모든 텍스트를 추출하세요. 원본 레이아웃을 최대한 유지하세요. {langPrompt}", "extract_data" => $"이 이미지에서 구조화된 데이터를 추출하세요. 테이블, 차트, 그래프 등의 데이터를 " + $"CSV 또는 JSON 형식으로 변환하세요. 차트의 경우 각 항목의 값을 추정하세요. {langPrompt}", "compare" => $"두 이미지를 비교하고 차이점을 설명하세요. {langPrompt}", _ => string.IsNullOrEmpty(question) ? $"이 이미지의 내용을 상세하게 설명하세요. 주요 요소, 텍스트, 레이아웃, 색상 등을 포함하세요. {langPrompt}" : $"{question} {langPrompt}" }; // LLM에 이미지 분석 요청을 위한 결과 생성 // 실제 LLM 호출은 에이전트 루프에서 수행하므로, 여기서는 이미지 정보와 프롬프트를 반환 var info = new System.Text.StringBuilder(); info.AppendLine($"🖼 이미지 분석 준비 완료"); info.AppendLine($" 파일: {Path.GetFileName(fullPath)}"); info.AppendLine($" 크기: {imageBytes.Length / 1024}KB"); info.AppendLine($" 형식: {mimeType}"); info.AppendLine($" 작업: {task}"); info.AppendLine(); info.AppendLine($"[IMAGE_BASE64:{mimeType}]{base64}[/IMAGE_BASE64]"); if (compareBase64 != null) info.AppendLine($"[IMAGE_BASE64:{compareMime}]{compareBase64}[/IMAGE_BASE64]"); info.AppendLine(); info.AppendLine($"분석 프롬프트: {prompt}"); return ToolResult.Ok(info.ToString()); } private static bool IsImageExtension(string ext) { return ext is ".png" or ".jpg" or ".jpeg" or ".gif" or ".bmp" or ".webp"; } }