Skip to content

Commit 8ec17c5

Browse files
Move system prompt to Instructions to prevent accumulation across turns
When using previous_response_id, the system prompt was being added as a ResponseItem.CreateSystemMessageItem in every turn's input list. Since OpenAI already holds the full conversation server-side, this caused the system prompt to accumulate in context on each turn and each tool-call leg. Fix: Set the system prompt via ResponseCreationOptions.Instructions in CreateResponseOptionsAsync instead. Per the Azure OpenAI API docs, Instructions is stateless across turns when using previous_response_id, so it is applied fresh each turn without accumulating. Both GetChatCompletionCore and GetChatCompletionStream now always pass only [ResponseItem.CreateUserMessageItem(prompt)] as input items. CreateResponseOptionsAsync is no longer static since it accesses _Options.
1 parent 9be79e6 commit 8ec17c5

1 file changed

Lines changed: 16 additions & 19 deletions

File tree

EssentialCSharp.Chat.Shared/Services/AIChatService.cs

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,9 @@ public AIChatService(IOptions<AIOptions> options, AISearchService searchService,
5454
bool enableContextualSearch = false,
5555
CancellationToken cancellationToken = default)
5656
{
57-
var responseOptions = await CreateResponseOptionsAsync(previousResponseId, tools, reasoningEffortLevel, mcpClient: mcpClient, cancellationToken: cancellationToken);
57+
var responseOptions = await CreateResponseOptionsAsync(systemPrompt, previousResponseId, tools, reasoningEffortLevel, mcpClient: mcpClient, cancellationToken: cancellationToken);
5858
var enrichedPrompt = await EnrichPromptWithContext(prompt, enableContextualSearch, cancellationToken);
59-
return await GetChatCompletionCore(enrichedPrompt, responseOptions, systemPrompt, cancellationToken);
59+
return await GetChatCompletionCore(enrichedPrompt, responseOptions, cancellationToken);
6060
}
6161

6262
/// <summary>
@@ -82,17 +82,12 @@ public AIChatService(IOptions<AIOptions> options, AISearchService searchService,
8282
bool enableContextualSearch = false,
8383
[System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken = default)
8484
{
85-
var responseOptions = await CreateResponseOptionsAsync(previousResponseId, tools, reasoningEffortLevel, mcpClient: mcpClient, cancellationToken: cancellationToken);
85+
var responseOptions = await CreateResponseOptionsAsync(systemPrompt, previousResponseId, tools, reasoningEffortLevel, mcpClient: mcpClient, cancellationToken: cancellationToken);
8686
var enrichedPrompt = await EnrichPromptWithContext(prompt, enableContextualSearch, cancellationToken);
8787

88-
// Construct the user input with system context if provided
89-
var systemContext = !string.IsNullOrWhiteSpace(systemPrompt) ? systemPrompt : _Options.SystemPrompt;
90-
9188
// Create the streaming response using the Responses API
9289
#pragma warning disable OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
93-
List<ResponseItem> responseItems = systemContext is not null
94-
? [ResponseItem.CreateSystemMessageItem(systemContext), ResponseItem.CreateUserMessageItem(enrichedPrompt)]
95-
: [ResponseItem.CreateUserMessageItem(enrichedPrompt)];
90+
List<ResponseItem> responseItems = [ResponseItem.CreateUserMessageItem(enrichedPrompt)];
9691
#pragma warning restore OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
9792
var streamingUpdates = _ResponseClient.CreateResponseStreamingAsync(
9893
responseItems,
@@ -254,7 +249,8 @@ private async Task<string> EnrichPromptWithContext(string prompt, bool enableCon
254249
/// Creates response options with optional features
255250
/// </summary>
256251
#pragma warning disable OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
257-
private static async Task<ResponseCreationOptions> CreateResponseOptionsAsync(
252+
private async Task<ResponseCreationOptions> CreateResponseOptionsAsync(
253+
string? systemPrompt = null,
258254
string? previousResponseId = null,
259255
IEnumerable<ResponseTool>? tools = null,
260256
ResponseReasoningEffortLevel? reasoningEffortLevel = null,
@@ -265,6 +261,14 @@ private static async Task<ResponseCreationOptions> CreateResponseOptionsAsync(
265261
var options = new ResponseCreationOptions();
266262
#pragma warning restore OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
267263

264+
// Set the system prompt via Instructions — this is stateless across turns when using previous_response_id,
265+
// preventing accumulation of system messages in the conversation context.
266+
var resolvedSystemPrompt = !string.IsNullOrWhiteSpace(systemPrompt) ? systemPrompt : _Options.SystemPrompt;
267+
if (resolvedSystemPrompt is not null)
268+
{
269+
options.Instructions = resolvedSystemPrompt;
270+
}
271+
268272
// Add conversation context if available
269273
if (!string.IsNullOrEmpty(previousResponseId))
270274
{
@@ -312,20 +316,13 @@ private static async Task<ResponseCreationOptions> CreateResponseOptionsAsync(
312316
#pragma warning disable OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
313317
ResponseCreationOptions responseOptions,
314318
#pragma warning restore OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
315-
string? systemPrompt = null,
316319
CancellationToken cancellationToken = default)
317320
{
318-
// Construct the user input with system context if provided
319-
var systemContext = !string.IsNullOrWhiteSpace(systemPrompt) ? systemPrompt : _Options.SystemPrompt;
320-
321-
// Create the streaming response using the Responses API
321+
// Create the response using the Responses API
322322
#pragma warning disable OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
323-
List<ResponseItem> responseItems = systemContext is not null
324-
? [ResponseItem.CreateSystemMessageItem(systemContext), ResponseItem.CreateUserMessageItem(prompt)]
325-
: [ResponseItem.CreateUserMessageItem(prompt)];
323+
List<ResponseItem> responseItems = [ResponseItem.CreateUserMessageItem(prompt)];
326324
#pragma warning restore OPENAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
327325

328-
// Create the response using the Responses API
329326
var response = await _ResponseClient.CreateResponseAsync(
330327
responseItems,
331328
options: responseOptions,

0 commit comments

Comments
 (0)