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 10 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
3 changes: 2 additions & 1 deletion KoeBook.Core/KoeBook.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Betalgo.OpenAI" Version="7.4.6" />
<PackageReference Include="Betalgo.OpenAI" Version="8.1.1" />
<PackageReference Include="Claudia" Version="1.2.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
201 changes: 201 additions & 0 deletions KoeBook.Core/Services/ClaudeAnalyzerService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
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 = Claudia.Models.Claude3Opus,
MaxTokens = 4000,
Messages = [new()
{
Role = "user",
Content = CreateCharacterGuessPrompt(lineNumberingText)
}]
},
cancellationToken: cancellationToken
);
var characterList = ExtractCharacterList(message1.ToString(), scriptLines);

var message2 = await _claudeService.Messages.CreateAsync(new()
{
Model = Claudia.Models.Claude3Opus,
MaxTokens = 4000,
Messages = [new()
{
Role = "user",
Content = CreateVoiceTypeAnalyzePrompt(characterList)
}]
},
cancellationToken: cancellationToken
);

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

return new(bookProperties, new(characterVoiceMapping)) { ScriptLines = scriptLines };
}
catch (OperationCanceledException) { throw; }
catch (Exception e)
{
throw new EbookException(ExceptionType.ClaudeTalkerAndStyleSettingFailed, innerException: e);
}
}

private Dictionary<string, string> ExtractCharacterVoiceMapping(string response, List<Character> characterList)
{
return response.Split("\n")
.SkipWhile(l => !l.StartsWith("[Assign Voices]"))
.Where(l => l.StartsWith('c'))
.Select(l =>
{
var characterId = l[1..l.IndexOf('.')];
var voiceType = l[(l.IndexOf(':') + 2)..];
// voiceTypeが_soundGenerationSelectorService.Modelsに含まれているかチェック
return _soundGenerationSelectorService.Models.Any(x => x.Name == voiceType)
? (characterId, voiceType)
: (characterId, string.Empty);
}).ToDictionary();
}

private static string CreateCharacterGuessPrompt(string lineNumberingText)
{
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 CreateVoiceTypeAnalyzePrompt(List<Character> characterList)
{
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:
{{string.Join("\n", characterList.Select(character => $"c{character.Id}. {character.Name}: {character.Description}"))}}

Voice Types:
{{string.Join(",", _soundGenerationSelectorService.Models.Select(m => m.Name))}}

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, List<ScriptLine> scriptLines)
{
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
break;
}
}
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 dic = characterList.Select(x => KeyValuePair.Create(x.Id, x.Name)).ToDictionary();
miyaji255 marked this conversation as resolved.
Show resolved Hide resolved
var lines2 = lines.AsSpan()[(characterListEndIndex + 1)..];

for (var i = 0; i < lines2.Length; i++)
{
var line = lines2[i].AsSpan().TrimEnd();
line = line[(line.IndexOf(' ') + 2)..];//cまで無視
line = line[..line.IndexOf(' ')];
miyaji255 marked this conversation as resolved.
Show resolved Hide resolved
if (dic.TryGetValue(line.ToString(), out var characterName))
{
scriptLines[i].Character = characterName;
}
}
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
}
}
2 changes: 2 additions & 0 deletions KoeBook.Core/Services/MyOpenAiService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ public class MyOpenAiService(ISecretSettingsService secretSettingsService, IHttp

public IAudioService Audio => GetOpenAiService()?.Audio!;

public IBatchService Batch => GetOpenAiService()?.Batch!;

public void SetDefaultModelId(string modelId)
{
GetOpenAiService()?.SetDefaultModelId(modelId);
Expand Down
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
4 changes: 2 additions & 2 deletions KoeBook/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ public static IHostBuilder UseCoreStartup(this IHostBuilder builder)
services.AddSingleton<IEpubGenerateService, EpubGenerateService>();
services.AddSingleton<IEpubDocumentStoreService, EpubDocumentStoreService>();
services.AddSingleton<IAnalyzerService, AnalyzerService>();
services.AddSingleton<ILlmAnalyzerService, ChatGptAnalyzerService>();
services.AddSingleton<OpenAI.Interfaces.IOpenAIService, MyOpenAiService>();
services.AddSingleton<ILlmAnalyzerService, ClaudeAnalyzerService>();
services.AddSingleton<IClaudeService, ClaudeService>();

// Epub Services
services
Expand Down
Loading