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 6 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
21 changes: 3 additions & 18 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 Down Expand Up @@ -49,22 +48,8 @@ public async ValueTask<BookScripts> AnalyzeAsync(BookProperties bookProperties,
return p.ScriptLine = new ScriptLine(line, "", "");
}).ToList();

// 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 @@ -45,6 +45,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
235 changes: 235 additions & 0 deletions KoeBook.Core/Services/ClaudeAnalyzerService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
using System.Text;
using KoeBook.Core.Contracts.Services;
using KoeBook.Core.Models;

namespace KoeBook.Core.Services;

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

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 = ExtractCharacterList(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)
}]
},
cancellationToken: cancellationToken
);

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

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, List<Character> characterList)
{
var characterVoiceMapping = new Dictionary<string, string>();
var lines = response.Split("\n");
var characterListStartIndex = 0;
for (var i = 0; i < lines.Length; i++)
{
if (lines[i].StartsWith("[Assign Voices]"))
{
characterListStartIndex = i + 1;
}
}

for (var i = characterListStartIndex; i < lines.Length; i++)
{
var line = lines[i];
if (line.StartsWith('c'))
{
var characterId = line[1..line.IndexOf('.')];
var voiceType = line[(line.IndexOf(':') + 2)..];
// voiceTypeが_soundGenerationSelectorService.Modelsに含まれているかチェック
if (_soundGenerationSelectorService.Models.Any(x => x.Name == voiceType))
{
characterVoiceMapping.Add(characterId, voiceType);
}
else
{
characterVoiceMapping.Add(characterId, string.Empty);
}
}
}

return characterVoiceMapping;
miyaji255 marked this conversation as resolved.
Show resolved Hide resolved
}

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. ナレーター: {character_and_voice_description, example:"The person who speaks the narration parts. A calm-toned male voice."}
c1. {character_name}: {character_and_voice_description}
{character_id}. {character_name}: {character_and_voice_description}
...
[VOICE ID]
1. {character_id} {narration|dialogue}
2. {character_id} {narration|dialogue}
3. {character_id} {narration|dialogue}
...
[REVISE CHARACTER LIST]
c0. ナレーター: {character_and_voice_description}
c1. {character_name}: {character_and_voice_description}
{character_id}. {character_name}: {character_and_voice_description}
...
[REVISE VOICE ID]
1. {character_id} {narration|dialogue}
2. {character_id} {narration|dialogue}
3. {character_id} {narration|dialogue}
...
""";
}

private string CreatePrompt2(List<Character> characterList)
{
StringBuilder sb = new StringBuilder();
foreach (var character in characterList)
{
sb.AppendLine($"c{character.Id}. {character.Name}: {character.Description}");
}

StringBuilder sb2 = new StringBuilder();
foreach (var voiceType in _soundGenerationSelectorService.Models)
{
sb2.Append($"{voiceType.Name},");
}

return $$"""
Assign the most fitting voice type to each character from the provided list, ensuring the chosen voice aligns with their role and attributes in the story. Only select from the available voice types.

Characters:
{{sb}}

Voice Types:
{{sb2}}
miyaji255 marked this conversation as resolved.
Show resolved Hide resolved

Output Format:
[Assign Voices]
c0. {character_name}: {voice_type}
c1. {character_name}: {voice_type}
""";
}

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> ExtractCharacterList(string response)
{
var characterList = new List<Character>();
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;
}

private class Character
{
public string Id { get; }
public string Name { get; }
public string Description { get; }
public string VoiceType { get; set; } = string.Empty;
public Character(string id, string name, string description)
{
Id = id;
Name = name;
Description = 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
}
}
1 change: 0 additions & 1 deletion KoeBook/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using KoeBook.Core.Contracts.Services;
using KoeBook.Core.Services;
using KoeBook.Core.Services.Mocks;
using KoeBook.Epub;
using KoeBook.Epub.Contracts.Services;
using KoeBook.Epub.Services;
using KoeBook.Models;
Expand Down
Loading
Loading