Skip to content

Commit 7c1dce1

Browse files
authored
Adding llm_chat function to starlark stored procedure. (#22300)
Adding `llm_chat` to starlark stored procedure. Approved by: @zhangxu19830126, @qingxinhome, @daviszhen, @heni02
1 parent 6ab6afc commit 7c1dce1

File tree

10 files changed

+632
-47
lines changed

10 files changed

+632
-47
lines changed

go.mod

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,15 @@ require (
8787
github.com/ti-mo/netfilter v0.5.2
8888
github.com/tidwall/btree v1.7.0
8989
github.com/tidwall/pretty v1.2.1
90+
github.com/tmc/langchaingo v0.1.13
9091
github.com/unum-cloud/usearch/golang v0.0.0-20250207215718-306d6646b8f5
9192
go.starlark.net v0.0.0-20250701195324-d457b4515e0e
9293
go.uber.org/automaxprocs v1.5.3
9394
go.uber.org/ratelimit v0.2.0
9495
go.uber.org/zap v1.24.0
9596
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c
96-
golang.org/x/sync v0.8.0
97-
golang.org/x/sys v0.26.0
97+
golang.org/x/sync v0.9.0
98+
golang.org/x/sys v0.27.0
9899
gonum.org/v1/gonum v0.14.0
99100
google.golang.org/grpc v1.65.0
100101
google.golang.org/protobuf v1.36.0
@@ -109,6 +110,7 @@ require (
109110
github.com/cilium/ebpf v0.9.1 // indirect
110111
github.com/clbanning/mxj v1.8.4 // indirect
111112
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
113+
github.com/dlclark/regexp2 v1.10.0 // indirect
112114
github.com/dustin/go-humanize v1.0.1 // indirect
113115
github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a // indirect
114116
github.com/go-ini/ini v1.67.0 // indirect
@@ -142,17 +144,18 @@ require (
142144
github.com/opencontainers/runtime-spec v1.0.2 // indirect
143145
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
144146
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
147+
github.com/pkoukk/tiktoken-go v0.1.6 // indirect
145148
github.com/rivo/uniseg v0.4.7 // indirect
146149
github.com/rs/xid v1.6.0 // indirect
147150
github.com/segmentio/asm v1.1.3 // indirect
148151
github.com/shoenig/go-m1cpu v0.1.6 // indirect
149152
github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 // indirect
150153
github.com/tetratelabs/wazero v1.8.1-0.20240916092830-1353ca24fef0 // indirect
151154
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
152-
golang.org/x/crypto v0.28.0 // indirect
155+
golang.org/x/crypto v0.29.0 // indirect
153156
golang.org/x/net v0.30.0 // indirect
154-
golang.org/x/text v0.19.0 // indirect
155-
golang.org/x/time v0.3.0 // indirect
157+
golang.org/x/text v0.20.0 // indirect
158+
golang.org/x/time v0.5.0 // indirect
156159
)
157160

158161
require (
@@ -209,7 +212,7 @@ require (
209212
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
210213
github.com/prometheus/common v0.44.0 // indirect
211214
github.com/prometheus/procfs v0.11.1 // indirect
212-
github.com/rogpeppe/go-internal v1.10.0 // indirect
215+
github.com/rogpeppe/go-internal v1.11.0 // indirect
213216
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect
214217
github.com/sirupsen/logrus v1.9.3 // indirect
215218
github.com/smartystreets/assertions v1.13.1 // indirect

go.sum

Lines changed: 43 additions & 36 deletions
Large diffs are not rendered by default.

pkg/frontend/starlark_interpreter.go

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020

2121
"github.com/matrixorigin/matrixone/pkg/common/moerr"
2222
"github.com/matrixorigin/matrixone/pkg/logutil"
23+
"github.com/matrixorigin/matrixone/pkg/monlp/llm"
2324
"github.com/matrixorigin/matrixone/pkg/sql/parsers/tree"
2425
"github.com/matrixorigin/matrixone/pkg/sql/plan"
2526
ujson "github.com/matrixorigin/matrixone/pkg/util/json"
@@ -33,6 +34,8 @@ type starlarkInterpreter struct {
3334
interp *Interpreter
3435
thread *starlark.Thread
3536
predeclared starlark.StringDict
37+
38+
llm llm.LLMClient
3639
}
3740

3841
func convertToStarlarkValue(ctx context.Context, v any) (starlark.Value, error) {
@@ -202,11 +205,13 @@ func (si *starlarkInterpreter) buildModule() starlark.Value {
202205
return &starlarkstruct.Module{
203206
Name: "mo",
204207
Members: starlark.StringDict{
205-
"sql": starlark.NewBuiltin("mo.sql", si.moSql),
206-
"jq": starlark.NewBuiltin("mo.jq", si.moJq),
207-
"quote": starlark.NewBuiltin("mo.quote", si.moQuote),
208-
"getvar": starlark.NewBuiltin("mo.getvar", si.moGetVar),
209-
"setvar": starlark.NewBuiltin("mo.getvar", si.moSetVar),
208+
"sql": starlark.NewBuiltin("mo.sql", si.moSql),
209+
"jq": starlark.NewBuiltin("mo.jq", si.moJq),
210+
"quote": starlark.NewBuiltin("mo.quote", si.moQuote),
211+
"getvar": starlark.NewBuiltin("mo.getvar", si.moGetVar),
212+
"setvar": starlark.NewBuiltin("mo.getvar", si.moSetVar),
213+
"llm_connect": starlark.NewBuiltin("mo.llm_connect", si.moLlmConnect),
214+
"llm_chat": starlark.NewBuiltin("mo.llm_chat", si.moLlmChat),
210215
},
211216
}
212217
}
@@ -339,3 +344,66 @@ func (si *starlarkInterpreter) moSetVar(thread *starlark.Thread, b *starlark.Bui
339344
}
340345
return starlark.NewList(ret), nil
341346
}
347+
348+
func (si *starlarkInterpreter) llmGetVar(name, orig string) string {
349+
if orig == "" {
350+
value, err := si.interp.ses.GetUserDefinedVar(name)
351+
if err != nil {
352+
return ""
353+
}
354+
return value.Value.(string)
355+
}
356+
return orig
357+
}
358+
359+
func (si *starlarkInterpreter) llmConnect(server, addr, model, options string) (llm.LLMClient, error) {
360+
server = si.llmGetVar("llm_server", server)
361+
addr = si.llmGetVar("llm_addr", addr)
362+
model = si.llmGetVar("llm_model", model)
363+
options = si.llmGetVar("llm_options", options)
364+
return llm.NewLLMClient(server, addr, model, options)
365+
}
366+
367+
func (si *starlarkInterpreter) moLlmConnect(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
368+
var err error
369+
var server, addr, model, options string
370+
var ret = []starlark.Value{starlark.None, starlark.None}
371+
if err := starlark.UnpackPositionalArgs("llm_connect", args, kwargs, 3, &server, &addr, &model, &options); err != nil {
372+
ret[1] = starlark.String(err.Error())
373+
return starlark.NewList(ret), nil
374+
}
375+
376+
si.llm, err = si.llmConnect(server, addr, model, options)
377+
if err != nil {
378+
ret[1] = starlark.String(err.Error())
379+
return starlark.NewList(ret), nil
380+
}
381+
return starlark.NewList(ret), nil
382+
}
383+
384+
func (si *starlarkInterpreter) moLlmChat(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
385+
var err error
386+
var prompt string
387+
var ret = []starlark.Value{starlark.None, starlark.None}
388+
if err := starlark.UnpackPositionalArgs("llm_chat", args, kwargs, 1, &prompt); err != nil {
389+
ret[1] = starlark.String(err.Error())
390+
return starlark.NewList(ret), nil
391+
}
392+
393+
if si.llm == nil {
394+
si.llm, err = si.llmConnect("", "", "", "")
395+
if err != nil {
396+
ret[1] = starlark.String(err.Error())
397+
return starlark.NewList(ret), nil
398+
}
399+
}
400+
401+
reply, err := si.llm.Chat(si.interp.ctx, prompt)
402+
if err != nil {
403+
ret[1] = starlark.String(err.Error())
404+
return starlark.NewList(ret), nil
405+
}
406+
407+
ret[0] = starlark.String(reply)
408+
return starlark.NewList(ret), nil
409+
}

pkg/monlp/llm/llm.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright 2024 Matrix Origin
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package llm
16+
17+
import (
18+
"context"
19+
"encoding/json"
20+
21+
"github.com/matrixorigin/matrixone/pkg/common/moerr"
22+
)
23+
24+
const (
25+
MockServer = ""
26+
OllamaServer = "ollama"
27+
28+
MockEchoModel = "echo"
29+
30+
LLMRoleSystem = "system"
31+
LLMRoleUser = "user"
32+
LLMRoleAI = "ai"
33+
)
34+
35+
type Message struct {
36+
Role string `json:"role"`
37+
Content string `json:"content"`
38+
}
39+
40+
type LLMClient interface {
41+
ChatMsg(ctx context.Context, messages []Message) (string, error)
42+
Chat(ctx context.Context, prompt string) (string, error)
43+
}
44+
45+
func NewLLMClient(server string, addr string, model string, options string) (LLMClient, error) {
46+
switch server {
47+
case MockServer:
48+
return NewMockClient(model, options)
49+
case OllamaServer:
50+
return NewOllamaClient(addr, model, options)
51+
default:
52+
return nil, moerr.NewInvalidInputf(context.TODO(), "invalid server: %s", server)
53+
}
54+
}
55+
56+
func stringToMessage(prompt string) ([]Message, error) {
57+
var messages []Message
58+
err := json.Unmarshal([]byte(prompt), &messages)
59+
if err != nil {
60+
return nil, err
61+
}
62+
return messages, nil
63+
}

pkg/monlp/llm/mockllm.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright 2024 Matrix Origin
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package llm
16+
17+
import (
18+
"context"
19+
20+
"github.com/matrixorigin/matrixone/pkg/common/moerr"
21+
)
22+
23+
type MockClient struct {
24+
model string
25+
options string
26+
}
27+
28+
func NewMockClient(model string, options string) (*MockClient, error) {
29+
switch model {
30+
case MockEchoModel:
31+
return &MockClient{model: model, options: options}, nil
32+
default:
33+
return nil, moerr.NewInvalidInputf(context.TODO(), "invalid model: %s", model)
34+
}
35+
}
36+
37+
func (c *MockClient) ChatMsg(ctx context.Context, messages []Message) (string, error) {
38+
switch c.model {
39+
case MockEchoModel:
40+
if len(messages) == 0 {
41+
return "", moerr.NewInvalidInputf(ctx, "no messages")
42+
}
43+
return messages[len(messages)-1].Content, nil
44+
}
45+
return "", moerr.NewInvalidInputf(ctx, "invalid model: %s", c.model)
46+
}
47+
48+
func (c *MockClient) Chat(ctx context.Context, prompt string) (string, error) {
49+
messages, err := stringToMessage(prompt)
50+
if err != nil {
51+
return "", err
52+
}
53+
return c.ChatMsg(ctx, messages)
54+
}

pkg/monlp/llm/mockllm_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright 2024 Matrix Origin
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package llm
16+
17+
import (
18+
"context"
19+
"encoding/json"
20+
"testing"
21+
)
22+
23+
func TestMockEchoModel(t *testing.T) {
24+
client, err := NewMockClient(MockEchoModel, "")
25+
if err != nil {
26+
t.Fatal(err)
27+
}
28+
29+
msgs := []Message{
30+
{
31+
Role: "system",
32+
Content: "You are a helpful assistant.",
33+
},
34+
{
35+
Role: "user",
36+
Content: "Hello, world!",
37+
},
38+
}
39+
40+
reply, err := client.ChatMsg(context.Background(), msgs)
41+
if err != nil {
42+
t.Fatal(err)
43+
}
44+
45+
if reply != "Hello, world!" {
46+
t.Fatal("reply is not correct")
47+
}
48+
49+
prompt, err := json.Marshal(msgs)
50+
if err != nil {
51+
t.Fatal(err)
52+
}
53+
reply, err = client.Chat(context.Background(), string(prompt))
54+
if err != nil {
55+
t.Fatal(err)
56+
}
57+
if reply != "Hello, world!" {
58+
t.Fatal("reply is not correct")
59+
}
60+
61+
msgs = append(msgs, Message{
62+
Role: "assistant",
63+
Content: reply + " again",
64+
})
65+
66+
reply, err = client.ChatMsg(context.Background(), msgs)
67+
if err != nil {
68+
t.Fatal(err)
69+
}
70+
71+
if reply != "Hello, world! again" {
72+
t.Fatal("reply is not correct")
73+
}
74+
}

0 commit comments

Comments
 (0)