Skip to content

Commit

Permalink
example/my-app use OpenAI function calling
Browse files Browse the repository at this point in the history
  • Loading branch information
widmogrod committed Nov 10, 2023
1 parent 77d3432 commit 1ffcef9
Show file tree
Hide file tree
Showing 4 changed files with 204 additions and 75 deletions.
1 change: 1 addition & 0 deletions example/my-app/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ replace github.com/widmogrod/mkunion => ../../

require (
github.com/labstack/echo/v4 v4.11.2
github.com/sashabaranov/go-openai v1.17.3
github.com/sirupsen/logrus v1.9.3
github.com/widmogrod/mkunion v1.17.1
golang.org/x/image v0.13.0
Expand Down
85 changes: 85 additions & 0 deletions example/my-app/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"fmt"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"github.com/sashabaranov/go-openai"
"github.com/sashabaranov/go-openai/jsonschema"
log "github.com/sirupsen/logrus"
"github.com/widmogrod/mkunion/x/machine"
"github.com/widmogrod/mkunion/x/schema"
Expand All @@ -19,9 +21,27 @@ import (
"io"
"math/rand"
"net/http"
"os"
)

//go:generate mkunion -name=ChatCMD
type (
UserMessage struct {
Message string
}
)

//go:generate mkunion -name=ChatResult
type (
SystemResponse struct {
Message string
ToolCalls []openai.ToolCall
}
)

func main() {
schema.RegisterUnionTypes(ChatResultSchemaDef())
schema.RegisterUnionTypes(ChatCMDSchemaDef())
schema.RegisterRules([]schema.RuleMatcher{
schema.WhenPath([]string{"*", "BaseState"}, schema.UseStruct(workflow.BaseState{})),
schema.WhenPath([]string{"*", "Await"}, schema.UseStruct(&workflow.ApplyAwaitOptions{})),
Expand Down Expand Up @@ -57,6 +77,8 @@ func main() {
},
}

oaic := openai.NewClient(os.Getenv("OPENAI_API_KEY"))

srv := NewService[workflow.Command, workflow.State](
"process",
statesRepo,
Expand Down Expand Up @@ -96,6 +118,69 @@ func main() {
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"*"},
}))
e.POST("/message", TypedRequest(func(x ChatCMD) (ChatResult, error) {
log.Infof("x: %+v", x)
ctx := context.Background()
result, err := oaic.CreateChatCompletion(ctx, openai.ChatCompletionRequest{
Model: openai.GPT3Dot5Turbo1106,
Messages: []openai.ChatCompletionMessage{
{
Role: openai.ChatMessageRoleUser,
Content: x.(*UserMessage).Message,
},
},
Tools: []openai.Tool{
{
Type: openai.ToolTypeFunction,
Function: openai.FunctionDefinition{
Name: "list_workflows",
Description: "list all workflows that are being executed",
Parameters: &jsonschema.Definition{
Type: jsonschema.Object,
Properties: map[string]jsonschema.Definition{
"count": {
Type: jsonschema.Number,
Description: "total number of words in sentence",
},
"words": {
Type: jsonschema.Array,
Description: "list of words in sentence",
Items: &jsonschema.Definition{
Type: jsonschema.String,
},
},
"enumTest": {
Type: jsonschema.String,
Enum: []string{"hello", "world"},
},
},
},
},
},
},
})

if err != nil {
log.Errorf("failed to create chat completion: %v", err)
return nil, err
}

log.Infof("result: %+v", result)
return &SystemResponse{
Message: result.Choices[0].Message.Content,
ToolCalls: result.Choices[0].Message.ToolCalls,
}, nil
}))

e.POST("/func", TypedRequest(func(x *workflow.FunctionInput) (*workflow.FunctionOutput, error) {
fn, err := di.FindFunction(x.Name)
if err != nil {
return nil, err
}

return fn(x)
}))

e.POST("/flow", func(c echo.Context) error {
data, err := io.ReadAll(c.Request().Body)
if err != nil {
Expand Down
37 changes: 36 additions & 1 deletion example/my-app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import './App.css';
import * as workflow from './workflow/workflow'
import {dediscriminateCommand} from './workflow/workflow'
import * as schema from "./workflow/github_com_widmogrod_mkunion_x_schema";
import {Chat} from "./Chat";

function flowCreate(flow: workflow.Flow) {
console.log("save-flow", flow)
Expand All @@ -23,6 +24,7 @@ function flowToString(flow: workflow.Worflow) {
function App() {
const [state, setState] = React.useState({} as workflow.State);
const [input, setInput] = React.useState("hello");
const [output, setOutput] = React.useState("" as any);

type record = {
ID: string,
Expand Down Expand Up @@ -433,9 +435,14 @@ function App() {

<CreateAttachment input={input}/>

<CallConcat name={input} onResult={(x) => setOutput(JSON.stringify(x))}/>

<table>
<tbody>
<tr>
<td>
<Chat name="John"/>
</td>
<td>
<PaginatedTable table={flows} mapData={(data: workflow.Flow) => {
return <WorkflowToString flow={{
Expand Down Expand Up @@ -500,7 +507,8 @@ function App() {
</td>
<td>
<img src={`data:image/jpeg;base64,${image}`} alt=""/>
<pre>{JSON.stringify(state, null, 2)} </pre>
<pre>Func output: {output}</pre>
<pre>Workflow output: {JSON.stringify(state, null, 2)} </pre>
</td>
</tr>
</tbody>
Expand Down Expand Up @@ -823,4 +831,31 @@ function ResumeSchedule(props: { parentRunID: string }) {
return <button onClick={doIt}>
Resume Schedule
</button>
}

function CallConcat(props: { name: string, onResult: (x: workflow.FunctionOutput) => void }) {
const cmd: workflow.FunctionInput = {
Name: "concat",
Args: [
{
"schema.String": "hello ",
},
{
"schema.String": props.name,
},
]
}

const doIt = () => {
fetch('http://localhost:8080/func', {
method: 'POST',
body: JSON.stringify(cmd),
})
.then(res => res.json())
.then(data => props.onResult(data as workflow.FunctionOutput))
}

return <button onClick={doIt}>
Concat with {props.name}
</button>
}
156 changes: 82 additions & 74 deletions example/my-app/src/workflow/workflow.ts
Original file line number Diff line number Diff line change
@@ -1,77 +1,5 @@
//generated by mkunion

/**
* This function is used to remove $type field from Command, so it can be understood by mkunion,
* that is assuming unions have one field in object, that is used to discriminate between variants.
*/
export function dediscriminateCommand(x: Command): Command {
if (x["$type"] !== undefined) {
delete x["$type"]
return x
}
return x
}

/**
* This function is used to populate $type field in Command, so it can be used as discriminative switch statement
* @example https://www.typescriptlang.org/play#example/discriminate-types
*/
export function discriminateCommand(x: Command): Command {
if (x["$type"] === undefined) {
let keyx = Object.keys(x)
if (keyx.length === 1) {
x["$type"] = keyx[0] as any
}
}
return x
}

export type Command = {
// $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema
"$type"?: "workflow.Run",
"workflow.Run": Run
} | {
// $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema
"$type"?: "workflow.Callback",
"workflow.Callback": Callback
} | {
// $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema
"$type"?: "workflow.TryRecover",
"workflow.TryRecover": TryRecover
} | {
// $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema
"$type"?: "workflow.StopSchedule",
"workflow.StopSchedule": StopSchedule
} | {
// $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema
"$type"?: "workflow.ResumeSchedule",
"workflow.ResumeSchedule": ResumeSchedule
}

export type Run = {
Flow?: Worflow,
Input?: schema.Schema,
RunOption?: RunOption,
}

export type Callback = {
CallbackID?: string,
Result?: schema.Schema,
}

export type TryRecover = {
}

export type StopSchedule = {
ParentRunID?: string,
}

export type ResumeSchedule = {
ParentRunID?: string,
}

//generated by mkunion

/**
* This function is used to remove $type field from State, so it can be understood by mkunion,
* that is assuming unions have one field in object, that is used to discriminate between variants.
Expand Down Expand Up @@ -434,9 +362,78 @@ export type DelayRun = {
DelayBySeconds?: number,
}

export type ApplyAwaitOptions = {
Timeout?: number,
//generated by mkunion

/**
* This function is used to remove $type field from Command, so it can be understood by mkunion,
* that is assuming unions have one field in object, that is used to discriminate between variants.
*/
export function dediscriminateCommand(x: Command): Command {
if (x["$type"] !== undefined) {
delete x["$type"]
return x
}
return x
}

/**
* This function is used to populate $type field in Command, so it can be used as discriminative switch statement
* @example https://www.typescriptlang.org/play#example/discriminate-types
*/
export function discriminateCommand(x: Command): Command {
if (x["$type"] === undefined) {
let keyx = Object.keys(x)
if (keyx.length === 1) {
x["$type"] = keyx[0] as any
}
}
return x
}

export type Command = {
// $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema
"$type"?: "workflow.Run",
"workflow.Run": Run
} | {
// $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema
"$type"?: "workflow.Callback",
"workflow.Callback": Callback
} | {
// $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema
"$type"?: "workflow.TryRecover",
"workflow.TryRecover": TryRecover
} | {
// $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema
"$type"?: "workflow.StopSchedule",
"workflow.StopSchedule": StopSchedule
} | {
// $type this is optional field, that is used to enable discriminative switch-statement in TypeScript, its not part of mkunion schema
"$type"?: "workflow.ResumeSchedule",
"workflow.ResumeSchedule": ResumeSchedule
}

export type Run = {
Flow?: Worflow,
Input?: schema.Schema,
RunOption?: RunOption,
}

export type Callback = {
CallbackID?: string,
Result?: schema.Schema,
}

export type TryRecover = {
}

export type StopSchedule = {
ParentRunID?: string,
}

export type ResumeSchedule = {
ParentRunID?: string,
}

export type ResumeOptions = {
Timeout?: number,
}
Expand All @@ -457,6 +454,17 @@ export type Execution = {
EndTime?: number,
Variables?: {[key: string]: any},
}
export type ApplyAwaitOptions = {
Timeout?: number,
}
export type FunctionInput = {
Name?: string,
CallbackID?: string,
Args?: any[],
}
export type FunctionOutput = {
Result?: schema.Schema,
}

//eslint-disable-next-line
import * as schema from './github_com_widmogrod_mkunion_x_schema'

0 comments on commit 1ffcef9

Please sign in to comment.