From a47216590e359a491f216a6a8e719d39793880f9 Mon Sep 17 00:00:00 2001 From: Jesse Liberty Date: Wed, 29 Jul 2026 11:58:08 -0400 Subject: [PATCH] Fix MaxOutputTokens to prevent runaway generation dominating bill and latency --- AuthorAgent.cs | 11 ++++++++++- BloggerAgent.cs | 11 ++++++++++- ResearcherAgent.cs | 11 ++++++++++- ReviewerAgent.cs | 11 ++++++++++- 4 files changed, 40 insertions(+), 4 deletions(-) diff --git a/AuthorAgent.cs b/AuthorAgent.cs index 073e896..d8b66d6 100644 --- a/AuthorAgent.cs +++ b/AuthorAgent.cs @@ -21,9 +21,13 @@ public class AuthorAgent : IAuthorAgent private readonly ILogger _logger; + // Per-call output-token cap, applied on each RunAsync to bound cost. + private readonly int? _maxOutputTokens; + public AuthorAgent(IChatClient llm, ChatOptions chatOptions, ILogger logger) { _logger = logger; + _maxOutputTokens = chatOptions.MaxOutputTokens; _agent = new ChatClientAgent(llm, new ChatClientAgentOptions { @@ -60,7 +64,12 @@ public async Task InvokeAsync(ResearchState state) try { - AgentResponse response = await _agent.RunAsync(message); + // Cap per-call output tokens so a single turn can't blow the cost budget. + ChatClientAgentRunOptions runOptions = new(new ChatOptions + { + MaxOutputTokens = _maxOutputTokens, + }); + AgentResponse response = await _agent.RunAsync(message, options: runOptions); string content = response.Text; return !string.IsNullOrEmpty(content) ? content : "Draft in progress..."; } diff --git a/BloggerAgent.cs b/BloggerAgent.cs index 07b1753..897888c 100644 --- a/BloggerAgent.cs +++ b/BloggerAgent.cs @@ -30,9 +30,13 @@ public class BloggerAgent : IBloggerAgent private readonly ILogger _logger; + // Per-call output-token cap, applied on each RunAsync to bound cost. + private readonly int? _maxOutputTokens; + public BloggerAgent(IChatClient llm, ChatOptions chatOptions, ILogger logger) { _logger = logger; + _maxOutputTokens = chatOptions.MaxOutputTokens; _agent = new ChatClientAgent(llm, new ChatClientAgentOptions { @@ -110,8 +114,13 @@ public async Task InvokeAsync(ResearchState state) try { + // Cap per-call output tokens so a single turn can't blow the cost budget. + ChatClientAgentRunOptions runOptions = new(new ChatOptions + { + MaxOutputTokens = _maxOutputTokens, + }); AgentResponse response = - await _agent.RunAsync(stateSummary, serializerOptions: _jsonOptions); + await _agent.RunAsync(stateSummary, options: runOptions, serializerOptions: _jsonOptions); BloggerDecision decision = response.Result; if (decision is not null && !string.IsNullOrEmpty(decision.NextStep)) diff --git a/ResearcherAgent.cs b/ResearcherAgent.cs index 34fe6fb..0d88fa4 100644 --- a/ResearcherAgent.cs +++ b/ResearcherAgent.cs @@ -25,9 +25,13 @@ public class ResearcherAgent : IResearcherAgent private readonly ILogger _logger; + // Per-call output-token cap, applied on each RunAsync to bound cost. + private readonly int? _maxOutputTokens; + public ResearcherAgent(IChatClient llm, ChatOptions chatOptions, AIFunction tavilyTool, ILogger logger) { _logger = logger; + _maxOutputTokens = chatOptions.MaxOutputTokens; _agent = new ChatClientAgent(llm, new ChatClientAgentOptions { @@ -59,7 +63,12 @@ public async Task InvokeAsync(string query) { // A single agent run: the model may call tavily_search one or more // times, read the results, and return a concise summary as its text. - AgentResponse response = await _agent.RunAsync(query); + // Cap per-call output tokens so a single turn can't blow the cost budget. + ChatClientAgentRunOptions runOptions = new(new ChatOptions + { + MaxOutputTokens = _maxOutputTokens, + }); + AgentResponse response = await _agent.RunAsync(query, options: runOptions); string summary = response.Text; return !string.IsNullOrEmpty(summary) diff --git a/ReviewerAgent.cs b/ReviewerAgent.cs index 20ff6ce..c55b78c 100644 --- a/ReviewerAgent.cs +++ b/ReviewerAgent.cs @@ -22,9 +22,13 @@ public class ReviewerAgent : IReviewerAgent private readonly ILogger _logger; + // Per-call output-token cap, applied on each RunAsync to bound cost. + private readonly int? _maxOutputTokens; + public ReviewerAgent(IChatClient llm, ChatOptions chatOptions, ILogger logger) { _logger = logger; + _maxOutputTokens = chatOptions.MaxOutputTokens; _agent = new ChatClientAgent(llm, new ChatClientAgentOptions { @@ -62,7 +66,12 @@ public async Task InvokeAsync(ResearchState state) try { - AgentResponse response = await _agent.RunAsync(message); + // Cap per-call output tokens so a single turn can't blow the cost budget. + ChatClientAgentRunOptions runOptions = new(new ChatOptions + { + MaxOutputTokens = _maxOutputTokens, + }); + AgentResponse response = await _agent.RunAsync(message, options: runOptions); string content = response.Text; return !string.IsNullOrEmpty(content) ? content : "APPROVED"; }