Skip to content

Commit d255f49

Browse files
authored
feat(ai-bridge): handle llm-sfn return nothing or multiple results returned (#833)
1 parent cf78fea commit d255f49

5 files changed

Lines changed: 38 additions & 6 deletions

File tree

core/serverless/context_llm.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ import (
99

1010
// ReadLLMArguments reads LLM function arguments
1111
func (c *Context) ReadLLMArguments(args any) error {
12-
fnCall := &ai.FunctionCall{}
13-
err := fnCall.FromBytes(c.data)
12+
fnCall, err := c.LLMFunctionCall()
1413
if err != nil {
1514
return err
1615
}
@@ -27,13 +26,17 @@ func (c *Context) WriteLLMResult(result string) error {
2726
if c.fnCall == nil {
2827
return errors.New("no function call, can't write result")
2928
}
29+
if c.fnCall.IsOK && c.fnCall.Result != "" {
30+
return errors.New("LLM function can only be called once")
31+
}
3032
// function call
3133
c.fnCall.IsOK = true
3234
c.fnCall.Result = result
3335
buf, err := c.fnCall.Bytes()
3436
if err != nil {
3537
return err
3638
}
39+
c.data = buf
3740
return c.Write(ai.ReducerTag, buf)
3841
}
3942

pkg/bridge/ai/service.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,7 @@ func (s *Service) GetChatCompletions(ctx context.Context, req openai.ChatComplet
427427
}
428428
promptUsage = resp.Usage.PromptTokens
429429
completionUsage = resp.Usage.CompletionTokens
430-
totalUsage = resp.Usage.CompletionTokens
430+
totalUsage = resp.Usage.TotalTokens
431431

432432
ylog.Debug(" #1 first call", "response", fmt.Sprintf("%+v", resp))
433433
// it is a function call

serverless/mock/mock_context.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77

88
"github.com/yomorun/yomo/ai"
99
"github.com/yomorun/yomo/serverless"
10-
"github.com/yomorun/yomo/serverless/guest"
1110
)
1211

1312
var _ serverless.Context = (*MockContext)(nil)
@@ -55,7 +54,7 @@ func (c *MockContext) Metadata(_ string) (string, bool) {
5554

5655
// HTTP returns the HTTP interface.H
5756
func (m *MockContext) HTTP() serverless.HTTP {
58-
return &guest.GuestHTTP{}
57+
panic("not implemented, to use `net/http` package")
5958
}
6059

6160
// Write writes the data with the given tag.
@@ -106,8 +105,13 @@ func (c *MockContext) WriteLLMResult(result string) error {
106105
defer c.mu.Unlock()
107106

108107
if c.fnCall == nil {
109-
return errors.New("no function call, can't write result")
108+
fnCall, err := c.LLMFunctionCall()
109+
if err != nil {
110+
return err
111+
}
112+
c.fnCall = fnCall
110113
}
114+
111115
// function call
112116
c.fnCall.IsOK = true
113117
c.fnCall.Result = result

serverless/mock/mock_context_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,14 @@ func TestWriteLLMResult(t *testing.T) {
8989
assert.Equal(t, ai.ReducerTag, res[0].Tag)
9090
assert.Equal(t, jsonStrWithResult("test result"), string(res[0].Data))
9191
}
92+
93+
func TestWriteLLMResultWithoutRead(t *testing.T) {
94+
ctx := NewMockContext([]byte(jsonStr), 0x10)
95+
// write
96+
err := ctx.WriteLLMResult("test result")
97+
assert.NoError(t, err)
98+
99+
res := ctx.RecordsWritten()
100+
assert.Equal(t, ai.ReducerTag, res[0].Tag)
101+
assert.Equal(t, jsonStrWithResult("test result"), string(res[0].Data))
102+
}

sfn.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package yomo
33
import (
44
"context"
55
"errors"
6+
"log/slog"
67

78
"github.com/robfig/cron/v3"
89

@@ -12,6 +13,7 @@ import (
1213
"github.com/yomorun/yomo/core/serverless"
1314
"github.com/yomorun/yomo/pkg/id"
1415
"github.com/yomorun/yomo/pkg/trace"
16+
yserverless "github.com/yomorun/yomo/serverless"
1517
"go.opentelemetry.io/otel/attribute"
1618
)
1719

@@ -251,6 +253,7 @@ func (s *streamFunction) onDataFrame(dataFrame *frame.DataFrame) {
251253

252254
serverlessCtx := serverless.NewContext(s.client, dataFrame.Tag, md, dataFrame.Payload)
253255
s.fn(serverlessCtx)
256+
checkLLMFunctionCall(s.client.Logger, serverlessCtx)
254257
}(dataFrame)
255258
} else if s.pfn != nil {
256259
data := dataFrame.Payload
@@ -270,3 +273,14 @@ func (s *streamFunction) SetErrorHandler(fn func(err error)) {
270273
func (s *streamFunction) Init(fn func() error) error {
271274
return fn()
272275
}
276+
277+
func checkLLMFunctionCall(logger *slog.Logger, serverlessCtx yserverless.Context) {
278+
fc, err := serverlessCtx.LLMFunctionCall()
279+
if err != nil {
280+
// it's not a LLM function call ctx
281+
return
282+
}
283+
if !fc.IsOK {
284+
logger.Warn("The function is not returning anything, please check if `WriteLLMResult()` has been called")
285+
}
286+
}

0 commit comments

Comments
 (0)