-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
fix: Inability to Parse API Response Data Due to Missing Space After … #932
base: master
Are you sure you want to change the base?
Conversation
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## master #932 +/- ##
==========================================
+ Coverage 98.46% 98.89% +0.43%
==========================================
Files 24 27 +3
Lines 1364 1812 +448
==========================================
+ Hits 1343 1792 +449
+ Misses 15 14 -1
Partials 6 6 ☔ View full report in Codecov by Sentry. |
|
@sunhailin-Leo Thank you for updates, the approach looks good, but the I propose we remove Here's an idea to simplify, but I'm not sure it's correct func trimAndCompare(s []byte, cutset string) ([]byte, bool) {
originalLen = len(s)
updated := bytes.Trim(s, cutset)
lengthChanged := originalLen != len(updated)
return updated, lengthChanged
}
func (stream *streamReader[T]) processLines() ([]byte, error) {
var (
emptyMessagesCount uint
hasErrorPrefix bool
noPrefixLine []byte
)
for {
line, readErr := stream.reader.ReadBytes('\n')
if readErr != nil || hasErrorPrefix {
respErr := stream.unmarshalError()
if respErr != nil {
return nil, fmt.Errorf("error, %w", respErr.Error)
}
return nil, readErr
}
line = bytes.TrimSpace(rawLine)
line, gotHeader = trimAndCompare(line, headerData)
line = bytes.TrimSpace(line)
if gotHeader {
hasErrorPrefix = bytes.HasPrefix(line, errorPrefix)
}
if !gotHeader || hasErrorPrefix {
writeErr := stream.errAccumulator.Write(line)
if writeErr != nil {
return nil, writeErr
}
emptyMessagesCount++
if emptyMessagesCount > stream.emptyMessagesLimit {
return nil, ErrTooManyEmptyStreamMessages
}
continue
}
if string(noPrefixLine) == "[DONE]" {
stream.isFinished = true
return nil, io.EOF
}
return noPrefixLine, nil
}
} |
Side idea: maybe we can re-factor this as a state machine here, with states clearly enumerated |
If want to re-factor, I think we can refer to https://github.com/openai/openai-go/blob/main/packages/ssestream/ssestream.go#L80 |
@sunhailin-Leo that's a way to go as well! |
var (
//headerData = []byte("data:")
//errorPrefix = []byte(`{"error":`)
doneFlag = []byte("[DONE]")
dataFlag = "data"
errorFlag = []byte(`"error"`)
errorPrefixFlag = []byte(`{"error`)
)
func (stream *streamReader[T]) processLines() ([]byte, error) {
var (
emptyMessagesCount uint
hasErrorPrefix bool
)
for {
rawLine, readErr := stream.reader.ReadBytes('\n')
if readErr != nil || (hasErrorPrefix && readErr == io.EOF) {
respErr := stream.unmarshalError()
if respErr != nil {
return nil, fmt.Errorf("error, %w", respErr.Error)
}
return nil, readErr
}
// Split a string like "event: bar" into name="event" and value=" bar".
name, value, _ := bytes.Cut(rawLine, []byte(":"))
value = bytes.TrimSpace(value)
// Consume an optional space after the colon if it exists.
if len(value) > 0 && value[0] == ' ' {
value = value[1:]
}
switch string(name) {
case dataFlag:
if bytes.Equal(value, doneFlag) {
stream.isFinished = true
return nil, io.EOF
}
if bytes.HasPrefix(value, errorPrefixFlag) {
if writeErr := stream.writeErrAccumulator(value); writeErr != nil {
return nil, writeErr
}
respErr := stream.unmarshalError()
if respErr != nil {
return nil, fmt.Errorf("error, %w", respErr.Error)
}
continue
}
return value, nil
default:
if writeErr := stream.writeErrAccumulator(rawLine); writeErr != nil {
return nil, writeErr
}
if bytes.Equal(name, errorFlag) {
hasErrorPrefix = true
continue
}
emptyMessagesCount++
if emptyMessagesCount > stream.emptyMessagesLimit {
return nil, ErrTooManyEmptyStreamMessages
}
continue
}
}
} |
|
@sunhailin-Leo Could you please rebase on the latest master for CI to pass? |
There's also this PR: #941 @sunhailin-Leo do you think it makes sense or not? |
* ref: add image url support to messages * fix linter error * fix linter error
* fix: remove validateO1Specific * update golangci-lint-action version * fix actions * fix actions * fix actions * fix actions * remove some o1 test
…nov#934) * feat: add Anthropic API support with custom version header * refactor: use switch statement for API type header handling * refactor: add OpenAI & AzureAD types to be exhaustive * Update client.go need explicit fallthrough in empty case statements * constant for APIVersion; addtl tests
* fix lint * remove linters
I think that PR is pretty interesting, and it looks like the way to go with minimal changes. |
Fixed