Skip to content

Commit 71be983

Browse files
committed
Cleanup/improve docs
1 parent 1aaa299 commit 71be983

3 files changed

Lines changed: 89 additions & 106 deletions

File tree

readme.md

Lines changed: 86 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,6 @@
66
[![Version](https://img.shields.io/nuget/vpre/Devlooped.Extensions.AI.svg?color=royalblue)](https://www.nuget.org/packages/Devlooped.Extensions.AI)
77
[![Downloads](https://img.shields.io/nuget/dt/Devlooped.Extensions.AI.svg?color=green)](https://www.nuget.org/packages/Devlooped.Extensions.AI)
88

9-
Extensions for Microsoft.Extensions.AI.
10-
11-
<!-- include https://github.com/devlooped/.github/raw/main/osmf.md -->
12-
## Open Source Maintenance Fee
13-
14-
To ensure the long-term sustainability of this project, users of this package who generate
15-
revenue must pay an [Open Source Maintenance Fee](https://opensourcemaintenancefee.org).
16-
While the source code is freely available under the terms of the [License](license.txt),
17-
this package and other aspects of the project require [adherence to the Maintenance Fee](osmfeula.txt).
18-
19-
To pay the Maintenance Fee, [become a Sponsor](https://github.com/sponsors/devlooped) at the proper
20-
OSMF tier. A single fee covers all of [Devlooped packages](https://www.nuget.org/profiles/Devlooped).
21-
22-
<!-- https://github.com/devlooped/.github/raw/main/osmf.md -->
23-
249
<!-- #extensions-title -->
2510
Extensions for Microsoft.Extensions.AI
2611
<!-- #extensions-title -->
@@ -60,56 +45,109 @@ var grok = app.Services.GetRequiredKeyedService<IChatClient>("Grok");
6045
Changing the `appsettings.json` file will automatically update the client
6146
configuration without restarting the application.
6247

63-
## OpenAI
64-
65-
The support for OpenAI chat clients provided in [Microsoft.Extensions.AI.OpenAI](https://www.nuget.org/packages/Microsoft.Extensions.AI.OpenAI) fall short in some scenarios:
66-
67-
* Specifying per-chat model identifier: the OpenAI client options only allow setting
68-
a single model identifier for all requests, at the time the `OpenAIClient.GetChatClient` is
69-
invoked.
70-
* Setting reasoning effort: the Microsoft.Extensions.AI API does not expose a way to set reasoning
71-
effort for reasoning-capable models, which is very useful for some models like `gpt-5.2`.
72-
73-
So solve both issues, this package provides an `OpenAIChatClient` that wraps the underlying
74-
`OpenAIClient` and allows setting the model identifier and reasoning effort per request, just
75-
like the above Grok examples showed:
48+
There's also a simpler `Chat` class for streamlined creation of chat messages, which can
49+
be used instead of creating an array of `ChatMessage` using `ChatRole.[System|Assistant|User]`:
7650

7751
```csharp
7852
var messages = new Chat()
7953
{
8054
{ "system", "You are a highly intelligent AI assistant." },
8155
{ "user", "What is 101*3?" },
8256
};
57+
```
58+
59+
## Tool Results
60+
61+
Given the following tool:
62+
63+
```csharp
64+
MyResult RunTool(string name, string description, string content) { ... }
65+
```
8366

84-
IChatClient chat = new OpenAIChatClient(Environment.GetEnvironmentVariable("OPENAI_API_KEY")!, "gpt-5");
67+
You can use the `ToolFactory` and `FindCall<MyResult>` extension method to
68+
locate the function invocation, its outcome and the typed result for inspection:
8569

70+
```csharp
71+
AIFunction tool = ToolFactory.Create(RunTool);
72+
var options = new ChatOptions
73+
{
74+
ToolMode = ChatToolMode.RequireSpecific(tool.Name), // 👈 forces the tool to be used
75+
Tools = [tool]
76+
};
77+
78+
var response = await client.GetResponseAsync(chat, options);
79+
// 👇 finds the expected result of the tool call
80+
var result = response.FindCalls<MyResult>(tool).FirstOrDefault();
81+
82+
if (result != null)
83+
{
84+
// Successful tool call
85+
Console.WriteLine($"Args: '{result.Call.Arguments.Count}'");
86+
MyResult typed = result.Result;
87+
}
88+
else
89+
{
90+
Console.WriteLine("Tool call not found in response.");
91+
}
92+
```
93+
94+
If the typed result is not found, you can also inspect the raw outcomes by finding
95+
untyped calls to the tool and checking their `Outcome.Exception` property:
96+
97+
```csharp
98+
var result = response.FindCalls(tool).FirstOrDefault();
99+
if (result.Outcome.Exception is not null)
100+
{
101+
Console.WriteLine($"Tool call failed: {result.Outcome.Exception.Message}");
102+
}
103+
else
104+
{
105+
Console.WriteLine($"Tool call succeeded: {result.Outcome.Result}");
106+
}
107+
```
108+
109+
> [!IMPORTANT]
110+
> The `ToolFactory` will also automatically sanitize the tool name
111+
> when using local functions to avoid invalid characters and honor
112+
> its original name.
113+
114+
## OpenAI
115+
116+
OpenAI-specific extensions enable more seamless usage with the MS.E.AI API:
117+
118+
* Setting reasoning effort: the Microsoft.Extensions.AI API does not expose a way to set reasoning
119+
effort for reasoning-capable models, which is very useful for some models like
120+
[`gpt-5.2`](https://platform.openai.com/docs/guides/latest-model#lower-reasoning-effort)
121+
* Setting output verbosity: similarly, [output verbosity](https://platform.openai.com/docs/guides/latest-model#verbosity) is not exposed in the base API.
122+
123+
These can be used as extension properties on `ChatOptions` whenever `Devlooped.Extensions.AI.OpenAI` is imported:
124+
125+
```csharp
86126
var options = new ChatOptions
87127
{
88-
ModelId = "gpt-5-mini", // 👈 can override the model on the client
89128
ReasoningEffort = ReasoningEffort.High, // 👈 or Medium/Low/Minimal/None, extension property
129+
Verbosity = Verbosity.Low // 👈 or Medium/High, extension property
90130
};
91131

92132
var response = await chat.GetResponseAsync(messages, options);
93133
```
94134

95-
> [!TIP]
96-
> We provide support for the newest `Minimal` reasoning effort in the just-released
97-
> GPT-5 model family as well as `None` which is the new default in GPT-5.2.
135+
Or you can opt to use the `ChatOptions`-derived `OpenAIChatOptions` class directly:
98136

99137
### Web Search
100138

101-
Similar to the Grok client, we provide the `WebSearchTool` to enable search customization
102-
in OpenAI too:
139+
The `WebSearchTool` can be used to customize the web search behavior in a typed manner,
140+
unlike the generic `HostedWebSearchTool`:
103141

104142
```csharp
105143
var options = new ChatOptions
106144
{
107145
// 👇 search in Argentina, Bariloche region
108146
Tools = [new WebSearchTool("AR")
109147
{
110-
Region = "Bariloche", // 👈 Bariloche region
111-
TimeZone = "America/Argentina/Buenos_Aires", // 👈 IANA timezone
112-
ContextSize = WebSearchToolContextSize.High // 👈 high search context size
148+
AllowedDomains = ["catedralaltapatagonia.com"], // 👈 restrict domain
149+
Region = "Bariloche", // 👈 Bariloche region
150+
TimeZone = "America/Argentina/Buenos_Aires", // 👈 IANA timezone
113151
}]
114152
};
115153
```
@@ -121,7 +159,6 @@ var options = new ChatOptions
121159
If advanced search settings are not needed, you can use the built-in M.E.AI `HostedWebSearchTool`
122160
instead, which is a more generic tool and provides the basics out of the box.
123161

124-
125162
## Observing Request/Response
126163

127164
The underlying HTTP pipeline provided by the Azure SDK allows setting up
@@ -163,61 +200,6 @@ var openai = new OpenAIClient(
163200
OpenAIClientOptions.Observable(requests.Add, responses.Add));
164201
```
165202

166-
## Tool Results
167-
168-
Given the following tool:
169-
170-
```csharp
171-
MyResult RunTool(string name, string description, string content) { ... }
172-
```
173-
174-
You can use the `ToolFactory` and `FindCall<MyResult>` extension method to
175-
locate the function invocation, its outcome and the typed result for inspection:
176-
177-
```csharp
178-
AIFunction tool = ToolFactory.Create(RunTool);
179-
var options = new ChatOptions
180-
{
181-
ToolMode = ChatToolMode.RequireSpecific(tool.Name), // 👈 forces the tool to be used
182-
Tools = [tool]
183-
};
184-
185-
var response = await client.GetResponseAsync(chat, options);
186-
// 👇 finds the expected result of the tool call
187-
var result = response.FindCalls<MyResult>(tool).FirstOrDefault();
188-
189-
if (result != null)
190-
{
191-
// Successful tool call
192-
Console.WriteLine($"Args: '{result.Call.Arguments.Count}'");
193-
MyResult typed = result.Result;
194-
}
195-
else
196-
{
197-
Console.WriteLine("Tool call not found in response.");
198-
}
199-
```
200-
201-
If the typed result is not found, you can also inspect the raw outcomes by finding
202-
untyped calls to the tool and checking their `Outcome.Exception` property:
203-
204-
```csharp
205-
var result = response.FindCalls(tool).FirstOrDefault();
206-
if (result.Outcome.Exception is not null)
207-
{
208-
Console.WriteLine($"Tool call failed: {result.Outcome.Exception.Message}");
209-
}
210-
else
211-
{
212-
Console.WriteLine($"Tool call succeeded: {result.Outcome.Result}");
213-
}
214-
```
215-
216-
> [!IMPORTANT]
217-
> The `ToolFactory` will also automatically sanitize the tool name
218-
> when using local functions to avoid invalid characters and honor
219-
> its original name.
220-
221203
## Console Logging
222204

223205
Additional `UseJsonConsoleLogging` extension for rich JSON-formatted console logging of AI requests
@@ -261,14 +243,18 @@ IChatClient chat = new OpenAIChatClient(Environment.GetEnvironmentVariable("OPEN
261243
```
262244
<!-- #extensions -->
263245

264-
# xAI (Grok)
246+
<!-- include https://github.com/devlooped/.github/raw/main/osmf.md -->
247+
## Open Source Maintenance Fee
248+
249+
To ensure the long-term sustainability of this project, users of this package who generate
250+
revenue must pay an [Open Source Maintenance Fee](https://opensourcemaintenancefee.org).
251+
While the source code is freely available under the terms of the [License](license.txt),
252+
this package and other aspects of the project require [adherence to the Maintenance Fee](osmfeula.txt).
265253

266-
[![Version](https://img.shields.io/nuget/vpre/xAI.svg?color=royalblue)](https://www.nuget.org/packages/xAI)
267-
[![Downloads](https://img.shields.io/nuget/dt/xAI.svg?color=green)](https://www.nuget.org/packages/xAI)
254+
To pay the Maintenance Fee, [become a Sponsor](https://github.com/sponsors/devlooped) at the proper
255+
OSMF tier. A single fee covers all of [Devlooped packages](https://www.nuget.org/profiles/Devlooped).
268256

269-
For Microsoft.Extensions.AI `IChatClient` integration with Grok (xAI), see the
270-
[xAI](https://nuget.org/packages/xAI) package, which provides full support for all
271-
[agentic tools](https://docs.x.ai/docs/guides/tools/overview).
257+
<!-- https://github.com/devlooped/.github/raw/main/osmf.md -->
272258

273259
<!-- include https://github.com/devlooped/sponsors/raw/main/footer.md -->
274260
# Sponsors

src/Extensions/OpenAI/OpenAIChatOptions.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using System.ComponentModel;
2-
using Microsoft.Extensions.AI;
1+
using Microsoft.Extensions.AI;
32

43
namespace Devlooped.Extensions.AI.OpenAI;
54

src/Extensions/OpenAI/OpenAIExtensions.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
1-
using System.ClientModel.Primitives;
2-
using System.ComponentModel;
3-
using System.Text.Json;
1+
using System.ComponentModel;
42
using Microsoft.Extensions.AI;
5-
using OpenAI.Responses;
63

74
namespace Devlooped.Extensions.AI.OpenAI;
85

@@ -26,6 +23,7 @@ public static class OpenAIExtensions
2623
/// has been set to a non-OpenAI factory.</exception>
2724
extension(ChatOptions options)
2825
{
26+
/// <summary>Controls how many reasoning tokens the model generates before producing a response.</summary>
2927
public ReasoningEffort? ReasoningEffort
3028
{
3129
get => options.AdditionalProperties?.TryGetValue("reasoning_effort", out var value) == true && value is ReasoningEffort effort ? effort : null;

0 commit comments

Comments
 (0)