Skip to content

Commit be95a3c

Browse files
authored
graph: check nil choices from final response (#747)
1 parent 42a1bba commit be95a3c

File tree

2 files changed

+56
-2
lines changed

2 files changed

+56
-2
lines changed

graph/checkpoint_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1112,6 +1112,22 @@ func (e *emptyModel) GenerateContent(ctx context.Context, req *model.Request) (<
11121112
}
11131113
func (e *emptyModel) Info() model.Info { return model.Info{Name: "empty"} }
11141114

1115+
type emptyChoicesModel struct{}
1116+
1117+
func (e *emptyChoicesModel) GenerateContent(
1118+
ctx context.Context,
1119+
req *model.Request,
1120+
) (<-chan *model.Response, error) {
1121+
ch := make(chan *model.Response, 1)
1122+
ch <- &model.Response{}
1123+
close(ch)
1124+
return ch, nil
1125+
}
1126+
1127+
func (e *emptyChoicesModel) Info() model.Info {
1128+
return model.Info{Name: "empty_choices"}
1129+
}
1130+
11151131
func TestExecuteModelWithEvents_NoResponseError(t *testing.T) {
11161132
tracer := oteltrace.NewNoopTracerProvider().Tracer("t")
11171133
_, span := tracer.Start(context.Background(), "s")
@@ -1130,6 +1146,29 @@ func TestExecuteModelWithEvents_NoResponseError(t *testing.T) {
11301146
require.Error(t, err)
11311147
}
11321148

1149+
func TestExecuteModelWithEvents_NoChoicesError(t *testing.T) {
1150+
tracer := oteltrace.NewNoopTracerProvider().Tracer("t")
1151+
_, span := tracer.Start(context.Background(), "s")
1152+
_, err := executeModelWithEvents(context.Background(),
1153+
modelExecutionConfig{
1154+
ModelCallbacks: nil,
1155+
LLMModel: &emptyChoicesModel{},
1156+
Request: &model.Request{
1157+
Messages: []model.Message{
1158+
model.NewUserMessage("hi"),
1159+
},
1160+
},
1161+
EventChan: make(chan *event.Event, 1),
1162+
InvocationID: "inv",
1163+
SessionID: "sid",
1164+
AppName: "app",
1165+
UserID: "user",
1166+
Span: span,
1167+
NodeID: "n",
1168+
})
1169+
require.Error(t, err)
1170+
}
1171+
11331172
func TestRunModel_BeforeModelCustomResponse(t *testing.T) {
11341173
// Callbacks return custom response, dummy model should not be called
11351174
cbs := model.NewCallbacks().RegisterBeforeModel(func(ctx context.Context, req *model.Request) (*model.Response, error) {

graph/state_graph.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1483,6 +1483,11 @@ type modelExecutionConfig struct {
14831483
Span oteltrace.Span
14841484
}
14851485

1486+
const (
1487+
errMsgNoModelResponse = "no response received from model"
1488+
errMsgNoModelChoices = "model returned no choices"
1489+
)
1490+
14861491
// executeModelWithEvents executes the model with event processing.
14871492
func executeModelWithEvents(ctx context.Context, config modelExecutionConfig) (any, error) {
14881493
ctx, responseChan, err := runModel(ctx, config.ModelCallbacks, config.LLMModel, config.Request)
@@ -1544,8 +1549,18 @@ func executeModelWithEvents(ctx context.Context, config modelExecutionConfig) (a
15441549
finalResponse = response
15451550
}
15461551
if finalResponse == nil {
1547-
config.Span.SetAttributes(attribute.String("trpc.go.agent.error", "no response received from model"))
1548-
return nil, errors.New("no response received from model")
1552+
config.Span.SetAttributes(attribute.String(
1553+
"trpc.go.agent.error",
1554+
errMsgNoModelResponse,
1555+
))
1556+
return nil, errors.New(errMsgNoModelResponse)
1557+
}
1558+
if len(finalResponse.Choices) == 0 {
1559+
config.Span.SetAttributes(attribute.String(
1560+
"trpc.go.agent.error",
1561+
errMsgNoModelChoices,
1562+
))
1563+
return nil, errors.New(errMsgNoModelChoices)
15491564
}
15501565
if len(finalResponse.Choices[0].Message.ToolCalls) < len(toolCalls) {
15511566
finalResponse.Choices[0].Message.ToolCalls = toolCalls

0 commit comments

Comments
 (0)