Collections
Six methods apply an intent to a whole collection in one call: map, filter, sort, group, reduce and map-values.
Each is an extension method in the IntentSharp.Extensions namespace that takes the client, the intent and,
optionally, AIRequestOptions and a CancellationToken. Each returns an AIResult like any other call: check
Success before you read Value, and read Issues for what went wrong.
Every method except reduce adds a check of its own, so an answer that does not fit the collection fails, and the
model is asked again with the problem, up to MaxAttempts.
The six methods
| Method | You get back | Its check |
|---|---|---|
AIMapAsync<TIn, TOut> |
List<TOut>, one result per element, in the input's order |
exactly as many results as elements |
AIFilterAsync<T> |
List<T>: your elements that match, in their original order |
every position named is in the collection |
AISortAsync<T> |
List<T>: all your elements, in the new order |
every element appears exactly once |
AIGroupAsync<T> |
Dictionary<string, List<T>>: your elements under labels the model chooses |
every element is in exactly one group, and every label is present and unique, ignoring case |
AIReduceAsync<TIn, TOut> |
one TOut for the whole collection: a summary, a total, a verdict |
none; add your own (below) |
AIMapValuesAsync<TKey, TIn, TOut> |
Dictionary<TKey, TOut> with your original keys |
every key once, no key missing, no key added, and no null value unless TOut is a nullable value type such as int? |
Map: one result per element
AIResult<List<string>> summaries = await tickets.AIMapAsync<SupportTicket, string>(client,
"Summarize the ticket in at most ten words.");
The intent is applied to each element on its own, and the results come back as a list in the same order. A reply with more or fewer results than elements fails the check.
Filter, sort and group hand back your own objects
Filter, sort and group never ask the model to write your objects back. The model answers with positions in the collection, and the method returns your original instances: the same references, with any state that does not serialize still on them.
AIResult<List<SupportTicket>> defects = await tickets.AIFilterAsync(client,
"Tickets that describe a defect rather than a feature request.");
if (defects.Success)
foreach (var ticket in defects.Value!)
Console.WriteLine($"#{ticket.Id} {ticket.Title}");
Filter returns the matching elements in their original order, each once, and an empty list when nothing matches.
AIResult<List<SupportTicket>> ranked = await tickets.AISortAsync(client,
"Most severe first: data loss, then outages, then wrong results, then cosmetic problems.");
AIResult<Dictionary<string, List<SupportTicket>>> byArea = await tickets.AIGroupAsync(client,
"Group by the product area the ticket is about.");
if (byArea.Success)
foreach (var (area, inArea) in byArea.Value!)
Console.WriteLine($"{area}: {inArea.Count}");
The group labels are the model's, so write the intent to steer them ("by product area", "by the team that should answer"). The dictionary's keys compare ignoring case.
Reduce: one answer for the whole collection
AIResult<string> digest = await tickets.AIReduceAsync<SupportTicket, string>(client,
"Write a three-sentence digest of these tickets for the support lead.");
Reduce adds no check, because its result can be anything. When the result has a shape you can check, add a check of your own.
Map-values keeps your keys
// Key: SKU. Value: the product name in English.
AIResult<Dictionary<string, string>> french = await productNames.AIMapValuesAsync<string, string, string>(client,
"Translate the product name into French.");
Each value is changed on its own, and the result has the same keys as the input. Keys travel to the model as text,
by their ToString(), so two keys with the same text could not be told apart: the method throws
ArgumentException naming them, before any model call.
Add a check of your own
Pass AIRequestOptions with Require<T> or Validate<T>, as for any call. A failed check is sent back to the
model on the next attempt:
var options = new AIRequestOptions { MaxAttempts = 3 }
.Require<List<string>>(list => list.All(s => s.Length <= 80), "Every summary must be at most 80 characters long.");
var summaries = await tickets.AIMapAsync<SupportTicket, string>(client,
"Summarize the ticket in at most ten words.", options);
The methods add their checks to a copy of your options, so one options object can be reused across calls.
Your check receives the model's answer. For map that is a List<TOut>, and for reduce a TOut, so Require<T>
works as shown. Filter, sort, group and map-values exchange positions or keys with the model, so a check you add
to them does not receive your elements; check what they return after the call instead.
Without passing the client
Inside client.AsAmbient(), the same six methods work without the client argument:
using var scope = client.AsAmbient();
var defects = await tickets.AIFilterAsync("Tickets that describe a defect rather than a feature request.");
The scope belongs to the current async flow: it flows through await and into tasks started inside it, is not
seen by other requests or threads, and ends when it is disposed. Open it in the method that makes the calls, or in
one of its callers; a scope opened inside an awaited helper does not reach the code that awaited it. With no scope
open, these methods throw InvalidOperationException. There is no process-wide default client. Catalog intents
have the same kind of scope (Running intents from files).
Limits
- The whole collection goes in one request, so its size counts against
IntentClientOptions.MaxInputChars(200,000 characters by default). A larger input throwsArgumentExceptionbefore any model call; it is never cut. Split a large collection into batches yourself. - The checks are about the collection's structure. Whether an element belongs in a group, or a summary is accurate, is the model's judgement; add checks of your own where a rule can say it.
- The same class has the methods for a single value,
AIAsync,AIAsync<T>,AIConvertAsync<T>andAIClassifyAsync<TEnum>: see AIExtensions in the API reference.