Skip to content

Split by LLM #1319

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

Merged
merged 15 commits into from
Apr 7, 2025
Merged
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
40 changes: 30 additions & 10 deletions flows/actions/base_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,9 @@ func testActionType(t *testing.T, assetsJSON json.RawMessage, typeName string) {
Events json.RawMessage `json:"events,omitempty"`
Webhook json.RawMessage `json:"webhook,omitempty"`
ContactAfter json.RawMessage `json:"contact_after,omitempty"`
LocalsAfter json.RawMessage `json:"locals_after,omitempty"`
Templates []string `json:"templates,omitempty"`
LocalizedText []string `json:"localizables,omitempty"`
Locals json.RawMessage `json:"locals,omitempty"`
Inspection json.RawMessage `json:"inspection,omitempty"`
}{}

Expand Down Expand Up @@ -271,15 +271,15 @@ func testActionType(t *testing.T, assetsJSON json.RawMessage, typeName string) {
if tc.ContactAfter != nil {
actual.ContactAfter, _ = jsonx.Marshal(session.Contact())
}
if tc.LocalsAfter != nil {
actual.LocalsAfter, _ = jsonx.Marshal(run.Locals())
}
if tc.Templates != nil {
actual.Templates = flow.ExtractTemplates()
}
if tc.LocalizedText != nil {
actual.LocalizedText = flow.ExtractLocalizables()
}
if tc.Locals != nil {
actual.Locals, _ = jsonx.Marshal(run.Locals())
}
if tc.Inspection != nil {
actual.Inspection, _ = jsonx.Marshal(flow.Inspect(sa))
}
Expand All @@ -301,6 +301,11 @@ func testActionType(t *testing.T, assetsJSON json.RawMessage, typeName string) {
test.AssertEqualJSON(t, tc.ContactAfter, actual.ContactAfter, "contact mismatch in %s", testName)
}

// check locals match
if tc.LocalsAfter != nil {
test.AssertEqualJSON(t, tc.LocalsAfter, actual.LocalsAfter, "locals mismatch in %s", testName)
}

// check extracted templates
if tc.Templates != nil {
assert.Equal(t, tc.Templates, actual.Templates, "extracted templates mismatch in %s", testName)
Expand All @@ -311,11 +316,6 @@ func testActionType(t *testing.T, assetsJSON json.RawMessage, typeName string) {
assert.Equal(t, tc.LocalizedText, actual.LocalizedText, "extracted localized text mismatch in %s", testName)
}

// check locals match
if tc.Locals != nil {
test.AssertEqualJSON(t, tc.Locals, actual.Locals, "locals mismatch in %s", testName)
}

// check inspection results
if tc.Inspection != nil {
test.AssertEqualJSON(t, tc.Inspection, actual.Inspection, "inspection mismatch in %s", testName)
Expand Down Expand Up @@ -401,7 +401,7 @@ func TestConstructors(t *testing.T) {
{
actions.NewCallClassifier(
actionUUID,
assets.NewClassifierReference(assets.ClassifierUUID("0baee364-07a7-4c93-9778-9f55a35903bb"), "Booking"),
assets.NewClassifierReference("0baee364-07a7-4c93-9778-9f55a35903bb", "Booking"),
"@input.text",
"Intent",
),
Expand All @@ -416,6 +416,26 @@ func TestConstructors(t *testing.T) {
"result_name": "Intent"
}`,
},
{
actions.NewCallLLM(
actionUUID,
assets.NewLLMReference("0baee364-07a7-4c93-9778-9f55a35903bb", "GPT-4"),
"Tell a joke about a person with this name",
"@contact.name",
"the_joke",
),
`{
"type": "call_llm",
"uuid": "ad154980-7bf7-4ab8-8728-545fd6378912",
"llm": {
"uuid": "0baee364-07a7-4c93-9778-9f55a35903bb",
"name": "GPT-4"
},
"instructions": "Tell a joke about a person with this name",
"input": "@contact.name",
"output_local": "the_joke"
}`,
},
{
actions.NewCallResthook(
actionUUID,
Expand Down
22 changes: 13 additions & 9 deletions flows/actions/call_llm.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ func init() {
// TypeCallLLM is the type for the call LLM action
const TypeCallLLM string = "call_llm"

// LLMErrorOutput is the output used when the LLM call fails
const LLMErrorOutput = "<ERROR>"

// CallLLMAction can be used to call an LLM.
//
// An [event:llm_called] event will be created if the LLM could be called.
Expand All @@ -28,38 +31,39 @@ const TypeCallLLM string = "call_llm"
// "name": "GPT-4"
// },
// "instructions": "Categorize the following text as positive or negative",
// "input": "@input.text"
// "input": "@input.text",
// "output_local": "_llm_output"
// }
//
// @action call_llm
type CallLLMAction struct {
baseAction
onlineAction

LLM *assets.LLMReference `json:"llm" validate:"required"`
Instructions string `json:"instructions" validate:"required" engine:"evaluated"`
Input string `json:"input" validate:"required" engine:"evaluated"`
LLM *assets.LLMReference `json:"llm" validate:"required"`
Instructions string `json:"instructions" validate:"required" engine:"evaluated"`
Input string `json:"input" engine:"evaluated"`
OutputLocal string `json:"output_local" validate:"required,local_ref"`
}

// NewCallLLM creates a new call LLM action
func NewCallLLM(uuid flows.ActionUUID, llm *assets.LLMReference, instructions, input string) *CallLLMAction {
func NewCallLLM(uuid flows.ActionUUID, llm *assets.LLMReference, instructions, input, outputLocal string) *CallLLMAction {
return &CallLLMAction{
baseAction: newBaseAction(TypeCallLLM, uuid),
LLM: llm,
Instructions: instructions,
Input: input,
OutputLocal: outputLocal,
}
}

// Execute runs this action
func (a *CallLLMAction) Execute(ctx context.Context, run flows.Run, step flows.Step, logModifier flows.ModifierCallback, logEvent flows.EventCallback) error {
resp := a.call(ctx, run, logEvent)
if resp != nil {
run.Locals().Set("_llm_status", "success")
run.Locals().Set("_llm_output", resp.Output)
run.Locals().Set(a.OutputLocal, resp.Output)
} else {
run.Locals().Set("_llm_status", "failure")
run.Locals().Set("_llm_output", "")
run.Locals().Set(a.OutputLocal, LLMErrorOutput)
}

return nil
Expand Down
32 changes: 21 additions & 11 deletions flows/actions/set_run_local.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,21 @@
// TypeSetRunLocal is the type for the set run result action
const TypeSetRunLocal string = "set_run_local"

type LocalOperation string

const (
LocalOperationSet LocalOperation = "set"
LocalOperationIncrement LocalOperation = "increment"
LocalOperationClear LocalOperation = "clear"
)

// SetRunLocalAction can be used to save a local variable. The local will be available in the context
// for the run as @locals.[name]. The value field can be a template and will be evaluated.
// for the run as @locals.[local]. The value field can be a template and will be evaluated.
//
// {
// "uuid": "8eebd020-1af5-431c-b943-aa670fc74da9",
// "type": "set_run_local",
// "name": "my_var",
// "local": "my_var",
// "value": "1",
// "operation": "increment"
// }
Expand All @@ -32,16 +40,16 @@
baseAction
universalAction

Name string `json:"name" validate:"required,local_name"`
Value string `json:"value" engine:"evaluated" validate:"max=1000"`
Operation string `json:"operation" validate:"required,eq=set|eq=increment"`
Local string `json:"local" validate:"required,local_ref"`
Value string `json:"value,omitempty" engine:"evaluated" validate:"max=1000"`
Operation LocalOperation `json:"operation" validate:"required,eq=set|eq=increment|eq=clear"`
}

// NewSetRunLocal creates a new set run local action
func NewSetRunLocal(uuid flows.ActionUUID, name, value string) *SetRunLocalAction {
func NewSetRunLocal(uuid flows.ActionUUID, local, value string) *SetRunLocalAction {

Check warning on line 49 in flows/actions/set_run_local.go

View check run for this annotation

Codecov / codecov/patch

flows/actions/set_run_local.go#L49

Added line #L49 was not covered by tests
return &SetRunLocalAction{
baseAction: newBaseAction(TypeSetRunLocal, uuid),
Name: name,
Local: local,

Check warning on line 52 in flows/actions/set_run_local.go

View check run for this annotation

Codecov / codecov/patch

flows/actions/set_run_local.go#L52

Added line #L52 was not covered by tests
Value: value,
}
}
Expand All @@ -53,16 +61,18 @@
return nil
}

if a.Operation == "increment" {
existing, _ := strconv.Atoi(run.Locals().Get(a.Name))
if a.Operation == LocalOperationSet {
run.Locals().Set(a.Local, value)
} else if a.Operation == LocalOperationIncrement {
existing, _ := strconv.Atoi(run.Locals().Get(a.Local))
increment, err := strconv.Atoi(value)
if err != nil {
logEvent(events.NewError("increment value is not an integer"))
} else {
run.Locals().Set(a.Name, fmt.Sprint(existing+increment))
run.Locals().Set(a.Local, fmt.Sprint(existing+increment))
}
} else {
run.Locals().Set(a.Name, value)
run.Locals().Clear(a.Local)
}

return nil
Expand Down
28 changes: 14 additions & 14 deletions flows/actions/testdata/call_llm.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"name": "Deleted"
},
"instructions": "Categorize the following text as positive or negative",
"input": "@input.text"
"input": "@input.text",
"output_local": "_llm_output"
},
"events": [
{
Expand All @@ -24,8 +25,7 @@
"@input.text"
],
"locals": {
"_llm_status": "failure",
"_llm_output": ""
"_llm_output": "\u003cERROR\u003e"
},
"inspection": {
"dependencies": [
Expand Down Expand Up @@ -63,31 +63,31 @@
"name": "Claude"
},
"instructions": "Categorize the following text as positive or negative",
"input": "@input.text"
"input": "@input.text",
"output_local": "_llm_output"
},
"events": [
{
"type": "llm_called",
"created_on": "2018-10-18T14:20:30.000123456Z",
"elapsed_ms": 0,
"input": "Hi everybody",
"instructions": "Categorize the following text as positive or negative",
"step_uuid": "9688d21d-95aa-4bed-afc7-f31b35731a3d",
"llm": {
"name": "Claude",
"uuid": "51ade705-8338-40a9-8a77-37657a936966"
"uuid": "51ade705-8338-40a9-8a77-37657a936966",
"name": "Claude"
},
"output": "You asked:\n\nCategorize the following text as positive or negative\n\nHi everybody",
"step_uuid": "9688d21d-95aa-4bed-afc7-f31b35731a3d",
"instructions": "Categorize the following text as positive or negative",
"input": "Hi everybody",
"output": "negative",
"tokens_used": 123,
"type": "llm_called"
"elapsed_ms": 0
}
],
"templates": [
"Categorize the following text as positive or negative",
"@input.text"
],
"locals": {
"_llm_status": "success",
"_llm_output": "You asked:\n\nCategorize the following text as positive or negative\n\nHi everybody"
"_llm_output": "negative"
},
"inspection": {
"dependencies": [
Expand Down
40 changes: 29 additions & 11 deletions flows/actions/testdata/set_run_local.json
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
[
{
"description": "Read fails when name or operation is empty",
"description": "Read fails when local or operation is empty",
"action": {
"uuid": "ad154980-7bf7-4ab8-8728-545fd6378912",
"type": "set_run_local",
"name": "",
"local": "",
"value": "bar",
"operation": ""
},
"read_error": "field 'name' is required, field 'operation' is required"
"read_error": "field 'local' is required, field 'operation' is required"
},
{
"description": "Error event and action skipped if value contains expression error",
"action": {
"type": "set_run_local",
"uuid": "ad154980-7bf7-4ab8-8728-545fd6378912",
"name": "my_var",
"local": "my_var",
"value": "@(1 / 0)",
"operation": "set"
},
Expand All @@ -42,7 +42,7 @@
"action": {
"type": "set_run_local",
"uuid": "ad154980-7bf7-4ab8-8728-545fd6378912",
"name": "counter",
"local": "counter",
"value": "xxx",
"operation": "increment"
},
Expand All @@ -65,16 +65,16 @@
}
},
{
"description": "Non-existent local updated via set",
"description": "Non-existent local set",
"action": {
"type": "set_run_local",
"uuid": "ad154980-7bf7-4ab8-8728-545fd6378912",
"name": "my_var",
"local": "my_var",
"value": "@fields.Gender",
"operation": "set"
},
"events": [],
"locals": {
"locals_after": {
"my_var": "Male"
},
"templates": [
Expand All @@ -94,16 +94,16 @@
}
},
{
"description": "Non-existent local updated via increment",
"description": "Non-existent local incremented",
"action": {
"type": "set_run_local",
"uuid": "ad154980-7bf7-4ab8-8728-545fd6378912",
"name": "counter",
"local": "counter",
"value": "@(3 + 4)",
"operation": "increment"
},
"events": [],
"locals": {
"locals_after": {
"counter": "7"
},
"templates": [
Expand All @@ -115,5 +115,23 @@
"results": [],
"parent_refs": []
}
},
{
"description": "Non-existent local cleared",
"action": {
"type": "set_run_local",
"uuid": "ad154980-7bf7-4ab8-8728-545fd6378912",
"local": "counter",
"operation": "clear"
},
"events": [],
"locals_after": {},
"templates": [],
"inspection": {
"dependencies": [],
"issues": [],
"results": [],
"parent_refs": []
}
}
]
Loading