Skip to content
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

feat: add init type for cli #979

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions cli/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
)

var name string
var sfnType string

// initCmd represents the init command
var initCmd = &cobra.Command{
Expand All @@ -48,15 +49,30 @@
name = strings.ReplaceAll(name, " ", "_")
// create app.go
fname := filepath.Join(name, defaultSFNSourceFile)
contentTmpl := golang.InitTmpl
// sfn content template
var contentTmpl, testTmpl []byte
// sfn type
switch sfnType {
case "llm":
contentTmpl = golang.InitLLMTmpl
testTmpl = golang.InitLLMTestTmpl
case "normal":
contentTmpl = golang.InitTmpl
testTmpl = golang.InitTestTmpl
default:
log.WarningStatusEvent(os.Stdout, "The type of Stream Function is not supported, use the default type: llm")
contentTmpl = golang.InitLLMTmpl
testTmpl = golang.InitLLMTestTmpl

Check warning on line 65 in cli/init.go

View check run for this annotation

Codecov / codecov/patch

cli/init.go#L52-L65

Added lines #L52 - L65 were not covered by tests

}
if err := file.PutContents(fname, contentTmpl); err != nil {
log.FailureStatusEvent(os.Stdout, "Write stream function into app.go file failure with the error: %v", err)
return
}

// create app_test.go
testName := filepath.Join(name, defaultSFNTestSourceFile)
if err := file.PutContents(testName, golang.InitTestTmpl); err != nil {
if err := file.PutContents(testName, testTmpl); err != nil {

Check warning on line 75 in cli/init.go

View check run for this annotation

Codecov / codecov/patch

cli/init.go#L75

Added line #L75 was not covered by tests
log.FailureStatusEvent(os.Stdout, "Write unittest tmpl into app_test.go file failure with the error: %v", err)
return
}
Expand All @@ -79,4 +95,5 @@
rootCmd.AddCommand(initCmd)

initCmd.Flags().StringVarP(&name, "name", "n", "", "The name of Stream Function")
initCmd.Flags().StringVarP(&sfnType, "type", "t", "llm", "The type of Stream Function, support normal and llm")

Check warning on line 98 in cli/init.go

View check run for this annotation

Codecov / codecov/patch

cli/init.go#L98

Added line #L98 was not covered by tests
}
6 changes: 6 additions & 0 deletions cli/serverless/golang/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,15 @@ var MainFuncTmpl []byte
//go:embed templates/init.tmpl
var InitTmpl []byte

//go:embed templates/init_llm.tmpl
var InitLLMTmpl []byte

//go:embed templates/init_test.tmpl
var InitTestTmpl []byte

//go:embed templates/init_llm_test.tmpl
var InitLLMTestTmpl []byte

//go:embed templates/wasi_main.tmpl
var WasiMainFuncTmpl []byte

Expand Down
52 changes: 11 additions & 41 deletions cli/serverless/golang/templates/init.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package main

import (
"fmt"
"log/slog"
"strings"

"github.com/yomorun/yomo/serverless"
)
Expand All @@ -16,55 +16,25 @@ func Init() error {
return nil
}

// Description outlines the functionality for the LLM Function Calling feature.
// It provides a detailed description of the function's purpose, essential for
// integration with LLM Function Calling. The presence of this function and its
// return value make the function discoverable and callable within the LLM
// ecosystem. For more information on Function Calling, refer to the OpenAI
// documentation at: https://platform.openai.com/docs/guides/function-calling
func Description() string {
return `Get current weather for a given city. If no city is provided, you
should ask to clarify the city. If the city name is given, you should
convert the city name to Latitude and Longitude geo coordinates, keeping
Latitude and Longitude in decimal format.`
}

// InputSchema defines the argument structure for LLM Function Calling. It
// utilizes jsonschema tags to detail the definition. For jsonschema in Go,
// see https://github.com/invopop/jsonschema.
func InputSchema() any {
return &LLMArguments{}
}

// LLMArguments defines the arguments for the LLM Function Calling. These
// arguments are combined to form a prompt automatically.
type LLMArguments struct {
City string `json:"city" jsonschema:"description=The city name to get the weather for,required"`
Latitude float64 `json:"latitude" jsonschema:"description=The latitude of the city, in decimal format, range should be in (-90, 90)"`
Longitude float64 `json:"longitude" jsonschema:"description=The longitude of the city, in decimal format, range should be in (-180, 180)"`
}

// DataTags specifies the data tags to which this serverless function
// subscribes, essential for data reception. Upon receiving data with these
// tags, the Handler function is triggered.
func DataTags() []uint32 {
return []uint32{0x30}
return []uint32{0x33}
}

// Handler orchestrates the core processing logic of this function.
// - ctx.ReadLLMArguments() parses LLM Function Calling Arguments (skip if none).
// - ctx.WriteLLMResult() sends the retrieval result back to LLM.
// - ctx.Tag() identifies the tag of the incoming data.
// - ctx.Data() accesses the raw data.
// - ctx.Write() forwards processed data downstream.
func Handler(ctx serverless.Context) {
var p LLMArguments
// deserilize the arguments from llm tool_call response
ctx.ReadLLMArguments(&p)

// invoke the open weather map api and return the result back to LLM
result := fmt.Sprintf("The current weather in %s (%f,%f) is sunny", p.City, p.Latitude, p.Longitude)
ctx.WriteLLMResult(result)

slog.Info("get-weather", "city", p.City, "rag", result)
data := ctx.Data()
fmt.Printf("<< sfn received[%d Bytes]: %s\n", len(data), data)
output := strings.ToUpper(string(data))
err := ctx.Write(0x34, []byte(output))
if err != nil {
fmt.Printf(">> sfn write error: %v\n", err)
return
}
fmt.Printf(">> sfn written[%d Bytes]: %s\n", len(output), output)
}
70 changes: 70 additions & 0 deletions cli/serverless/golang/templates/init_llm.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package main

import (
"fmt"
"log/slog"

"github.com/yomorun/yomo/serverless"
)

// Init is an optional function invoked during the initialization phase of the
// sfn instance. It's designed for setup tasks like global variable
// initialization, establishing database connections, or loading models into
// GPU memory. If initialization fails, the sfn instance will halt and terminate.
// This function can be omitted if no initialization tasks are needed.
func Init() error {
return nil
}

// Description outlines the functionality for the LLM Function Calling feature.
// It provides a detailed description of the function's purpose, essential for
// integration with LLM Function Calling. The presence of this function and its
// return value make the function discoverable and callable within the LLM
// ecosystem. For more information on Function Calling, refer to the OpenAI
// documentation at: https://platform.openai.com/docs/guides/function-calling
func Description() string {
return `Get current weather for a given city. If no city is provided, you
should ask to clarify the city. If the city name is given, you should
convert the city name to Latitude and Longitude geo coordinates, keeping
Latitude and Longitude in decimal format.`
}

// InputSchema defines the argument structure for LLM Function Calling. It
// utilizes jsonschema tags to detail the definition. For jsonschema in Go,
// see https://github.com/invopop/jsonschema.
func InputSchema() any {
return &LLMArguments{}
}

// LLMArguments defines the arguments for the LLM Function Calling. These
// arguments are combined to form a prompt automatically.
type LLMArguments struct {
City string `json:"city" jsonschema:"description=The city name to get the weather for,required"`
Latitude float64 `json:"latitude" jsonschema:"description=The latitude of the city, in decimal format, range should be in (-90, 90)"`
Longitude float64 `json:"longitude" jsonschema:"description=The longitude of the city, in decimal format, range should be in (-180, 180)"`
}

// DataTags specifies the data tags to which this serverless function
// subscribes, essential for data reception. Upon receiving data with these
// tags, the Handler function is triggered.
func DataTags() []uint32 {
return []uint32{0x30}
}

// Handler orchestrates the core processing logic of this function.
// - ctx.ReadLLMArguments() parses LLM Function Calling Arguments (skip if none).
// - ctx.WriteLLMResult() sends the retrieval result back to LLM.
// - ctx.Tag() identifies the tag of the incoming data.
// - ctx.Data() accesses the raw data.
// - ctx.Write() forwards processed data downstream.
func Handler(ctx serverless.Context) {
var p LLMArguments
// deserilize the arguments from llm tool_call response
ctx.ReadLLMArguments(&p)

// invoke the open weather map api and return the result back to LLM
result := fmt.Sprintf("The current weather in %s (%f,%f) is sunny", p.City, p.Latitude, p.Longitude)
ctx.WriteLLMResult(result)

slog.Info("get-weather", "city", p.City, "rag", result)
}
40 changes: 40 additions & 0 deletions cli/serverless/golang/templates/init_llm_test.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package main

import (
"fmt"
"reflect"
"testing"

"github.com/yomorun/yomo/ai"
"github.com/yomorun/yomo/serverless/mock"
)

func TestHandler(t *testing.T) {
tests := []struct {
name string
ctx *mock.MockContext
// want is the expected data and tag that be written by ctx.Write
want []mock.WriteRecord
}{
{
name: "get weather",
ctx: mock.NewMockContext([]byte(`{"arguments":"{\"city\":\"New York\",\"latitude\":40.7128,\"longitude\":-74.0060}"}`), 0x33),
want: []mock.WriteRecord{
{Data: []byte(`{"result":"The current weather in New York (40.712800,-74.006000) is sunny","arguments":"{\"city\":\"New York\",\"latitude\":40.7128,\"longitude\":-74.0060}","is_ok":true}`), Tag: ai.ReducerTag},
},
},
// TODO: add more test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Handler(tt.ctx)
got := tt.ctx.RecordsWritten()

fmt.Println(string(got[0].Data))

if !reflect.DeepEqual(got, tt.want) {
t.Errorf("TestHandler got: %v, want: %v", got, tt.want)
}
})
}
}
7 changes: 3 additions & 4 deletions cli/serverless/golang/templates/init_test.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"reflect"
"testing"

"github.com/yomorun/yomo/ai"
"github.com/yomorun/yomo/serverless/mock"
)

Expand All @@ -17,10 +16,10 @@ func TestHandler(t *testing.T) {
want []mock.WriteRecord
}{
{
name: "get weather",
ctx: mock.NewMockContext([]byte(`{"arguments":"{\"city\":\"New York\",\"latitude\":40.7128,\"longitude\":-74.0060}"}`), 0x33),
name: "upper",
ctx: mock.NewMockContext([]byte("hello"), 0x33),
want: []mock.WriteRecord{
{Data: []byte(`{"result":"The current weather in New York (40.712800,-74.006000) is sunny","arguments":"{\"city\":\"New York\",\"latitude\":40.7128,\"longitude\":-74.0060}","is_ok":true}`), Tag: ai.ReducerTag},
{Data: []byte("HELLO"), Tag: 0x34},
},
},
// TODO: add more test cases.
Expand Down
Loading