1. Two parts the code and the backend

Aiflo is composed of two essential elements: the code, which is written in JSON format, and the backend infrastructure. The backend necessitates a Go-based runtime environment to function correctly. This runtime environment can be established and executed through multiple methods, providing flexibility in implementation.

1.1 Using Aiflo from a Go program

It is a very simple thing to use Aiflo from Go. You start by importing the Aiflo module:

    import "github.com/cloudcamelopard/aiflo"

then we need a compiler:

    compiler := aiflo.NewCompiler()

This is used to compile our programs and test for errors. Now, let's create the simplest possible Aiflo code file.

    [
        { "name":"greeting", "type":"response", "value":"Hello World!" }
    ]

We prepare this. Note you can store the 'prepared' code if you want or do it in advance.

    prepared,err := compiler.Prepare("first-program", "first-program.json")
    if err != nil {
        panic(err)
    }

After this we run the code by using a runner.

    runner := aiflo.NewRunner()
    response, err := runner.Run(prepared)
    if err != nil {
        panic(err)
    }

And we can output it as:

   fmt.Println(response["greeting"])

The full code is for the program running the aiflo code:

    package main

    import (
        "fmt"   
        "github.com/cloudcamelopard/aiflo"
    )

    func main() {
        compiler := aiflo.NewCompiler()
        prepared, err := compiler.Prepare("first-program", "first-program.json")
        if err != nil {
            panic(err)
        }
        runner := aiflo.NewRunner()
        response, err := runner.Run(prepared)
        if err != nil {
            panic(err)
        }
        fmt.Println(response["greeting"])
    }

and the aiflo code:

    [
        { "name":"greeting", "type":"response", "value":"Hello World!" }
    ]

You see, it's not very hard.

1.2 Inputs

Most programs require inputs. It is easy to send inputs to a program. Lets modify our program taking two strings to and from.

    [
        { "type":"inputs", "then":[
            { "value":"to", "target":"message_builder" },
            { "value":"from", "target":"message_builder" }
        ]},
        { "name":"message_builder", "type":"template", "value":"Greeting {{.to}} from {{.from}}!", "then":[
            { "target":"greeting" }
        ]},
        { "name":"greeting", "type":"response" }
    ]

Inserting the inputs is done in the run method it takes a variadic argument

   runner.Run(prepared, map[string] { "to":"Joe", "from":"Ronny" })

If we run it we get:

    panic: type template not handled

You have to specify for the compiler which types you support, this is done with:

   compiler.AddType(types.TemplateTypeName, types.TemplateTypeHandler)

That's all. If we now run it it outputs:

    Greeting Joe from Ronny!

The full code:

   package main

    import (
        "fmt"

        "github.com/cloudcamelopard/aiflo"
        "github.com/cloudcamelopard/aiflo/types"
    )

    func main() {
        compiler := aiflo.NewCompiler()
        compiler.AddType(types.TemplateTypeName, types.TemplateTypeHandler)
        prepared, err := compiler.Prepare("first-program", "first-program.json")
        if err != nil {
            panic(err)
        }
        runner := aiflo.NewRunner()
        response, err := runner.Run(prepared, map[string]any{ "to":"Joe", "from": "Ronny"})
        if err != nil {
            panic(err)
        }
        fmt.Println(response["greeting"])
    }

1.3 Making a custom type

One of the strengths with Aiflo is its extensibility. Let's make our own node uppercase. This will ... make an input uppercase.

We make a function.

    func UppercaseTypeHandler(value any, inputs []any) any {
        if len(inputs) > 1 {
            return errors.New("uppercase type max 1 input of type string")
        }
        var str string
        if val, ok := value.(string); ok {
            str = strings.ToUpper(val)
        }
        if len(inputs) == 1 {
            if val, ok := inputs[0].(string); ok {
                str = strings.ToUpper(val)
            } else {
                return errors.New("uppercase type max 1 input of type string")
            }
        }
        return str
    }

And add if like before to the compiler:

    compiler.AddType("uppercase", UppercaseTypeHandler)

And we modify our program to use it:

    [
        { "type":"inputs", "then":[
            { "value":"to", "target":"message_builder", "name":"to"},
            { "value":"from", "target":"message_builder", "name":"from"}
        ]},
        { "name":"message_builder", "type":"template", "value":"Greeting {{"{{.to}}"}} from {{"{{.from}}"}}!", "then":[
            { "target":"uppercase" }
        ]},
        { "name":"uppercase", "type":"uppercase", "then":[
            { "target": "greeting"}
        ]},
        { "name":"greeting", "type":"response" }
    ]

And the full code for the program:

    package main

    import (
        "errors"
        "fmt"
        "strings"

        "github.com/cloudcamelopard/aiflo"
        "github.com/cloudcamelopard/aiflo/types"
    )

    func UppercaseTypeHandler(value any, inputs []any) any {
        if len(inputs) > 1 {
            return errors.New("uppercase type max 1 input of type string")
        }
        var str string
        if val, ok := value.(string); ok {
            str = strings.ToUpper(val)
        }
        if len(inputs) == 1 {
            if val, ok := inputs[0].(string); ok {
                str = strings.ToUpper(val)
            } else {
                return errors.New("uppercase type max 1 input of type string")
            }
        }
        return str
    }

    func main() {
        compiler := aiflo.NewCompiler()
        compiler.AddType(types.TemplateTypeName, types.TemplateTypeHandler)
        compiler.AddType("uppercase", UppercaseTypeHandler)

        prepared, err := compiler.Prepare("first-program", "first-program.json")
        if err != nil {
            panic(err)
        }
        runner := aiflo.NewRunner()
        response, err := runner.Run(prepared, map[string]any{ "to":"Joe", "from": "Ronny"})
        if err != nil {
            panic(err)
        }
        fmt.Println(response["greeting"])
    }