Skip to content

Commit

Permalink
Create chat service (#16)
Browse files Browse the repository at this point in the history
- Create chat service allowing to reuse chat operations on other scenarios
  • Loading branch information
skarllot authored Dec 30, 2024
1 parent 3509e28 commit a7cb774
Show file tree
Hide file tree
Showing 34 changed files with 1,491 additions and 369 deletions.
2 changes: 2 additions & 0 deletions src/FlowPair/Agent/Infrastructure/AgentJsonContext.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Collections.Immutable;
using System.Text.Json.Serialization;
using Ciandt.FlowTools.FlowPair.Agent.Operations.ReviewChanges.v1;
using Ciandt.FlowTools.FlowPair.Flow.Operations.ProxyCompleteChat.v1;

namespace Ciandt.FlowTools.FlowPair.Agent.Infrastructure;

Expand All @@ -11,4 +12,5 @@ namespace Ciandt.FlowTools.FlowPair.Agent.Infrastructure;
PropertyNameCaseInsensitive = true,
RespectNullableAnnotations = true)]
[JsonSerializable(typeof(ImmutableList<ReviewerFeedbackResponse>))]
[JsonSerializable(typeof(ImmutableList<ImmutableList<Message>>))]
public partial class AgentJsonContext : JsonSerializerContext;
4 changes: 4 additions & 0 deletions src/FlowPair/Agent/Infrastructure/IAgentModule.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Ciandt.FlowTools.FlowPair.Agent.Operations.Login;
using Ciandt.FlowTools.FlowPair.Agent.Operations.ReviewChanges;
using Ciandt.FlowTools.FlowPair.Agent.Services;
using Jab;

namespace Ciandt.FlowTools.FlowPair.Agent.Infrastructure;
Expand All @@ -9,6 +10,9 @@ namespace Ciandt.FlowTools.FlowPair.Agent.Infrastructure;
// Infrastructure
[Singleton(typeof(AgentJsonContext), Factory = nameof(GetJsonContext))]

// Services
[Singleton(typeof(IChatService), typeof(ChatService))]

// Operations
[Singleton(typeof(ILoginUseCase), typeof(LoginUseCase))]
[Singleton(typeof(LoginCommand))]
Expand Down
31 changes: 31 additions & 0 deletions src/FlowPair/Agent/Models/ChatScript.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.Collections.Immutable;

namespace Ciandt.FlowTools.FlowPair.Agent.Models;

public sealed record ChatScript(
string Name,
ImmutableArray<string> Extensions,
string SystemInstruction,
ImmutableList<Instruction> Instructions)
{
public const string StopKeywordPlaceholder = "<NO FEEDBACK>";

public double TotalSteps => Instructions
.Aggregate(
(IEnumerable<double>) [0D],
(curr, next) => next.Match(
StepInstruction: _ => curr.Select(v => v + 1),
MultiStepInstruction: x => Enumerable.Range(0, x.Messages.Count)
.Select((_, i) => i == 0 ? curr.First() + 1 : 1),
JsonConvertInstruction: _ => curr.Select(v => v + 1)))
.Sum();

public static Option<ChatScript> FindChatScriptForFile(
IReadOnlyList<ChatScript> scripts,
string filePath)
{
return scripts
.Reverse()
.FirstOrDefault(i => i.Extensions.Any(s => filePath.EndsWith(s, StringComparison.OrdinalIgnoreCase)));
}
}
116 changes: 116 additions & 0 deletions src/FlowPair/Agent/Models/ChatThread.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
using System.Collections.Immutable;
using Ciandt.FlowTools.FlowPair.Common;
using Ciandt.FlowTools.FlowPair.Flow.Operations.ProxyCompleteChat;
using Ciandt.FlowTools.FlowPair.Flow.Operations.ProxyCompleteChat.v1;
using Spectre.Console;

namespace Ciandt.FlowTools.FlowPair.Agent.Models;

public sealed record ChatThread(
ProgressTask Progress,
AllowedModel Model,
string StopKeyword,
Func<ReadOnlySpan<char>, Result<Unit, string>> ValidateJson,
ImmutableList<Message> Messages)
{
private const int MaxJsonRetries = 3;

public Message? LastMessage => Messages.Count > 0 ? Messages[^1] : null;

public bool IsInterrupted =>
LastMessage?.Role == Role.Assistant &&
LastMessage.Content.Contains(StopKeyword, StringComparison.Ordinal);

public bool IsCompleted => LastMessage?.Role == Role.Assistant;

public ChatThread AddMessages(params ReadOnlySpan<Message> newMessages) =>
this with { Messages = [..Messages, ..newMessages] };

public Result<ChatThread, string> RunStepInstruction(
Instruction.StepInstruction instruction,
IProxyCompleteChatHandler completeChatHandler)
{
try
{
if (IsInterrupted)
{
return this;
}

return AddMessages(instruction.ToMessage(StopKeyword))
.CompleteChat(completeChatHandler);
}
finally
{
Progress.Increment(1);
}
}

public Result<ChatThread, string> RunMultiStepInstruction(
Instruction.MultiStepInstruction instruction,
int index,
IProxyCompleteChatHandler completeChatHandler)
{
try
{
if (IsInterrupted)
{
return this;
}

return AddMessages(instruction.ToMessage(index, StopKeyword))
.CompleteChat(completeChatHandler);
}
finally
{
Progress.Increment(1);
}
}

public Result<ChatThread, string> RunJsonInstruction(
Instruction.JsonConvertInstruction instruction,
IProxyCompleteChatHandler completeChatHandler)
{
try
{
if (IsInterrupted)
{
return this;
}

return Enumerable.Range(0, MaxJsonRetries)
.TryAggregate(
AddMessages(instruction.ToMessage(StopKeyword)),
(chat, _) => chat.CompleteChatAndDeserialize(completeChatHandler));
}
finally
{
Progress.Increment(1);
}
}

private Result<ChatThread, string> CompleteChat(
IProxyCompleteChatHandler completeChatHandler)
{
return completeChatHandler.ChatCompletion(Model, Messages)
.Match<Result<ChatThread, string>>(
msg => this with { Messages = Messages.Add(msg) },
error => error.ToString());
}

private Result<ChatThread, string> CompleteChatAndDeserialize(
IProxyCompleteChatHandler completeChatHandler)
{
if (IsCompleted)
{
return this;
}

return (from message in completeChatHandler.ChatCompletion(Model, Messages)
select ValidateJson(message.Content)
.Match(
_ => AddMessages(message),
e => AddMessages(message, new Message(Role.User, e))))
.MapErr(error => error.ToString());
}
}
54 changes: 54 additions & 0 deletions src/FlowPair/Agent/Models/ChatWorkspace.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System.Collections.Immutable;
using Ciandt.FlowTools.FlowPair.Flow.Operations.ProxyCompleteChat;

