128 lines
4.3 KiB
C#
128 lines
4.3 KiB
C#
using System.Collections.Generic;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Windows;
|
|
|
|
namespace AxCopilot.Services.Agent;
|
|
|
|
public class CodeSearchTool : IAgentTool
|
|
{
|
|
private static CodeIndexService? _indexService;
|
|
|
|
private static string _lastWorkFolder = "";
|
|
|
|
public string Name => "search_codebase";
|
|
|
|
public string Description => "코드베이스를 시맨틱 검색합니다. 자연어 질문으로 관련 있는 코드를 찾습니다.\n예: '사용자 인증 로직', '데이터베이스 연결 설정', '에러 핸들링 패턴'\n인덱스는 영속 저장되어 변경된 파일만 증분 업데이트합니다.";
|
|
|
|
public ToolParameterSchema Parameters => new ToolParameterSchema
|
|
{
|
|
Properties = new Dictionary<string, ToolProperty>
|
|
{
|
|
["query"] = new ToolProperty
|
|
{
|
|
Type = "string",
|
|
Description = "검색할 내용 (자연어 또는 키워드)"
|
|
},
|
|
["max_results"] = new ToolProperty
|
|
{
|
|
Type = "integer",
|
|
Description = "최대 결과 수 (기본 5)"
|
|
},
|
|
["reindex"] = new ToolProperty
|
|
{
|
|
Type = "boolean",
|
|
Description = "true면 기존 인덱스를 버리고 전체 재인덱싱 (기본 false)"
|
|
}
|
|
},
|
|
Required = new List<string> { "query" }
|
|
};
|
|
|
|
public async Task<ToolResult> ExecuteAsync(JsonElement args, AgentContext context, CancellationToken ct = default(CancellationToken))
|
|
{
|
|
if (!((Application.Current as App)?.SettingsService?.Settings.Llm.Code.EnableCodeIndex ?? true))
|
|
{
|
|
return ToolResult.Ok("코드 시맨틱 검색이 비활성 상태입니다. 설정 → AX Agent → 코드에서 활성화하세요.");
|
|
}
|
|
JsonElement q;
|
|
string query = (args.TryGetProperty("query", out q) ? (q.GetString() ?? "") : "");
|
|
JsonElement m;
|
|
int maxResults = (args.TryGetProperty("max_results", out m) ? m.GetInt32() : 5);
|
|
JsonElement ri;
|
|
bool reindex = args.TryGetProperty("reindex", out ri) && ri.GetBoolean();
|
|
if (string.IsNullOrWhiteSpace(query))
|
|
{
|
|
return ToolResult.Fail("query가 필요합니다.");
|
|
}
|
|
if (string.IsNullOrEmpty(context.WorkFolder))
|
|
{
|
|
return ToolResult.Fail("작업 폴더가 설정되어 있지 않습니다.");
|
|
}
|
|
if (_indexService == null || _lastWorkFolder != context.WorkFolder || reindex)
|
|
{
|
|
_indexService?.Dispose();
|
|
_indexService = new CodeIndexService();
|
|
_lastWorkFolder = context.WorkFolder;
|
|
if (!reindex)
|
|
{
|
|
_indexService.TryLoadExisting(context.WorkFolder);
|
|
}
|
|
}
|
|
if (!_indexService.IsIndexed || reindex)
|
|
{
|
|
await _indexService.IndexAsync(context.WorkFolder, ct);
|
|
if (!_indexService.IsIndexed)
|
|
{
|
|
return ToolResult.Fail("코드 인덱싱에 실패했습니다.");
|
|
}
|
|
}
|
|
List<SearchResult> results = _indexService.Search(query, maxResults);
|
|
if (results.Count == 0)
|
|
{
|
|
return ToolResult.Ok("'" + query + "'에 대한 관련 코드를 찾지 못했습니다. 다른 키워드로 검색해보세요.");
|
|
}
|
|
StringBuilder sb = new StringBuilder();
|
|
StringBuilder stringBuilder = sb;
|
|
StringBuilder stringBuilder2 = stringBuilder;
|
|
StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(24, 3, stringBuilder);
|
|
handler.AppendLiteral("'");
|
|
handler.AppendFormatted(query);
|
|
handler.AppendLiteral("' 검색 결과 (");
|
|
handler.AppendFormatted(results.Count);
|
|
handler.AppendLiteral("개, 인덱스 ");
|
|
handler.AppendFormatted(_indexService.ChunkCount);
|
|
handler.AppendLiteral("개 청크):\n");
|
|
stringBuilder2.AppendLine(ref handler);
|
|
foreach (SearchResult r in results)
|
|
{
|
|
stringBuilder = sb;
|
|
StringBuilder stringBuilder3 = stringBuilder;
|
|
handler = new StringBuilder.AppendInterpolatedStringHandler(20, 4, stringBuilder);
|
|
handler.AppendLiteral("\ud83d\udcc1 ");
|
|
handler.AppendFormatted(r.FilePath);
|
|
handler.AppendLiteral(" (line ");
|
|
handler.AppendFormatted(r.StartLine);
|
|
handler.AppendLiteral("-");
|
|
handler.AppendFormatted(r.EndLine);
|
|
handler.AppendLiteral(", score ");
|
|
handler.AppendFormatted(r.Score, "F3");
|
|
handler.AppendLiteral(")");
|
|
stringBuilder3.AppendLine(ref handler);
|
|
sb.AppendLine("```");
|
|
sb.AppendLine(r.Preview);
|
|
sb.AppendLine("```");
|
|
sb.AppendLine();
|
|
}
|
|
return ToolResult.Ok(sb.ToString());
|
|
}
|
|
|
|
public static void InvalidateIndex()
|
|
{
|
|
_indexService?.Dispose();
|
|
_indexService = null;
|
|
_lastWorkFolder = "";
|
|
}
|
|
}
|