Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Claudeでの解析 #26

Merged
merged 26 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
2b65adc
no
herring101 Mar 22, 2024
6124b4f
Merge branch 'main' into herring/claudia
aiueo-1234 Mar 22, 2024
bae10e2
#25 簡単なコード修正
aiueo-1234 Mar 22, 2024
c5b1ab2
character解析 (エラー付き)
herring101 Apr 13, 2024
64bc49a
Merge branch 'main' into herring/claudia
herring101 Apr 13, 2024
7247602
openai to claude
herring101 Apr 13, 2024
4722f6c
#25 修正
aiueo-1234 Apr 27, 2024
47b0c5f
#25 claudiaとchatgptのライブラリバージョンの変更
aiueo-1234 Apr 27, 2024
e0804df
Merge branch 'main' into herring/claudia
aiueo-1234 Apr 27, 2024
ee25951
#25 ExtractCharacterListの修正
aiueo-1234 Apr 28, 2024
79893cf
#25 StyleBertVirsのUrl生成の修正
aiueo-1234 Apr 28, 2024
153e3de
#25 SoundGenerationSelectorMockの修正
aiueo-1234 Apr 28, 2024
a47d535
#25 (キャラクター名, ボイスモデル名)のマッピングになるように修正
aiueo-1234 Apr 28, 2024
98f50b4
#25 ステータスを変化させる
aiueo-1234 Apr 28, 2024
078c5f5
#25 変数名の調整
aiueo-1234 Apr 28, 2024
a9a7715
#25 apikeyがnullになったらクライアントもnullにする
aiueo-1234 Apr 29, 2024
72f09a2
#25 ExtractCharacterVoiceMappingの最適化
aiueo-1234 Apr 29, 2024
0384571
#25 変数名の再修正
aiueo-1234 Apr 29, 2024
684f74e
Merge branch 'main' into herring/claudia
aiueo-1234 Apr 29, 2024
bd0d143
#25 ExtractCharacterListでlinqを使う
aiueo-1234 Apr 29, 2024
00f638a
#25 フォーマット
aiueo-1234 Apr 29, 2024
e16f6d5
#25 ListからArrayに変更&forからforeachに変更
aiueo-1234 Apr 29, 2024
acf2e35
#25 二重ループを避ける
aiueo-1234 Apr 29, 2024
3d7c3d1
#25 ListからArrayに変更
aiueo-1234 Apr 29, 2024
cf9828f
#25 分解代入の式を修正
aiueo-1234 Apr 30, 2024
49e0aae
Merge branch 'main' into herring/claudia
aiueo-1234 Apr 30, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 6 additions & 21 deletions Epub/KoeBook.Epub/Services/AnalyzerService.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Text;
using System.Text.RegularExpressions;
using System.Text.RegularExpressions;
using KoeBook.Core;
using KoeBook.Core.Contracts.Services;
using KoeBook.Core.Models;
Expand All @@ -13,10 +12,10 @@ public partial class AnalyzerService(IScraperSelectorService scrapingService, IE
private readonly IScraperSelectorService _scrapingService = scrapingService;
private readonly IEpubDocumentStoreService _epubDocumentStoreService = epubDocumentStoreService;
private readonly ILlmAnalyzerService _llmAnalyzerService = llmAnalyzerService;
private Dictionary<string, string> _rubyReplacements = new Dictionary<string, string>();

public async ValueTask<BookScripts> AnalyzeAsync(BookProperties bookProperties, string tempDirectory, string coverFilePath, CancellationToken cancellationToken)
{
var _rubyReplacements = new Dictionary<string, string>();
coverFilePath = Path.Combine(tempDirectory, "Cover.png");
miyaji255 marked this conversation as resolved.
Show resolved Hide resolved
using var fs = File.Create(coverFilePath);
await fs.WriteAsync(CoverFile.ToArray(), cancellationToken);
Expand All @@ -43,8 +42,8 @@ public async ValueTask<BookScripts> AnalyzeAsync(BookProperties bookProperties,
{
if (element is Paragraph paragraph)
{
var line = paragraph.Text;
miyaji255 marked this conversation as resolved.
Show resolved Hide resolved
// rubyタグがあればルビのdictionaryに登録
var line = paragraph.Text ?? "";
// rubyタグがあればルビの dictionary に登録
var rubyDict = ExtractRuby(line);

foreach (var ruby in rubyDict)
Expand All @@ -65,22 +64,8 @@ public async ValueTask<BookScripts> AnalyzeAsync(BookProperties bookProperties,
}
}

// 800文字以上になったら1チャンクに分ける
var chunks = new List<string>();
var chunk = new StringBuilder();
foreach (var line in scriptLines)
{
if (chunk.Length + line.Text.Length > 800)
{
chunks.Add(chunk.ToString());
chunk.Clear();
}
chunk.AppendLine(line.Text);
}
if (chunk.Length > 0) chunks.Add(chunk.ToString());

// GPT4による話者、スタイル解析
var bookScripts = await _llmAnalyzerService.LlmAnalyzeScriptLinesAsync(bookProperties, scriptLines, chunks, cancellationToken);
// LLMによる話者、スタイル解析
var bookScripts = await _llmAnalyzerService.LlmAnalyzeScriptLinesAsync(bookProperties, scriptLines, cancellationToken)!;

return bookScripts;
}
Expand Down
8 changes: 8 additions & 0 deletions KoeBook.Core/Contracts/Services/IClaudeService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Claudia;

namespace KoeBook.Core.Contracts.Services;

public interface IClaudeService
{
IMessages? Messages { get; }
}
2 changes: 1 addition & 1 deletion KoeBook.Core/Contracts/Services/ILlmAnalyzerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ namespace KoeBook.Core.Contracts.Services;

public interface ILlmAnalyzerService
{
ValueTask<BookScripts> LlmAnalyzeScriptLinesAsync(BookProperties bookProperties, List<ScriptLine> scriptLines, List<string> chunks, CancellationToken cancellationToken);
ValueTask<BookScripts> LlmAnalyzeScriptLinesAsync(BookProperties bookProperties, List<ScriptLine> scriptLines, CancellationToken cancellationToken);
miyaji255 marked this conversation as resolved.
Show resolved Hide resolved
}
6 changes: 6 additions & 0 deletions KoeBook.Core/EbookException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ public enum ExceptionType
[EnumMember(Value = "GPT4による話者・スタイル設定に失敗しました")]
Gpt4TalkerAndStyleSettingFailed,

[EnumMember(Value = "APIキーが設定されていません")]
ApiKeyNotSet,

[EnumMember(Value = "Claudeによる話者・スタイル設定に失敗しました")]
ClaudeTalkerAndStyleSettingFailed,

[EnumMember(Value = "webページの解析に失敗しました")]
WebScrapingFailed,

Expand Down
1 change: 1 addition & 0 deletions KoeBook.Core/KoeBook.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

<ItemGroup>
<PackageReference Include="Betalgo.OpenAI" Version="7.4.6" />
<PackageReference Include="Claudia" Version="1.0.0" />
<PackageReference Include="FastEnum" Version="1.8.0" />
<PackageReference Include="NAudio" Version="2.2.1" />
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="8.0.0" />
Expand Down
20 changes: 16 additions & 4 deletions KoeBook.Core/Services/ChatGptAnalyzerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,20 @@ public partial class ChatGptAnalyzerService(IOpenAIService openAIService, IDispl
private readonly IOpenAIService _openAiService = openAIService;
private readonly IDisplayStateChangeService _displayStateChangeService = displayStateChangeService;

public async ValueTask<BookScripts> LlmAnalyzeScriptLinesAsync(BookProperties bookProperties, List<ScriptLine> scriptLines, List<string> chunks, CancellationToken cancellationToken)
public async ValueTask<BookScripts> LlmAnalyzeScriptLinesAsync(BookProperties bookProperties, List<ScriptLine> scriptLines, CancellationToken cancellationToken)
{
var chunks = new List<string>();
var chunk = new StringBuilder();
foreach (var line in scriptLines)
{
if (chunk.Length + line.Text.Length > 800)
{
chunks.Add(chunk.ToString());
chunk.Clear();
}
chunk.AppendLine(line.Text);
}
if (chunk.Length > 0) chunks.Add(chunk.ToString());
var progress = _displayStateChangeService.ResetProgress(bookProperties, GenerationState.Analyzing, chunks.Count);
Queue<string> summaryList = new();
Queue<string> characterList = new();
Expand Down Expand Up @@ -147,7 +159,7 @@ 3. Target Sentence
},
Model = OpenAI.ObjectModels.Models.Gpt_4_turbo_preview,
MaxTokens = 4000
});
}, cancellationToken: cancellationToken);
if (completionResult.Successful)
{
var result = completionResult.Choices.First().Message.Content;
Expand Down Expand Up @@ -261,7 +273,7 @@ 3. Story
},
Model = OpenAI.ObjectModels.Models.Gpt_4_turbo_preview,
MaxTokens = 4000
});
}, cancellationToken: cancellationToken);
if (completionResult.Successful)
{
var result = completionResult.Choices.First().Message.Content;
Expand Down Expand Up @@ -356,7 +368,7 @@ Make a table of character names and voices
},
Model = OpenAI.ObjectModels.Models.Gpt_4_turbo_preview,
MaxTokens = 4000
});
}, cancellationToken: cancellationToken);
if (completionResult.Successful)
{
var result = completionResult.Choices.First().Message.Content;
Expand Down
168 changes: 168 additions & 0 deletions KoeBook.Core/Services/ClaudeAnalyzerService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
using System.Text;
using KoeBook.Core.Contracts.Services;
using KoeBook.Core.Models;

namespace KoeBook.Core.Services;

public partial class ClaudeAnalyzerService(IClaudeService claudeService, IDisplayStateChangeService displayStateChangeService) : ILlmAnalyzerService
{
private readonly IClaudeService _claudeService = claudeService;
private readonly IDisplayStateChangeService _displayStateChangeService = displayStateChangeService;

public async ValueTask<BookScripts> LlmAnalyzeScriptLinesAsync(BookProperties bookProperties, List<ScriptLine> scriptLines, CancellationToken cancellationToken)
{
var lineNumberingText = LineNumbering(scriptLines);
if (_claudeService.Messages is null)
{
throw new EbookException(ExceptionType.ApiKeyNotSet);
}
try
{
var message1 = await _claudeService.Messages.CreateAsync(new()
{
Model = "claude-3-opus-20240229", // you can use Claudia.Models.Claude3Opus string constant
MaxTokens = 4000,
Messages = [new()
{
Role = "user",
Content = CreatePrompt1(lineNumberingText)
}]
},
cancellationToken: cancellationToken
);
var (characterList, voiceIds) = ExtractCharacterListAndVoiceIds(message1.ToString());

var message2 = await _claudeService.Messages.CreateAsync(new()
{
Model = "claude-3-opus-20240229", // you can use Claudia.Models.Claude3Opus string constant
MaxTokens = 4000,
Messages = [new()
{
Role = "user",
Content = CreatePrompt2(characterList, voiceIds)
}]
},
cancellationToken: cancellationToken
);

var characterVoiceMapping = ExtractCharacterVoiceMapping(message2.ToString());

return new(bookProperties, new(characterVoiceMapping)) { ScriptLines = scriptLines };
}
catch (OperationCanceledException)
{
throw;
}
miyaji255 marked this conversation as resolved.
Show resolved Hide resolved
catch
{
throw new EbookException(ExceptionType.ClaudeTalkerAndStyleSettingFailed);
}
}

private Dictionary<string, string> ExtractCharacterVoiceMapping(string response)
{
throw new NotImplementedException();
}

private static string CreatePrompt1(string lineNumberingText)
miyaji255 marked this conversation as resolved.
Show resolved Hide resolved
{
return $$"""
{{lineNumberingText}}

Notes:
- For narration parts, which are not enclosed in quotation marks, select the narrator.
- For dialogues enclosed in quotation marks, assign a voice other than the narrator.
- In the character description, include the appropriate voice characteristics.
Tasks: Based on the notes above, perform the following two tasks:
- List of characters Objective: To understand the characters appearing in the text, list the character ID, name, and description for all characters who speak at least one line.
- Output the speaking character or narrator for each line Objective: To identify which character is speaking in each line of the text, output the speaking character or narrator for all lines. Carefully recognize the context and output with attention. Select only one character_id per line.
- Revise CHARACTER LIST and VOICE ID (in the event that the CHARACTER LIST is incomplete)
Output Format:
[CHARACTER LIST]
c0. ナレーター: The person who speaks the narration parts. A calm-toned male voice.
c1. {character_name}: {character_description}
{character_id}. {character_name}: {character_description}
...
[VOICE ID]
1. {character_id} {narration|dialogue}
2. {character_id} {narration|dialogue}
3. {character_id} {narration|dialogue}
...
[REVISE CHARACTER LIST]
c0. ナレーター: The person who speaks the narration parts. A calm-toned male voice.
c1. {character_name}: {character_description}
{character_id}. {character_name}: {character_description}
...
[REVISE VOICE ID]
1. {character_id} {narration|dialogue}
2. {character_id} {narration|dialogue}
3. {character_id} {narration|dialogue}
...
""";
}

private static string CreatePrompt2(List<Character> characterList, List<string> voiceIds)
{
throw new NotImplementedException();
}

private static string LineNumbering(List<ScriptLine> scriptLines)
{
var sb = new StringBuilder();
foreach (var (index, scriptLine) in scriptLines.Select((x, i) => (i, x)))
{
sb.AppendLine($"{index + 1}. {scriptLine.Text}");
}
return sb.ToString();
}

private static (List<Character>, List<string>) ExtractCharacterListAndVoiceIds(string response)
{
var characterList = new List<Character>();
var voiceIds = new List<string>();
var lines = response.Split("\n");
var characterListStartIndex = 0;
var characterListEndIndex = 0;
miyaji255 marked this conversation as resolved.
Show resolved Hide resolved
for (var i = 0; i < lines.Length; i++)
{
if (lines[i].StartsWith("[REVISE CHARACTER LIST]"))
{
characterListStartIndex = i + 1;
}

if (lines[i].StartsWith("[REVISE VOICE ID]"))
{
characterListEndIndex = i;
miyaji255 marked this conversation as resolved.
Show resolved Hide resolved
}
}
for (var i = characterListStartIndex; i < characterListEndIndex; i++)
{
var line = lines[i];
if (line.StartsWith('c'))
{
var characterId = line[1..line.IndexOf('.')];
var characterName = line[(line.IndexOf('.') + 2)..line.IndexOf(':')];
var characterDescription = line[(line.IndexOf(':') + 2)..];
characterList.Add(new Character(characterId, characterName, characterDescription));
}
}
miyaji255 marked this conversation as resolved.
Show resolved Hide resolved

{
var dest = (stackalloc Range[4]);
for (var i = characterListEndIndex + 1; i < lines.Length; i++)
{
var line = lines[i].AsSpan();
if (line.Length > 0 && line.Contains('c'))
{
if (line.Split(dest, ' ') > 3)
throw new EbookException(ExceptionType.ClaudeTalkerAndStyleSettingFailed);
var characterId = line[dest[1]];
var narrationOrDialogue = line[dest[2]];
}
}
}
return (characterList, voiceIds);
}

private record Character(string Id, string Name, string Description);
}
31 changes: 31 additions & 0 deletions KoeBook.Core/Services/ClaudeService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using Claudia;
using KoeBook.Core.Contracts.Services;

namespace KoeBook.Core.Services;

public class ClaudeService(ISecretSettingsService secretSettingsService) : IClaudeService
{
private readonly ISecretSettingsService _secretSettingsService = secretSettingsService;

private string? _apiKey;
private Anthropic? _anthropic;

public IMessages? Messages => GetAnthropic()?.Messages;


private Anthropic? GetAnthropic()
{
if (_apiKey != _secretSettingsService.ApiKey)
{
if (string.IsNullOrEmpty(_secretSettingsService.ApiKey))
{
_apiKey = _secretSettingsService.ApiKey;
return null;
}

_anthropic = new Anthropic { ApiKey = _secretSettingsService.ApiKey };
_apiKey = _secretSettingsService.ApiKey;
}
return _anthropic;
miyaji255 marked this conversation as resolved.
Show resolved Hide resolved
}
}
Loading