namespace Ciandt.FlowTools.FlowPair.Agent.Models;

public sealed record ChatWorkspace(
ImmutableList<ChatThread> ChatThreads)
{
public Result<ChatWorkspace, string> RunInstruction(
Instruction instruction,
IProxyCompleteChatHandler completeChatHandler)
{
return instruction.Match(
StepInstruction: x => RunStepInstruction(x, completeChatHandler),
MultiStepInstruction: x => RunMultiStepInstruction(x, completeChatHandler),
JsonConvertInstruction: x => RunJsonInstruction(x, completeChatHandler));
}

private Result<ChatWorkspace, string> RunStepInstruction(
Instruction.StepInstruction instruction,
IProxyCompleteChatHandler completeChatHandler)
{
return ChatThreads.AsParallel()
.Select(thread => thread.RunStepInstruction(instruction, completeChatHandler))
.Sequence()
.Select(list => new ChatWorkspace(ChatThreads: list.ToImmutableList()));
}

private Result<ChatWorkspace, string> RunMultiStepInstruction(
Instruction.MultiStepInstruction instruction,
IProxyCompleteChatHandler completeChatHandler)
{
if (ChatThreads.Count != 1)
{
return "Only one multi-step instruction is supported.";
}

return Enumerable.Repeat(ChatThreads[0], instruction.Messages.Count)
.AsParallel()
.Select((thread, i) => thread.RunMultiStepInstruction(instruction, i, completeChatHandler))
.Sequence()
.Select(list => new ChatWorkspace(ChatThreads: list.ToImmutableList()));
}

private Result<ChatWorkspace, string> RunJsonInstruction(
Instruction.JsonConvertInstruction instruction,
IProxyCompleteChatHandler completeChatHandler)
{
return ChatThreads.AsParallel()
.Select(thread => thread.RunJsonInstruction(instruction, completeChatHandler))
.Sequence()
.Select(list => new ChatWorkspace(ChatThreads: list.ToImmutableList()));
}
}
36 changes: 36 additions & 0 deletions src/FlowPair/Agent/Models/Instructions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Collections.Immutable;
using Ciandt.FlowTools.FlowPair.Flow.Operations.ProxyCompleteChat.v1;
using FxKit.CompilerServices;

namespace Ciandt.FlowTools.FlowPair.Agent.Models;

[Union]
public partial record Instruction
{
partial record StepInstruction(string Message)
{
public Message ToMessage(string stopKeyword) => new(
Role.User,
Message.Replace(ChatScript.StopKeywordPlaceholder, stopKeyword));
}

partial record MultiStepInstruction(string Preamble, ImmutableList<string> Messages, string Ending)
{
public Message ToMessage(int index, string stopKeyword) => new(
Role.User,
$"{Preamble}{Messages[index]}{Ending}"
.Replace(ChatScript.StopKeywordPlaceholder, stopKeyword));
}

partial record JsonConvertInstruction(string Message, string JsonSchema)
{
public Message ToMessage(string stopKeyword) => new(
Role.User,
$"""
{Message.Replace(ChatScript.StopKeywordPlaceholder, stopKeyword)}
```
{JsonSchema}
```
""");
}
}
11 changes: 0 additions & 11 deletions src/FlowPair/Agent/Operations/ReviewChanges/ChatThreadSpec.cs

This file was deleted.

41 changes: 0 additions & 41 deletions src/FlowPair/Agent/Operations/ReviewChanges/ContentDeserializer.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<#@ template language="C#" debug="false" linePragmas="false" hostspecific="false" inherits="CodeWriterBase<ImmutableList<ReviewerFeedbackResponse>>" #>
<#@ import namespace="System.Collections.Immutable" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="Ciandt.FlowTools.FlowPair.Common" #>
<#@ import namespace="Common" #>
<#@ import namespace="Raiqub.Generators.T4CodeWriter" #>
<#@ import namespace="Ciandt.FlowTools.FlowPair.Agent.Operations.ReviewChanges.v1" #>
<#@ import namespace="v1" #>
<!DOCTYPE html>
<html>
<head>
Expand Down
14 changes: 0 additions & 14 deletions src/FlowPair/Agent/Operations/ReviewChanges/Instructions.cs

This file was deleted.

Loading

0 comments on commit a7cb774

Please sign in to comment.