Skip to content

Commit 839495f

Browse files
committed
move methods onto ChatClient
Add Generate methods
1 parent f7956d7 commit 839495f

File tree

5 files changed

+191
-30
lines changed

5 files changed

+191
-30
lines changed

README.md

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,35 @@ This operator let's you ask a question for each item in a collection.
129129
var answers = items.Answer<float>(model, "What is the cost?");
130130
```
131131

132-
# Defining new operators
133-
All of these operators are built up of 2 core operators
134-
* ***TransformItem()/TransformItemAsync()*** - which allows you to give a transformation goal and instructions for a single item.
135-
* ***TransformItems()*** - Which allows you a transformation goal and instructions for each element in a enumerable collection.
132+
# Model Extensions
133+
All of these extensions are built up using 3 ChatClient extensions as primitives.
134+
135+
| Extension | Description |
136+
| ----------| ------------|
137+
| ***.Generate()/.GenerateAsync()*** | use a model and a goal to return a shaped result. |
138+
| ***.TransformItem()/.TransformItemAsync()*** | use a model, object and goal to transform object into shaped result. |
139+
| ***.TransformItems()/.TransformItemsAsync()*** | use a model, collection and goal to transform each object in the collection into a collection of shaped results. |
140+
141+
## model.Generate()/model.GenerateAsync()
142+
Given a model and a goal return a shaped result.
143+
```csharp
144+
var names = model.Generate<string[]>("funny names for people named bob");
145+
var cities = model.Generate<City>("return the top 5 largest cities in the world.");
146+
```
136147

148+
## model.TransformItem()/model.TransformItemAsync()
149+
Given a model and item, transform it into the shaped result using goal to guide the transformation.
150+
```csharp
151+
var piglatin = model.TransformItem<string>("cow", "transform to pig latin"); // outputs "owcay"
152+
```
153+
154+
## model.TransformItem()/model.TransformItemAsync()
155+
Given a model and item, transform it into the shaped result using goal to guide the transformation.
156+
```csharp
157+
var piglatin = model.TransformItems<string>(["cow", "dog", "pig"], "transform to pig latin"); // outputs ["owcay", "ogday", "igpay"]
158+
```
159+
160+
# Defining new operators
137161
To create a custom operator you create an static class and define static methods for transforming an object or collection of objects.
138162

139163
For example, here is the implementation of Summarize():
@@ -144,16 +168,16 @@ For example, here is the implementation of Summarize():
144168
```csharp
145169
public static class SummarizeExtension
146170
{
147-
// Object operator
171+
// operator to summarize object
148172
public static string Summarize(this object source, ChatClient model, string? goal, string? instructions = null, CancellationToken cancellationToken = default)
149173
=> source.TransformItem<string>(model, goal ?? "create a summarization", instructions, cancellationToken);
150174

151-
// Object operator
175+
// operator to summarize object
152176
public static Task<string> SummarizeAsync(this object source, ChatClient model, string? goal, string? instructions = null, CancellationToken cancellationToken = default)
153177
=> source.TransformItemAsync<string>(model, goal ?? "create a summarization", instructions, cancellationToken);
154178

155-
// collection operator
179+
// operator to summarize collection of objects
156180
public static IList<string> Summarize(this IEnumerable<object> source, ChatClient model, string? goal = null, string? instructions = null, int? maxParallel = null, CancellationToken cancellationToken = default)
157-
=> source.TransformItem<string>(model, goal ?? "create a summarization", instructions, maxParallel, cancellationToken);
181+
=> source.TransformItems<string>(model, goal ?? "create a summarization", instructions, maxParallel, cancellationToken);
158182
}
159183
```
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
using System.ComponentModel;
2+
3+
namespace Linq.AI.OpenAI.Tests
4+
{
5+
6+
7+
[TestClass]
8+
public class MiscTests : UnitTestBase
9+
{
10+
11+
[TestMethod]
12+
public async Task Generate_Text()
13+
{
14+
var results = await Model.GenerateAsync<string>("a haiku about camping");
15+
Assert.IsTrue(results.ToLower().Contains("camp"));
16+
}
17+
18+
[TestMethod]
19+
public void Generate_Object()
20+
{
21+
var result = Model.Generate<CityObject>("a city object for Ames, Iowa");
22+
Assert.AreEqual("Ames", result.Name);
23+
}
24+
25+
[TestMethod]
26+
public async Task Generate_Collection_Text()
27+
{
28+
var results = await Model.GenerateAsync<string[]>("return the top 5 largest cities in the world");
29+
Assert.IsTrue(results.Any(item => item.Contains("Mexico City")));
30+
Assert.AreEqual(5, results.Count());
31+
}
32+
33+
[TestMethod]
34+
public async Task Generate_Collection_Text2()
35+
{
36+
var results = await Model.GenerateAsync<string[]>("a list of funny names for people named bob");
37+
foreach(var result in results)
38+
{
39+
Assert.IsTrue(result.ToLower().Contains("bob"));
40+
}
41+
}
42+
43+
44+
45+
[TestMethod]
46+
public void Generate_Collections_Object()
47+
{
48+
var generated = Model.Generate<CityObject[]>("return the top 5 largest cities in the world.");
49+
Assert.IsTrue(generated.Any(item => item.Name == "Mexico City" && item.Country == "Mexico" ));
50+
Assert.AreEqual(5, generated.Count());
51+
}
52+
}
53+
54+
internal class CityObject
55+
{
56+
[System.ComponentModel.Description("Name of the city")]
57+
public string Name { get; set; }
58+
59+
[System.ComponentModel.Description("Name of the Country")]
60+
public string Country { get; set; }
61+
}
62+
}

source/Linq.AI.OpenAI.Tests/TransformTests.cs

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,22 @@ public class TransformationTests : UnitTestBase
1313
public async Task Transform_String2String()
1414
{
1515
var source = "My name is Tom.";
16-
var transformation = source.TransformItem<string>(Model, "into spanish");
16+
var transformation = Model.TransformItem<string>(source, "into spanish");
1717
Assert.AreEqual("Me llamo Tom.", transformation!);
1818

19-
transformation = await source.TransformItemAsync<string>(Model, "into spanish");
19+
transformation = await Model.TransformItemAsync<string>(source, "into spanish");
2020
Assert.AreEqual("Me llamo Tom.", transformation!);
2121
}
2222

2323
[TestMethod]
2424
public async Task Transform_String2Object()
2525
{
2626
var source = "I have 4 children and my name is Inigo Montoya.";
27-
var obj = source.TransformItem<TestObject>(Model);
27+
var obj = Model.TransformItem<TestObject>(source);
2828
Assert.AreEqual("Inigo Montoya", obj.Name);
2929
Assert.AreEqual(4, obj.Count);
3030

31-
obj = await source.TransformItemAsync<TestObject>(Model);
31+
obj = await Model.TransformItemAsync<TestObject>(source);
3232
Assert.AreEqual("Inigo Montoya", obj.Name);
3333
Assert.AreEqual(4, obj.Count);
3434
}
@@ -37,7 +37,7 @@ public async Task Transform_String2Object()
3737
public async Task Transform_Object2Object()
3838
{
3939
var source = new TestObject2() { FirstName = "Inigo", LastName = "Montoya" };
40-
var obj = await source.TransformItemAsync<TestObject>(Model, instructions: "Do not fill in properties that you don't have data for.");
40+
var obj = await Model.TransformItemAsync<TestObject>(source, instructions: "Do not fill in properties that you don't have data for.");
4141
Assert.AreEqual("Inigo Montoya", obj.Name);
4242
Assert.AreEqual(0, obj.Count);
4343
}
@@ -46,36 +46,36 @@ public async Task Transform_Object2Object()
4646
public async Task Transform_Bool()
4747
{
4848
var source = new TestObject2() { FirstName = "Inigo", LastName = "Montoya" };
49-
Assert.IsTrue(await source.TransformItemAsync<bool>(Model, "return true if the <ITEM> has a character in princess bride"));
50-
Assert.IsFalse(await source.TransformItemAsync<bool>(Model, "return true if if the <ITEM> has a character in star wars"));
49+
Assert.IsTrue(await Model.TransformItemAsync<bool>(source, "return true if the <ITEM> has a character in princess bride"));
50+
Assert.IsFalse(await Model.TransformItemAsync<bool>(source, "return true if if the <ITEM> has a character in star wars"));
5151
}
5252

5353
[TestMethod]
5454
public async Task Transform_Classify()
5555
{
56-
Assert.AreEqual(TestCategories.Car, await "Ford".TransformItemAsync<TestCategories>(Model, "classify"));
57-
Assert.AreEqual(TestCategories.Plane, await "Cessna".TransformItemAsync<TestCategories>(Model, "classify"));
56+
Assert.AreEqual(TestCategories.Car, await Model.TransformItemAsync<TestCategories>("Ford", "classify"));
57+
Assert.AreEqual(TestCategories.Plane, await Model.TransformItemAsync<TestCategories>("Cessna", "classify"));
5858
}
5959

6060
[TestMethod]
6161
public async Task Transform_Classify_text()
6262
{
6363

6464
var categories = String.Join(",", Enum.GetNames<TestCategories>());
65-
Assert.AreEqual("Car", await "Ford".TransformItemAsync<string>(Model, $"Pick the category from this list: {categories}"));
66-
Assert.AreEqual("Plane", await "Cessna".TransformItemAsync<string>(Model, $"Pick the category from this list: {categories}"));
65+
Assert.AreEqual("Car", await Model.TransformItemAsync<string>("Ford", $"Pick the category from this list: {categories}"));
66+
Assert.AreEqual("Plane", await Model.TransformItemAsync<string>("Cessna", $"Pick the category from this list: {categories}"));
6767
}
6868

6969
[TestMethod]
7070
public async Task Transform_Answer()
7171
{
72-
Assert.AreEqual("Honolulu", await Text.TransformItemAsync<string>(Model, "What town was he born in?"));
72+
Assert.AreEqual("Honolulu", await Model.TransformItemAsync<string>(Text, "What town was he born in?"));
7373
}
7474

7575
[TestMethod]
7676
public async Task Transform_Text2Strings()
7777
{
78-
var results = await Text.TransformItemAsync<string[]>(Model, "return titles that are bolded");
78+
var results = await Model.TransformItemAsync<string[]>(Text, "return titles that are bolded");
7979

8080
string[] titles =
8181
[
@@ -100,7 +100,7 @@ public async Task Transform_Text2Strings()
100100
[TestMethod]
101101
public async Task Transform_Text2Objects()
102102
{
103-
var results = await Text.TransformItemAsync<Article[]>(Model);
103+
var results = await Model.TransformItemAsync<Article[]>(Text);
104104

105105
Assert.IsTrue(results.Count() > 0);
106106
foreach(var result in results)
@@ -120,7 +120,7 @@ public void Transform_Collection_String2String()
120120
"Which is better? Flossing or brushing or doing nothing? Gert Gooble discusses this important subject." ,
121121
};
122122

123-
var results = sources.TransformItems<string>(Model, "Transform to text like this:\n# {{title}}\nBy {{Author}}\n{{Summary}}").ToList();
123+
var results = Model.TransformItems<string>(sources, "Transform to text like this:\n# {{title}}\nBy {{Author}}\n{{Summary}}").ToList();
124124
var result = results[0];
125125
Assert.IsTrue(result.StartsWith("#"));
126126
Assert.IsTrue(result.Split('\n').First().Contains("Title 1"));
@@ -138,7 +138,7 @@ public void Transform_Collection_String2Object()
138138
"Which is better? Flossing or brushing or doing nothing? Gert Gooble discusses this important subject." ,
139139
};
140140

141-
var results = sources.TransformItems<TargetObject>(Model).ToList();
141+
var results = Model.TransformItems<TargetObject>(sources).ToList();
142142
var result = results[0];
143143
Assert.AreEqual("Joe Blow", result.AuthorFullName);
144144
Assert.AreEqual("Joe", result.Author!.FirstName);
@@ -166,7 +166,7 @@ public void Transform_Collection_Object2Object()
166166
new SourceObject() { Title="Title 3", Description="Which is better? Flossing or brushing or doing nothing?", Writer="Gert Gooble", PubliicationDate = new DateTime(2020, 10, 1) },
167167
};
168168

169-
foreach (var result in sources.TransformItems<TargetObject>(Model))
169+
foreach (var result in Model.TransformItems<TargetObject>(sources))
170170
{
171171
switch (result.Summary)
172172
{
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using Iciclecreek.Async;
2+
using Newtonsoft.Json;
3+
using OpenAI.Chat;
4+
5+
namespace Linq.AI.OpenAI
6+
{
7+
8+
public static class MiscExtensions
9+
{
10+
/// <summary>
11+
/// Generate an item of shape T based on "goal"
12+
/// </summary>
13+
/// <typeparam name="ResultT">type of items</typeparam>
14+
/// <param name="source">collection of items</param>
15+
/// <param name="constraint">The constraint to match to remove an item</param>
16+
/// <param name="instructions">(OPTIONAL) additional instructions</param>
17+
/// <param name="maxParallel">(OPTIONAL) max parallel operations</param>
18+
/// <param name="cancellationToken">(OPTIONAL) cancellation token</param>
19+
/// <returns>collection of items which didn't match the goal</returns>
20+
public static ResultT Generate<ResultT>(this ChatClient model, string goal, string? instructions = null, int? maxParallel = null, CancellationToken cancellationToken = default)
21+
=> model.TransformItem<ResultT>(String.Empty, goal, instructions, cancellationToken);
22+
23+
/// <summary>
24+
/// Generate an item of shape T based on "goal"
25+
/// </summary>
26+
/// <typeparam name="T">type of items</typeparam>
27+
/// <param name="source">collection of items</param>
28+
/// <param name="constraint">The constraint to match to remove an item</param>
29+
/// <param name="instructions">(OPTIONAL) additional instructions</param>
30+
/// <param name="maxParallel">(OPTIONAL) max parallel operations</param>
31+
/// <param name="cancellationToken">(OPTIONAL) cancellation token</param>
32+
/// <returns>collection of items which didn't match the goal</returns>
33+
public static Task<ResultT> GenerateAsync<ResultT>(this ChatClient model, string goal, string? instructions = null, int? maxParallel = null, CancellationToken cancellationToken = default)
34+
=> model.TransformItemAsync<ResultT>(String.Empty, goal, instructions, cancellationToken);
35+
}
36+
}
37+

0 commit comments

Comments
 (0)