Files
AX-Copilot-Codex/src/AxCopilot/Services/Agent/ImageAnalyzeTool.cs

151 lines
6.4 KiB
C#

using System.IO;
using System.Text.Json;
namespace AxCopilot.Services.Agent;
/// <summary>
/// 이미지를 분석하여 내용 설명, 텍스트 추출, 차트 데이터 해석을 수행하는 도구.
/// LLM 멀티모달 API를 활용합니다.
/// </summary>
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<ToolResult> 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";
}
}