Skip to content

Commit 9f192c5

Browse files
Add document writer (#140)
* add document writer workflow * improve UI
1 parent 35e4029 commit 9f192c5

19 files changed

+860
-73
lines changed

StepWise.sln

+8-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StepWise.WebAPI", "src\Step
2929
EndProject
3030
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StepWise.WebAPI.Tests", "test\StepWise.WebAPI.Tests\StepWise.WebAPI.Tests.csproj", "{DAE8E54E-0A43-4FD7-9D75-6081792FA94E}"
3131
EndProject
32-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StepWise.Gallery", "example\HelloWorld\StepWise.Gallery.csproj", "{8BD13BAB-263D-403F-93CC-FB628FA2E0C2}"
32+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StepWise.Gallery.Basic", "example\HelloWorld\StepWise.Gallery.Basic.csproj", "{8BD13BAB-263D-403F-93CC-FB628FA2E0C2}"
3333
EndProject
3434
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StepWise.WebUI", "src\StepWise.WebUI\StepWise.WebUI.csproj", "{7DD9A65A-C68D-464A-AFDB-21E9A3C6AA0E}"
3535
EndProject
@@ -41,6 +41,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "template", "template", "{02
4141
EndProject
4242
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StepWise.Template", "template\StepWise.Template.csproj", "{E2D7CB90-C283-9851-BC17-380194A3AEA5}"
4343
EndProject
44+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StepWise.Gallery.Agentic", "example\StepWise.Gallery.Agentic\StepWise.Gallery.Agentic.csproj", "{567C38AD-31FC-4D5B-A891-DEE41AFF0AA8}"
45+
EndProject
4446
Global
4547
GlobalSection(SolutionConfigurationPlatforms) = preSolution
4648
Debug|Any CPU = Debug|Any CPU
@@ -91,6 +93,10 @@ Global
9193
{E2D7CB90-C283-9851-BC17-380194A3AEA5}.Debug|Any CPU.Build.0 = Debug|Any CPU
9294
{E2D7CB90-C283-9851-BC17-380194A3AEA5}.Release|Any CPU.ActiveCfg = Release|Any CPU
9395
{E2D7CB90-C283-9851-BC17-380194A3AEA5}.Release|Any CPU.Build.0 = Release|Any CPU
96+
{567C38AD-31FC-4D5B-A891-DEE41AFF0AA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
97+
{567C38AD-31FC-4D5B-A891-DEE41AFF0AA8}.Debug|Any CPU.Build.0 = Debug|Any CPU
98+
{567C38AD-31FC-4D5B-A891-DEE41AFF0AA8}.Release|Any CPU.ActiveCfg = Release|Any CPU
99+
{567C38AD-31FC-4D5B-A891-DEE41AFF0AA8}.Release|Any CPU.Build.0 = Release|Any CPU
94100
EndGlobalSection
95101
GlobalSection(SolutionProperties) = preSolution
96102
HideSolutionNode = FALSE
@@ -107,6 +113,7 @@ Global
107113
{3D5EDF70-D0B3-4552-8185-BE5EB11C7ADA} = {19750AFD-3091-4569-9D89-8D5735C3EBFC}
108114
{218B897C-F0F6-4B8F-A3F4-2289B6B5415F} = {0D861355-C022-4E1A-8C7B-7D1C3A066EE3}
109115
{E2D7CB90-C283-9851-BC17-380194A3AEA5} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
116+
{567C38AD-31FC-4D5B-A891-DEE41AFF0AA8} = {0D861355-C022-4E1A-8C7B-7D1C3A066EE3}
110117
EndGlobalSection
111118
GlobalSection(ExtensibilityGlobals) = postSolution
112119
SolutionGuid = {55953F2E-2283-4F22-9B79-17E81B54BDCE}

example/StepWIse.Gallery.Web/StepWise.Gallery.Web.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
</PropertyGroup>
88

99
<ItemGroup>
10-
<ProjectReference Include="..\HelloWorld\StepWise.Gallery.csproj" />
10+
<ProjectReference Include="..\HelloWorld\StepWise.Gallery.Basic.csproj" />
1111
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" />
1212
</ItemGroup>
1313

+232
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
// Copyright (c) LittleLittleCloud. All rights reserved.
2+
// CoT.cs
3+
4+
using System.Text;
5+
using System.Text.Json;
6+
using System.Text.Json.Serialization;
7+
using OpenAI;
8+
using OpenAI.Chat;
9+
using StepWise.Core;
10+
11+
namespace StepWise.Gallery.Agentic;
12+
13+
public class CoT
14+
{
15+
private readonly ChatClient _chatClient;
16+
private SubTasks? _subTasks;
17+
public CoT()
18+
{
19+
var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new Exception("Please set the OPENAI_API_KEY environment variable");
20+
var chatClient = new OpenAIClient(apiKey)
21+
.GetChatClient("gpt-4o");
22+
23+
_chatClient = chatClient;
24+
}
25+
26+
[Step(description: """
27+
This workflow is designed to help you resolve tasks by generating a chain of thoughts to break down the task into smaller sub-tasks and resolve them one by one.
28+
29+
## Pre-requisite
30+
- env: OPENAI_API_KEY: OpenAI API key
31+
""")]
32+
public async Task<string> README()
33+
{
34+
return "README";
35+
}
36+
37+
[Step(description: """
38+
You always resolve tasks by using a chain of thought to break down the task into smaller sub-tasks and resolve them one by one using ResolveSubTask.
39+
Then you summarize the chain of thoughts and generate the final reply.
40+
""")]
41+
public async Task<string> SystemMessage()
42+
{
43+
return "Start"; // doesn't matter
44+
}
45+
46+
[StepWiseUITextInput]
47+
public async Task<string?> GetTask()
48+
{
49+
return null;
50+
}
51+
52+
[Step(description: "Initialize chain of thoughts for the given task")]
53+
[DependOn(nameof(GetTask))]
54+
public async Task<SubTasks> InitializeChainOfThought(
55+
[FromStep(nameof(GetTask))] string task)
56+
{
57+
var prompt = @$"
58+
Given the following task:
59+
{task}
60+
61+
Generate a chain of actions that you would take to complete the task.
62+
63+
The chain of thoughts should be a list of sub-tasks that you would perform to complete the task.
64+
Each sub-task should have a `task` field that describes the sub-task.
65+
66+
Your response must be a valid JSON array of sub-tasks. Don't put the JSON array in a code block.
67+
68+
For example:
69+
[
70+
{{
71+
""task"": ""Task 1""
72+
}},
73+
{{
74+
""task"": ""Task 2""
75+
}}
76+
]";
77+
var systemMessage = new SystemChatMessage(prompt);
78+
var response = await _chatClient.CompleteChatAsync(systemMessage);
79+
80+
if (response.Value.Content.Count == 1 && response.Value.Content[0].Text is string text)
81+
{
82+
try
83+
{
84+
var subTasks = JsonSerializer.Deserialize<SubTask[]>(text) ?? throw new Exception("Invalid response from OpenAI");
85+
86+
_subTasks = new SubTasks(subTasks);
87+
88+
return _subTasks;
89+
}
90+
catch (JsonException)
91+
{
92+
throw new Exception($"Invalid JSON response from OpenAI: {text}");
93+
}
94+
}
95+
else
96+
{
97+
throw new Exception("Invalid response from OpenAI");
98+
}
99+
}
100+
101+
/// <summary>
102+
/// Resolve i-th task from the task list and return the result
103+
/// </summary>
104+
/// <param name="tasks"></param>
105+
/// <param name="index">if index is greater than the number of tasks, it inidcates that all tasks are resolved and return null</param>
106+
/// <returns></returns>
107+
/// <exception cref="Exception"></exception>
108+
[Step(description: "Resolve i-th task from the task list and return the result")]
109+
[DependOn(nameof(InitializeChainOfThought))]
110+
public async Task<(int, string)?> ResolveSubTask(
111+
[FromStep(nameof(InitializeChainOfThought))] SubTasks tasks,
112+
[FromStep(nameof(NextAsync))] int index = 0)
113+
{
114+
if (index < 0)
115+
{
116+
throw new Exception("Invalid index");
117+
}
118+
119+
if (index >= tasks.tasks.Length)
120+
{
121+
return null;
122+
}
123+
124+
var sb = new StringBuilder();
125+
for (var i = 0; i <= index; i++)
126+
{
127+
sb.AppendLine(tasks.tasks[i].ToString());
128+
}
129+
130+
var prompt = sb.ToString();
131+
132+
var systemMessage = new SystemChatMessage(prompt);
133+
134+
var response = await _chatClient.CompleteChatAsync(systemMessage);
135+
136+
if (response.Value.Content.Count == 1 && response.Value.Content[0].Text is string text)
137+
{
138+
_subTasks!.tasks[index] = _subTasks.tasks[index] with { IntermediateResult = text };
139+
return (index, text);
140+
}
141+
else
142+
{
143+
throw new Exception("Invalid response from OpenAI");
144+
}
145+
}
146+
147+
[Step(description: "Move to the next sub-task")]
148+
public async Task<int> NextAsync(
149+
[FromStep(nameof(ResolveSubTask))] (int, string)? result)
150+
{
151+
return result?.Item1 + 1 ?? 0;
152+
}
153+
154+
[Step(description: "Summarize the chain of thoughts and generate final reply")]
155+
[DependOn(nameof(NextAsync))]
156+
public async Task<string> SummarizeChainOfThought(
157+
[FromStep(nameof(NextAsync))] int index)
158+
{
159+
if (index >= _subTasks!.tasks.Length)
160+
{
161+
var sb = new StringBuilder();
162+
foreach (var task in _subTasks.tasks)
163+
{
164+
sb.AppendLine(task.ToString());
165+
}
166+
167+
var prompt = sb.ToString();
168+
169+
var systemMessage = $"""
170+
Summarize the chain of thoughts and generate the final reply.
171+
172+
The chain of thoughts is as follows:
173+
{prompt}
174+
""";
175+
176+
var response = await _chatClient.CompleteChatAsync(new SystemChatMessage(systemMessage));
177+
178+
if (response.Value.Content.Count == 1 && response.Value.Content[0].Text is string text)
179+
{
180+
return text;
181+
}
182+
else
183+
{
184+
throw new Exception("Invalid response from OpenAI");
185+
}
186+
}
187+
else
188+
{
189+
return "Not all tasks are resolved";
190+
}
191+
}
192+
193+
public record class SubTasks(SubTask[] tasks)
194+
{
195+
public override string ToString()
196+
{
197+
var sb = new StringBuilder();
198+
foreach (var task in tasks)
199+
{
200+
sb.AppendLine(task.ToString());
201+
}
202+
return sb.ToString();
203+
}
204+
}
205+
206+
public record class SubTask
207+
{
208+
public SubTask(string task, string? intermediateResult = null)
209+
{
210+
Task = task;
211+
IntermediateResult = intermediateResult;
212+
}
213+
214+
[JsonPropertyName("task")]
215+
public string Task { get; init; }
216+
217+
[JsonPropertyName("result")]
218+
public string? IntermediateResult { get; init; }
219+
220+
public override string ToString()
221+
{
222+
if (IntermediateResult is not null)
223+
{
224+
return $"Task: {Task}, Intermediate Result: {IntermediateResult}";
225+
}
226+
else
227+
{
228+
return $"Task: {Task}";
229+
}
230+
}
231+
}
232+
}

0 commit comments

Comments
 (0)