-
Notifications
You must be signed in to change notification settings - Fork 3
fix: consider only last input item when prompt recording for responses API #146
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
This stack of pull requests is managed by Graphite. Learn more about stacking. |
ce610be to
00bdd0d
Compare
intercept/responses/base.go
Outdated
| if i.promptWasRecorded { | ||
| return | ||
| } | ||
| i.promptWasRecorded = true |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1, after recorder.RecordPromptUsage ?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
intercept/responses/base.go
Outdated
| if i.promptWasRecorded { | ||
| return | ||
| } | ||
| i.promptWasRecorded = true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1, after recorder.RecordPromptUsage ?
intercept/responses/base.go
Outdated
| // 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 |
There was a problem hiding this comment.
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]()) { |
There was a problem hiding this comment.
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]())) |
There was a problem hiding this comment.
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
intercept/responses/base.go
Outdated
| // Check string variant | ||
| if i.req.Input.OfString.Valid() { | ||
| return i.req.Input.OfString.Value, nil | ||
| return &i.req.Input.OfString.Value, nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why ptr? large prompts?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
intercept/responses/base.go
Outdated
| id uuid.UUID | ||
| req *ResponsesNewParamsWrapper | ||
| reqPayload []byte | ||
| promptWasRecorded bool |
There was a problem hiding this comment.
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
…njectedTool test)
a156a20 to
77f0145
Compare

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