Skip to content

Commit

Permalink
feat: added file-based history (#121)
Browse files Browse the repository at this point in the history
  • Loading branch information
hiptopjones authored Jan 31, 2024
1 parent d17e1b4 commit d2bbe30
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 0 deletions.
7 changes: 7 additions & 0 deletions LangChain.sln
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LangChain.Providers.Automat
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LangChain.Providers.Automatic1111.IntegrationTests", "src\tests\LangChain.Providers.Automatic1111.IntegrationTests\LangChain.Providers.Automatic1111.IntegrationTests.csproj", "{A6CF79BC-8365-46E8-9230-1A4AD615D40B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LangChain.Samples.FileMemory", "examples\LangChain.Samples.FileMemory\LangChain.Samples.FileMemory.csproj", "{BA701280-0BEB-4DA4-92B3-9C777082C2AF}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -402,6 +404,10 @@ Global
{A6CF79BC-8365-46E8-9230-1A4AD615D40B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A6CF79BC-8365-46E8-9230-1A4AD615D40B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A6CF79BC-8365-46E8-9230-1A4AD615D40B}.Release|Any CPU.Build.0 = Release|Any CPU
{BA701280-0BEB-4DA4-92B3-9C777082C2AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BA701280-0BEB-4DA4-92B3-9C777082C2AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BA701280-0BEB-4DA4-92B3-9C777082C2AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BA701280-0BEB-4DA4-92B3-9C777082C2AF}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -470,6 +476,7 @@ Global
{4913844F-74EC-4E74-AE8A-EA825569E6BA} = {E55391DE-F8F3-4CC2-A0E3-2406C76E9C68}
{BF4C7B87-0997-4208-84EF-D368DF7B9861} = {E55391DE-F8F3-4CC2-A0E3-2406C76E9C68}
{A6CF79BC-8365-46E8-9230-1A4AD615D40B} = {FDEE2E22-C239-4921-83B2-9797F765FD6A}
{BA701280-0BEB-4DA4-92B3-9C777082C2AF} = {F17A86AE-A174-4B6C-BAA7-9D9A9704BE85}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5C00D0F1-6138-4ED9-846B-97E43D6DFF1C}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\libs\LangChain\LangChain.csproj" />
</ItemGroup>

</Project>
54 changes: 54 additions & 0 deletions examples/LangChain.Samples.FileMemory/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using LangChain.Memory;
using LangChain.Providers.OpenAI;
using static LangChain.Chains.Chain;

var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ??
throw new InvalidOperationException("OPENAI_API_KEY environment variable is not found.");

var model = new OpenAiModel(apiKey, "gpt-3.5-turbo");


// create simple template for conversation for AI to know what piece of text it is looking at
var template = @"
The following is a friendly conversation between a human and an AI.
{history}
Human: {input}
AI:";

// To have a conversation thar remembers previous messages we need to use memory.
// For memory to work properly we need to specify AI and Human prefixes.
// Since in our template we have "AI:" and "Human:" we need to specify them here. Pay attention to spaces after prefixes.
var conversationBufferMemory = new ConversationBufferMemory(new FileChatMessageHistory("messages.json"))
{
AiPrefix = "AI: ",
HumanPrefix = "Human: "
};

// build chain. Notice that we don't set input key here. It will be set in the loop
var chain =
// load history. at first it will be empty, but UpdateMemory will update it every iteration
LoadMemory(conversationBufferMemory, outputKey: "history")
| Template(template)
| LLM(model)
// update memory with new request from Human and response from AI
| UpdateMemory(conversationBufferMemory, requestKey: "input", responseKey: "text");

// run an endless loop of conversation
while (true)
{
Console.Write("Human: ");
var input = Console.ReadLine();
if (input == "exit")
break;

// build a new chain using previous chain but with new input every time
var chatChain = Set(input, "input")
| chain;

// get response from AI
var res = await chatChain.Run("text");


Console.Write("AI: ");
Console.WriteLine(res);
}
71 changes: 71 additions & 0 deletions src/libs/LangChain.Core/Memory/FileChatMessageHistory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using LangChain.Providers;
using System.Text.Json;

namespace LangChain.Memory;

/// <summary>
/// Stores history in a local file.
/// </summary>
public class FileChatMessageHistory : BaseChatMessageHistory
{
private string MessagesFilePath { get; }

private List<Message> _messages;

/// <inheritdoc/>
public override IReadOnlyList<Message> Messages
{
get
{
if (_messages is null)
{
LoadMessages().Wait();
}

return _messages;

Check warning on line 25 in src/libs/LangChain.Core/Memory/FileChatMessageHistory.cs

View workflow job for this annotation

GitHub Actions / Build, test and publish / Build, test and publish

Possible null reference return.

Check warning on line 25 in src/libs/LangChain.Core/Memory/FileChatMessageHistory.cs

View workflow job for this annotation

GitHub Actions / Build, test and publish / Build, test and publish

Possible null reference return.
}
}

/// <summary>
///
/// </summary>
/// <param name="messagesFilePath">Path to local history file</param>
/// <exception cref="ArgumentNullException"></exception>
public FileChatMessageHistory(string messagesFilePath)

Check warning on line 34 in src/libs/LangChain.Core/Memory/FileChatMessageHistory.cs

View workflow job for this annotation

GitHub Actions / Build, test and publish / Build, test and publish

Non-nullable field '_messages' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.
{
MessagesFilePath = messagesFilePath ?? throw new ArgumentNullException(nameof(messagesFilePath));
}

/// <inheritdoc/>
public override async Task AddMessage(Message message)
{
_messages.Add(message);
await SaveMessages();

Check warning on line 43 in src/libs/LangChain.Core/Memory/FileChatMessageHistory.cs

View workflow job for this annotation

GitHub Actions / Build, test and publish / Build, test and publish

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)
}

/// <inheritdoc/>
public override async Task Clear()
{
_messages.Clear();
await SaveMessages();

Check warning on line 50 in src/libs/LangChain.Core/Memory/FileChatMessageHistory.cs

View workflow job for this annotation

GitHub Actions / Build, test and publish / Build, test and publish

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)
}

private async Task SaveMessages()
{
string json = JsonSerializer.Serialize(_messages);
await Task.Run(() => File.WriteAllText(MessagesFilePath, json));

Check warning on line 56 in src/libs/LangChain.Core/Memory/FileChatMessageHistory.cs

View workflow job for this annotation

GitHub Actions / Build, test and publish / Build, test and publish

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)
}

private async Task LoadMessages()
{
if (File.Exists(MessagesFilePath))
{
string json = await Task.Run(() => File.ReadAllText(MessagesFilePath));

Check warning on line 63 in src/libs/LangChain.Core/Memory/FileChatMessageHistory.cs

View workflow job for this annotation

GitHub Actions / Build, test and publish / Build, test and publish

Consider calling ConfigureAwait on the awaited task (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2007)
_messages = JsonSerializer.Deserialize<List<Message>>(json);

Check warning on line 64 in src/libs/LangChain.Core/Memory/FileChatMessageHistory.cs

View workflow job for this annotation

GitHub Actions / Build, test and publish / Build, test and publish

Possible null reference assignment.
}
else
{
_messages = new List<Message>();
}
}
}

0 comments on commit d2bbe30

Please sign in to comment.