Skip to content

Agents API

The Strategos.Agents package provides integration with Microsoft.Extensions.AI for LLM-powered workflow steps.

Base interface for LLM-powered workflow steps. Extends IWorkflowStep<TState> with agent-specific context.

MethodParametersReturnsDescription
ExecuteAsyncTState state, AgentStepContext context, CancellationToken ctTask<StepResult<TState>>Executes the agent step
public class AnalyzeDocumentStep : IAgentStep<DocumentState>
{
private readonly IChatClient _chatClient;
public AnalyzeDocumentStep(IChatClient chatClient)
{
_chatClient = chatClient;
}
public async Task<StepResult<DocumentState>> ExecuteAsync(
DocumentState state,
AgentStepContext context,
CancellationToken ct)
{
var response = await _chatClient.GetResponseAsync(
$"Analyze this document: {state.Content}",
ct);
return state
.With(s => s.Analysis, response)
.AsResult();
}
}

Extended execution context for agent steps. Inherits all properties from StepContext.

PropertyTypeDescription
WorkflowIdGuidWorkflow instance identifier
CorrelationIdstringCorrelation ID for tracing
TimestampDateTimeOffsetWhen the step execution started
PhasestringCurrent workflow phase name
StepNamestringCurrent step name
MetadataIReadOnlyDictionary<string, object>Additional context data
ConversationThreadIConversationThread?Conversation history access
StreamingCallbackIStreamingCallback?Real-time token streaming
BudgetStatusBudgetStatusCurrent resource budget
public async Task<StepResult<ChatState>> ExecuteAsync(
ChatState state,
AgentStepContext context,
CancellationToken ct)
{
// Access conversation history
var history = context.ConversationThread?.GetMessages();
// Check budget before expensive operation
if (context.BudgetStatus.Level == ScarcityLevel.Critical)
{
return state.With(s => s.Response, "Budget exhausted").AsResult();
}
// Stream response tokens
await foreach (var chunk in _chatClient.GetStreamingResponseAsync(...))
{
context.StreamingCallback?.OnToken(chunk.Text);
}
// ...
}

Interface for workflow state that includes conversation history.

PropertyTypeDescription
MessagesIReadOnlyList<ChatMessage>Conversation history
[WorkflowState]
public record ChatState : IConversationalState
{
public string Query { get; init; }
public string Response { get; init; }
[Append]
public List<ChatMessage> Messages { get; init; } = new();
IReadOnlyList<ChatMessage> IConversationalState.Messages => Messages;
}

Interface for accessing and managing conversation history.

MethodParametersReturnsDescription
GetMessages-IReadOnlyList<ChatMessage>Gets full conversation history
GetRecentMessagesint countIReadOnlyList<ChatMessage>Gets N most recent messages
AddMessageChatMessage messagevoidAppends message to history

Callback interface for real-time token streaming.

MethodParametersReturnsDescription
OnTokenstring tokenvoidCalled for each streamed token
OnComplete-voidCalled when streaming completes
OnErrorException errorvoidCalled on streaming error
public class StreamingStep : IAgentStep<ChatState>
{
private readonly IChatClient _chatClient;
public async Task<StepResult<ChatState>> ExecuteAsync(
ChatState state,
AgentStepContext context,
CancellationToken ct)
{
var fullResponse = new StringBuilder();
await foreach (var chunk in _chatClient.GetStreamingResponseAsync(
state.Query, ct))
{
fullResponse.Append(chunk.Text);
context.StreamingCallback?.OnToken(chunk.Text);
}
context.StreamingCallback?.OnComplete();
return state
.With(s => s.Response, fullResponse.ToString())
.AsResult();
}
}

Represents a single message in a conversation.

PropertyTypeDescription
RoleChatRoleMessage role (User, Assistant, System)
ContentstringMessage content
TimestampDateTimeOffsetWhen message was created
MetadataDictionary<string, object>Additional message data

Enumeration of message roles.

ValueDescription
SystemSystem/instruction message
UserUser input message
AssistantLLM response message
ToolTool/function result message

The package integrates with Microsoft.Extensions.AI.IChatClient.

ProviderPackageRegistration
OpenAIMicrosoft.Extensions.AI.OpenAInew OpenAIChatClient(model, apiKey)
Azure OpenAIMicrosoft.Extensions.AI.AzureOpenAInew AzureOpenAIChatClient(endpoint, key)
OllamaOllamaChatClientnew OllamaChatClient(model)
// OpenAI
services.AddSingleton<IChatClient>(
new OpenAIChatClient("gpt-4o", apiKey));
// Azure OpenAI
services.AddSingleton<IChatClient>(
new AzureOpenAIChatClient(
new Uri("https://your-resource.openai.azure.com"),
new AzureKeyCredential(apiKey),
"gpt-4o"));
// Ollama (local)
services.AddSingleton<IChatClient>(
new OllamaChatClient("llama2"));

public class SimpleChatStep : IAgentStep<ChatState>
{
private readonly IChatClient _chatClient;
public async Task<StepResult<ChatState>> ExecuteAsync(
ChatState state,
AgentStepContext context,
CancellationToken ct)
{
var response = await _chatClient.GetResponseAsync(state.Query, ct);
return state.With(s => s.Response, response).AsResult();
}
}
public class ConversationalStep : IAgentStep<ChatState>
{
private readonly IChatClient _chatClient;
public async Task<StepResult<ChatState>> ExecuteAsync(
ChatState state,
AgentStepContext context,
CancellationToken ct)
{
var messages = state.Messages
.Select(m => new ChatMessage(m.Role, m.Content))
.Append(new ChatMessage(ChatRole.User, state.Query))
.ToList();
var response = await _chatClient.GetResponseAsync(messages, ct);
return state
.With(s => s.Response, response)
.With(s => s.Messages, state.Messages
.Append(new ChatMessage { Role = ChatRole.User, Content = state.Query })
.Append(new ChatMessage { Role = ChatRole.Assistant, Content = response })
.ToList())
.AsResult();
}
}
public class StreamingChatStep : IAgentStep<ChatState>
{
private readonly IChatClient _chatClient;
public async Task<StepResult<ChatState>> ExecuteAsync(
ChatState state,
AgentStepContext context,
CancellationToken ct)
{
var response = new StringBuilder();
await foreach (var chunk in _chatClient.GetStreamingResponseAsync(
state.Query, ct))
{
response.Append(chunk.Text);
context.StreamingCallback?.OnToken(chunk.Text);
}
context.StreamingCallback?.OnComplete();
return state.With(s => s.Response, response.ToString()).AsResult();
}
}