-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
64 changed files
with
5,227 additions
and
1,001 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
using LangChain.Memory; | ||
using LangChain.Providers; | ||
using static LangChain.Chains.Chain; | ||
|
||
namespace ChatRPG.API; | ||
|
||
// ReSharper disable once InconsistentNaming | ||
public static class ChatRPGSummarizer | ||
{ | ||
public const string SummaryPrompt = @" | ||
Progressively summarize the interaction between the player and the GM. Append to the summary so that new messages are most represented, while still remembering key details of the far past. The player describes their actions in response to the game world, and the GM narrates the outcome, revealing the next part of the adventure. Return a new summary based on each exchange taking into account the previous summary. Expand the summary so that it is long enough to capture the essence of the story from the beginning without forgetting key details. Do not remove important people, events, or locations from the summary. | ||
EXAMPLE | ||
Current summary: | ||
The player enters the forest and cautiously looks around. The GM describes towering trees and a narrow path leading deeper into the woods. The player decides to follow the path, staying alert. | ||
New lines of conversation: | ||
Player: I move carefully down the path, keeping an eye out for any hidden dangers. | ||
GM: As you continue, the air grows colder, and you hear rustling in the bushes ahead. Suddenly, a shadowy figure leaps out in front of you. | ||
New summary: | ||
The player enters the forest and follows a narrow path, staying alert. The GM introduces a shadowy figure that appears ahead after rustling is heard in the bushes. | ||
END OF EXAMPLE | ||
Current summary: | ||
{summary} | ||
New lines of conversation: | ||
{new_lines} | ||
New summary:"; | ||
|
||
public static async Task<string> SummarizeAsync( | ||
this IChatModel chatModel, | ||
IEnumerable<Message> newMessages, | ||
string existingSummary, | ||
MessageFormatter? formatter = null, | ||
CancellationToken cancellationToken = default) | ||
{ | ||
formatter ??= new MessageFormatter(); | ||
formatter.HumanPrefix = "Player"; | ||
formatter.AiPrefix = "GM"; | ||
|
||
var newLines = formatter.Format(newMessages); | ||
|
||
var chain = | ||
Set(existingSummary, outputKey: "summary") | ||
| Set(newLines, outputKey: "new_lines") | ||
| Template(SummaryPrompt) | ||
| LLM(chatModel); | ||
|
||
return await chain.RunAsync("text", cancellationToken: cancellationToken).ConfigureAwait(true) ?? string.Empty; | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
using ChatRPG.Data.Models; | ||
|
||
namespace ChatRPG.API; | ||
|
||
public interface IReActLlmClient | ||
{ | ||
Task<string> GetChatCompletionAsync(Campaign campaign, string actionPrompt, string input); | ||
IAsyncEnumerable<string> GetStreamedChatCompletionAsync(Campaign campaign, string actionPrompt, string input); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
using System.Text; | ||
using System.Threading.Channels; | ||
using LangChain.Providers; | ||
using LangChain.Providers.OpenAI; | ||
|
||
namespace ChatRPG.API; | ||
|
||
public class LlmEventProcessor | ||
{ | ||
private readonly object _lock = new object(); | ||
private readonly StringBuilder _buffer = new StringBuilder(); | ||
private bool _foundFinalAnswer = false; | ||
private readonly Channel<string> _channel = Channel.CreateUnbounded<string>(); | ||
|
||
public LlmEventProcessor(OpenAiChatModel model) | ||
{ | ||
model.DeltaReceived += OnDeltaReceived; | ||
model.ResponseReceived += OnResponseReceived; | ||
} | ||
|
||
public async IAsyncEnumerable<string> GetContentStreamAsync() | ||
{ | ||
while (await _channel.Reader.WaitToReadAsync()) | ||
{ | ||
while (_channel.Reader.TryRead(out var chunk)) | ||
{ | ||
yield return chunk; | ||
} | ||
} | ||
} | ||
|
||
private void OnDeltaReceived(object? sender, ChatResponseDelta delta) | ||
{ | ||
lock (_lock) | ||
{ | ||
if (_foundFinalAnswer) | ||
{ | ||
// Directly output content after "Final Answer: " has been detected | ||
_channel.Writer.TryWrite(delta.Content); | ||
} | ||
else | ||
{ | ||
// Accumulate the content in the buffer | ||
_buffer.Append(delta.Content); | ||
|
||
// Check if the buffer contains "Final Answer: " | ||
var bufferString = _buffer.ToString(); | ||
int finalAnswerIndex = bufferString.IndexOf("Final Answer:", StringComparison.Ordinal); | ||
|
||
if (finalAnswerIndex != -1) | ||
{ | ||
// Output everything after "Final Answer: " has been detected | ||
int startOutputIndex = finalAnswerIndex + "Final Answer:".Length; | ||
|
||
// Switch to streaming mode | ||
_foundFinalAnswer = true; | ||
|
||
// Output any content after "Final Answer: " | ||
_channel.Writer.TryWrite(bufferString[startOutputIndex..]); | ||
|
||
// Clear the buffer since it's no longer needed | ||
_buffer.Clear(); | ||
} | ||
} | ||
} | ||
} | ||
|
||
private void OnResponseReceived(object? sender, ChatResponse response) | ||
{ | ||
lock (_lock) | ||
{ | ||
_buffer.Clear(); // Clear buffer to avoid carrying over any previous data | ||
if (!_foundFinalAnswer) return; | ||
|
||
// Reset the state so that the process can start over | ||
_foundFinalAnswer = false; | ||
_channel.Writer.TryComplete(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,73 +1,32 @@ | ||
using OpenAI_API.Chat; | ||
using System.Text.Json; | ||
using System.Text.RegularExpressions; | ||
using ChatRPG.API.Response; | ||
using ChatRPG.Data.Models; | ||
using ChatRPG.Pages; | ||
using Microsoft.IdentityModel.Tokens; | ||
|
||
namespace ChatRPG.API; | ||
|
||
public class OpenAiGptMessage | ||
{ | ||
public OpenAiGptMessage(ChatMessageRole role, string content) | ||
public OpenAiGptMessage(MessageRole role, string content) | ||
{ | ||
Role = role; | ||
Content = content; | ||
NarrativePart = ""; | ||
UpdateNarrativePart(); | ||
if (!Content.IsNullOrEmpty() && NarrativePart.IsNullOrEmpty() && role.Equals(ChatMessageRole.Assistant)) | ||
{ | ||
NarrativePart = Content; | ||
} | ||
} | ||
|
||
public OpenAiGptMessage(ChatMessageRole role, string content, UserPromptType userPromptType) : this(role, content) | ||
public OpenAiGptMessage(MessageRole role, string content, UserPromptType userPromptType) : this(role, content) | ||
{ | ||
UserPromptType = userPromptType; | ||
} | ||
|
||
public ChatMessageRole Role { get; } | ||
public MessageRole Role { get; } | ||
public string Content { get; private set; } | ||
public string NarrativePart { get; private set; } | ||
public readonly UserPromptType UserPromptType = UserPromptType.Do; | ||
private static readonly Regex NarrativeRegex = | ||
new(pattern: "^\\s*{\\s*\"narrative\":\\s*\"([^\"]*)", RegexOptions.IgnoreCase); | ||
|
||
public LlmResponse? TryParseFromJson() | ||
{ | ||
try | ||
{ | ||
JsonSerializerOptions options = new() | ||
{ | ||
PropertyNameCaseInsensitive = true | ||
}; | ||
return JsonSerializer.Deserialize<LlmResponse>(Content, options); | ||
} | ||
catch (JsonException) | ||
{ | ||
return new LlmResponse { Narrative = Content }; // Format was unexpected | ||
} | ||
} | ||
|
||
public void AddChunk(string chunk) | ||
{ | ||
Content += chunk.Replace("\\\"", "'"); | ||
UpdateNarrativePart(); | ||
} | ||
|
||
private void UpdateNarrativePart() | ||
{ | ||
Match match = NarrativeRegex.Match(Content); | ||
if (match is { Success: true, Groups.Count: 2 }) | ||
{ | ||
NarrativePart = match.Groups[1].ToString(); | ||
} | ||
} | ||
|
||
public static OpenAiGptMessage FromMessage(Message message) | ||
{ | ||
ChatMessageRole role = ChatMessageRole.FromString(message.Role.ToString().ToLower()); | ||
return new OpenAiGptMessage(role, message.Content); | ||
return new OpenAiGptMessage(message.Role, message.Content); | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.