Skip to content

Conversation

@pawbana
Copy link
Contributor

@pawbana pawbana commented Jan 23, 2026

Changes lastUserPrompt behavior in responses API interceptors to only check last input item for user input.
Fixes: #145

Copy link
Contributor Author

pawbana commented Jan 23, 2026

This stack of pull requests is managed by Graphite. Learn more about stacking.

@pawbana pawbana marked this pull request as ready for review January 23, 2026 13:08
@pawbana pawbana force-pushed the pb/responses-last-user-prompt-fix branch from ce610be to 00bdd0d Compare January 23, 2026 14:48
if i.promptWasRecorded {
return
}
i.promptWasRecorded = true
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be set prospectively?
lastUserPrompt could fail, the prompt could be nil, etc.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, after recorder.RecordPromptUsage ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understood correctly only first inner loop iteration should log user prompt and this is the main change in this PR. If it fails then it fails and that is it.
Subsequent inner loop iterations should fail to extract user prompt since last input item should be function call result / not contain user prompt anyway.
I've mostly added this check to not spam logs about failing to record prompt on each inner loop iteration.

Copy link
Member

@mtojek mtojek Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, if there is no option to recover from failed recording, SGTM.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Semantically I think it's odd to name something "was recorded" before we even try make a record attempt.
I'm fine to leave it but you can understand why this might cause confusion.

I've mostly added this check to not spam logs about failing to record prompt on each inner loop iteration.

I'm not sure why that would ever occur since the last user prompt should only be recorded when the last prompt was a human prompt, not within an agentic loop.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps rename it to firstIter bool or similar

Copy link
Collaborator

@dannykopping dannykopping Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My point is that this is probably unnecessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored so prompt is extracted before inner loop and recorded only once after the loop.

if i.promptWasRecorded {
return
}
i.promptWasRecorded = true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, after recorder.RecordPromptUsage ?

Comment on lines 177 to 211
// content can be a string or array of objects:
// https://platform.openai.com/docs/api-reference/responses/create#responses_create-input-input_item_list-input_message-content
content := lastItem.Get(string(constant.ValueOf[constant.Content]()))

// non array case, should be string
if !content.IsArray() {
if content.Type == gjson.String {
return &content.Str, nil
}
return nil, fmt.Errorf("unexpected input type: %v", content.Type.String())
}

var sb strings.Builder
promptExists := false
for _, c := range content.Array() {
// ignore inputs of not `input_text` type
if c.Get(string(constant.ValueOf[constant.Type]())).Str != string(constant.ValueOf[constant.InputText]()) {
continue
}

text := c.Get(string(constant.ValueOf[constant.Text]()))
if text.Type == gjson.String {
promptExists = true
sb.WriteString(text.Str + "\n")
} else {
i.logger.Warn(ctx, fmt.Sprintf("unexpected input array type: %v", text.Type))
}
}

if !promptExists {
return nil, nil
}

prompt := strings.TrimSuffix(sb.String(), "\n")
return &prompt, nil
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: perhaps it is worth moving this logic to a separate function and cover with a few basic unit tests? to prevent dev errors in the future


// Request was likely not human-initiated.
return "", nil
if lastItem.Get("role").Str != string(constant.ValueOf[constant.User]()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I would hide the expression like this behind a descriptive function


// content can be a string or array of objects:
// https://platform.openai.com/docs/api-reference/responses/create#responses_create-input-input_item_list-input_message-content
content := lastItem.Get(string(constant.ValueOf[constant.Content]()))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe a helper function? I see .Get(string(constant.ValueOf[constant.Content]() is repeated a few times

// Check string variant
if i.req.Input.OfString.Valid() {
return i.req.Input.OfString.Value, nil
return &i.req.Input.OfString.Value, nil
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why ptr? large prompts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nil -> no prompt found, "" -> prompt that is empty string

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Thanks for the explanation.

The current form is correct, but I would assume the same rules as "comma, ok" pattern, so prompt, ok, err. Most likely, personal preference thing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed to this pattern with moving prompt recording outside of inner loop. Pointers where also causing issues in injected tools tests / when prepareRequestForAgenticLoop was called.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

id uuid.UUID
req *ResponsesNewParamsWrapper
reqPayload []byte
promptWasRecorded bool
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: maybe move it to the bottom with a new-line, looks more like a flow-control property

@pawbana pawbana requested a review from ssncferreira January 26, 2026 12:55
@pawbana pawbana force-pushed the pb/responses-last-user-prompt-fix branch from a156a20 to 77f0145 Compare January 26, 2026 14:39
@pawbana pawbana merged commit f603a56 into main Jan 27, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: prompt recording in responses interceptors wrongly records prompts

3 participants