feat: AI 텍스트 Provider (LlmProvider) — OpenAI/Anthropic 요약·번역·교정
AI를 '키 없으면 비활성되는 부가 엣지'로 통합. 기존 변환에 무영향, 일반 변환과 충돌 없는 txt↔txt/md↔md self-edge만 노출. - IChatClient + OpenAiChatClient/AnthropicChatClient (HttpClient 직접, SDK 의존 0) - LlmProvider: summarize/translate/proofread/custom, ISettingsStore 키 + 환경변수(OPENAI_API_KEY/ANTHROPIC_API_KEY) 폴백 - CheckAvailabilityAsync 게이트: 키 없으면 NotReady → 그래프에서 실행 비활성 - ConvertOptions.Ai (백엔드/모델/작업/대상언어) - 테스트 5개 (키 게이트·프롬프트 구성·그래프 엣지) — 총 34개 통과
This commit is contained in:
parent
937d9e7878
commit
b76d51f3c1
6 changed files with 313 additions and 0 deletions
68
src/Everything2Everything.Tests/LlmProviderTests.cs
Normal file
68
src/Everything2Everything.Tests/LlmProviderTests.cs
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Everything2Everything.Core;
|
||||
using Everything2Everything.Core.Converters;
|
||||
using Xunit;
|
||||
|
||||
namespace Everything2Everything.Tests;
|
||||
|
||||
public class LlmProviderTests
|
||||
{
|
||||
private sealed class FakeStore : ISettingsStore
|
||||
{
|
||||
private readonly Dictionary<string, string> _d = new();
|
||||
public string? Get(string key) => _d.TryGetValue(key, out var v) ? v : null;
|
||||
public void Set(string key, string value) => _d[key] = value;
|
||||
public void Remove(string key) => _d.Remove(key);
|
||||
public bool Contains(string key) => _d.ContainsKey(key);
|
||||
}
|
||||
|
||||
private static bool EnvHasKey()
|
||||
=> !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("OPENAI_API_KEY"))
|
||||
|| !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY"));
|
||||
|
||||
[Fact]
|
||||
public async Task NoKey_IsNotReady()
|
||||
{
|
||||
if (EnvHasKey()) return; // 환경변수 키가 있으면 이 단언은 건너뜀
|
||||
var p = new LlmProvider(new FakeStore());
|
||||
var a = await p.CheckAvailabilityAsync();
|
||||
Assert.False(a.IsReady);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WithKey_IsReady()
|
||||
{
|
||||
var store = new FakeStore();
|
||||
store.Set("openai.apikey", "sk-test-key");
|
||||
var p = new LlmProvider(store);
|
||||
var a = await p.CheckAvailabilityAsync();
|
||||
Assert.True(a.IsReady);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildPrompt_Translate_IncludesTargetLanguage()
|
||||
{
|
||||
var (system, user) = LlmProvider.BuildPrompt(
|
||||
new AiOptions { Task = "translate", TargetLanguage = "일본어" }, "hello");
|
||||
Assert.Contains("일본어", system);
|
||||
Assert.Equal("hello", user);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildPrompt_Summarize_MentionsSummary()
|
||||
{
|
||||
var (system, _) = LlmProvider.BuildPrompt(new AiOptions { Task = "summarize" }, "text");
|
||||
Assert.Contains("요약", system);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DefaultGraph_HasAiTextSelfEdge()
|
||||
{
|
||||
var graph = Everything2EverythingBootstrap.CreateDefault().Providers.Graph;
|
||||
// AI self-edge(txt→txt)가 그래프에 존재 — 키 없으면 실행 시 NotReady로 비활성되지만 엣지는 등록됨
|
||||
Assert.NotNull(graph.FindBestPath(".txt", ".txt"));
|
||||
Assert.NotNull(graph.FindBestPath(".md", ".md"));
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue