Skip to content

Commit 1021ae0

Browse files
LittleLittleCloudvictordibia
authored andcommitted
[.Net] add ReAct sample (#2977)
* add ReAct sample * fix source geenrator test
1 parent 0027699 commit 1021ae0

File tree

5 files changed

+193
-5
lines changed

5 files changed

+193
-5
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Example17_ReActAgent.cs
3+
4+
using AutoGen.Core;
5+
using AutoGen.OpenAI;
6+
using AutoGen.OpenAI.Extension;
7+
using Azure.AI.OpenAI;
8+
9+
namespace AutoGen.BasicSample;
10+
11+
public class OpenAIReActAgent : IAgent
12+
{
13+
private readonly OpenAIClient _client;
14+
private readonly string modelName = "gpt-3.5-turbo";
15+
private readonly FunctionContract[] tools;
16+
private readonly Dictionary<string, Func<string, Task<string>>> toolExecutors = new();
17+
private readonly IAgent reasoner;
18+
private readonly IAgent actor;
19+
private readonly IAgent helper;
20+
private readonly int maxSteps = 10;
21+
22+
private const string ReActPrompt = @"Answer the following questions as best you can.
23+
You can invoke the following tools:
24+
{tools}
25+
26+
Use the following format:
27+
28+
Question: the input question you must answer
29+
Thought: you should always think about what to do
30+
Tool: the tool to invoke
31+
Tool Input: the input to the tool
32+
Observation: the invoke result of the tool
33+
... (this process can repeat multiple times)
34+
35+
Once you have the final answer, provide the final answer in the following format:
36+
Thought: I now know the final answer
37+
Final Answer: the final answer to the original input question
38+
39+
Begin!
40+
Question: {input}";
41+
42+
public OpenAIReActAgent(OpenAIClient client, string modelName, string name, FunctionContract[] tools, Dictionary<string, Func<string, Task<string>>> toolExecutors)
43+
{
44+
_client = client;
45+
this.Name = name;
46+
this.modelName = modelName;
47+
this.tools = tools;
48+
this.toolExecutors = toolExecutors;
49+
this.reasoner = CreateReasoner();
50+
this.actor = CreateActor();
51+
this.helper = new OpenAIChatAgent(client, "helper", modelName)
52+
.RegisterMessageConnector();
53+
}
54+
55+
public string Name { get; }
56+
57+
public async Task<IMessage> GenerateReplyAsync(IEnumerable<IMessage> messages, GenerateReplyOptions? options = null, CancellationToken cancellationToken = default)
58+
{
59+
// step 1: extract the input question
60+
var userQuestion = await helper.SendAsync("Extract the question from chat history", chatHistory: messages);
61+
if (userQuestion.GetContent() is not string question)
62+
{
63+
return new TextMessage(Role.Assistant, "I couldn't find a question in the chat history. Please ask a question.", from: Name);
64+
}
65+
var reactPrompt = CreateReActPrompt(question);
66+
var promptMessage = new TextMessage(Role.User, reactPrompt);
67+
var chatHistory = new List<IMessage>() { promptMessage };
68+
69+
// step 2: ReAct
70+
for (int i = 0; i != this.maxSteps; i++)
71+
{
72+
// reasoning
73+
var reasoning = await reasoner.SendAsync(chatHistory: chatHistory);
74+
if (reasoning.GetContent() is not string reasoningContent)
75+
{
76+
return new TextMessage(Role.Assistant, "I couldn't find a reasoning in the chat history. Please provide a reasoning.", from: Name);
77+
}
78+
if (reasoningContent.Contains("I now know the final answer"))
79+
{
80+
return new TextMessage(Role.Assistant, reasoningContent, from: Name);
81+
}
82+
83+
chatHistory.Add(reasoning);
84+
85+
// action
86+
var action = await actor.SendAsync(reasoning);
87+
chatHistory.Add(action);
88+
}
89+
90+
// fail to find the final answer
91+
// return the summary of the chat history
92+
var summary = await helper.SendAsync("Summarize the chat history and find out what's missing", chatHistory: chatHistory);
93+
summary.From = Name;
94+
95+
return summary;
96+
}
97+
98+
private string CreateReActPrompt(string input)
99+
{
100+
var toolPrompt = tools.Select(t => $"{t.Name}: {t.Description}").Aggregate((a, b) => $"{a}\n{b}");
101+
var prompt = ReActPrompt.Replace("{tools}", toolPrompt);
102+
prompt = prompt.Replace("{input}", input);
103+
return prompt;
104+
}
105+
106+
private IAgent CreateReasoner()
107+
{
108+
return new OpenAIChatAgent(
109+
openAIClient: _client,
110+
modelName: modelName,
111+
name: "reasoner")
112+
.RegisterMessageConnector()
113+
.RegisterPrintMessage();
114+
}
115+
116+
private IAgent CreateActor()
117+
{
118+
var functionCallMiddleware = new FunctionCallMiddleware(tools, toolExecutors);
119+
return new OpenAIChatAgent(
120+
openAIClient: _client,
121+
modelName: modelName,
122+
name: "actor")
123+
.RegisterMessageConnector()
124+
.RegisterMiddleware(functionCallMiddleware)
125+
.RegisterPrintMessage();
126+
}
127+
}
128+
129+
public partial class Tools
130+
{
131+
/// <summary>
132+
/// Get weather report for a specific place on a specific date
133+
/// </summary>
134+
/// <param name="city">city</param>
135+
/// <param name="date">date as DD/MM/YYYY</param>
136+
[Function]
137+
public async Task<string> WeatherReport(string city, string date)
138+
{
139+
return $"Weather report for {city} on {date} is sunny";
140+
}
141+
142+
/// <summary>
143+
/// Get current localization
144+
/// </summary>
145+
[Function]
146+
public async Task<string> GetLocalization(string dummy)
147+
{
148+
return $"Paris";
149+
}
150+
151+
/// <summary>
152+
/// Get current date as DD/MM/YYYY
153+
/// </summary>
154+
[Function]
155+
public async Task<string> GetDateToday(string dummy)
156+
{
157+
return $"27/05/2024";
158+
}
159+
}
160+
161+
public class Example17_ReActAgent
162+
{
163+
public static async Task RunAsync()
164+
{
165+
var openAIKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new Exception("Please set OPENAI_API_KEY environment variable.");
166+
var modelName = "gpt-4-turbo";
167+
var tools = new Tools();
168+
var openAIClient = new OpenAIClient(openAIKey);
169+
var reactAgent = new OpenAIReActAgent(
170+
client: openAIClient,
171+
modelName: modelName,
172+
name: "react-agent",
173+
tools: [tools.GetLocalizationFunctionContract, tools.GetDateTodayFunctionContract, tools.WeatherReportFunctionContract],
174+
toolExecutors: new Dictionary<string, Func<string, Task<string>>>
175+
{
176+
{ tools.GetLocalizationFunctionContract.Name, tools.GetLocalizationWrapper },
177+
{ tools.GetDateTodayFunctionContract.Name, tools.GetDateTodayWrapper },
178+
{ tools.WeatherReportFunctionContract.Name, tools.WeatherReportWrapper },
179+
}
180+
)
181+
.RegisterPrintMessage();
182+
183+
var message = new TextMessage(Role.User, "What is the weather here", from: "user");
184+
185+
var response = await reactAgent.SendAsync(message);
186+
}
187+
}

dotnet/sample/AutoGen.BasicSamples/Program.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33

44
using AutoGen.BasicSample;
55
Console.ReadLine();
6-
await Example16_OpenAIChatAgent_ConnectToThirdPartyBackend.RunAsync();
6+
await Example17_ReActAgent.RunAsync();

dotnet/src/AutoGen.SourceGenerator/Template/FunctionCallTemplate.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,8 @@ public virtual string TransformText()
121121
this.Write("\",\r\n");
122122
}
123123
if (functionContract.Parameters != null) {
124-
this.Write(" Parameters = new []\r\n {\r\n");
124+
this.Write(" Parameters = new global::AutoGen.Core.FunctionParameterContract[]" +
125+
"\r\n {\r\n");
125126
foreach (var parameter in functionContract.Parameters) {
126127
this.Write(" new FunctionParameterContract\r\n {\r\n");
127128
if (parameter.Name != null) {

dotnet/src/AutoGen.SourceGenerator/Template/FunctionCallTemplate.tt

+2-2
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ namespace <#=NameSpace#>
7272
ReturnDescription = @"<#=functionContract.ReturnDescription#>",
7373
<#}#>
7474
<#if (functionContract.Parameters != null) {#>
75-
Parameters = new []
75+
Parameters = new global::AutoGen.Core.FunctionParameterContract[]
7676
{
7777
<#foreach (var parameter in functionContract.Parameters) {#>
7878
new FunctionParameterContract
@@ -110,6 +110,6 @@ namespace <#=NameSpace#>
110110
<#+
111111
public string NameSpace {get; set;}
112112
public string ClassName {get; set;}
113-
public IEnumerable<FunctionContract> FunctionContracts {get; set;}
113+
public IEnumerable<SourceGeneratorFunctionContract> FunctionContracts {get; set;}
114114
public bool IsStatic {get; set;} = false;
115115
#>

dotnet/test/AutoGen.SourceGenerator.Tests/ApprovalTests/FunctionCallTemplateTests.TestFunctionCallTemplate.approved.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ namespace AutoGen.SourceGenerator.Tests
4242
Name = @"AddAsync",
4343
Description = @"Add two numbers.",
4444
ReturnType = typeof(System.Threading.Tasks.Task`1[System.String]),
45-
Parameters = new []
45+
Parameters = new global::AutoGen.Core.FunctionParameterContract[]
4646
{
4747
new FunctionParameterContract
4848
{

0 commit comments

Comments
 (0)