Hello everyone. I’ve completed my simple chat stra...
# koog-agentic-framework
t
Hello everyone. I’ve completed my simple chat strategy. I’d like to get some reviews to improve it. Please let me know if I have any mistakes. Thanks.
Copy code
fun dashboardStrategy(
    userTools: UserTools,
    getWidgetsTools: GetDashboardDetailTool,
    dashboardTools: GetDashboardsTool
) = strategy<UserInput, String>("dashboard_strategy") {
    val dashboardKey = createStorageKey<List<Dashboard>>("dashboards_list")
        val setup by node<UserInput, String>("setup_strategy") { userInput ->
        llm.writeSession {
            appendPrompt {
                system {
                    xml {
                        tag("instructions") {
                            +"""
                                Today's date is ${userInput.currentDate}.
                                Users can ask multiple questions in this conversation.                                                              
                                IMPORTANT: You MUST use tools to fetch data before responding.
                                After gathering all necessary data, provide a summary response.
                                If the user wants to exit or end the conversation (says goodbye, exit, quit, bye, etc.), call the __exit__ tool." 
                                """.trimIndent()
                        }
                    }
                }
            }
        }
        userInput.message
    }

    val nodeExecuteTool by nodeExecuteTool("nodeExecuteTool")
    val nodeSendToolResult by nodeLLMSendToolResult("nodeSendToolResult")

    val clarifyUserQuestion by subgraphWithTask<String, ClassifiedDashboardRequest>(
        tools = userTools.asTools(),
        defineTask = { input ->
            xml {
                markdown {
                    h1("Requirements")
                    text("Ask the user what do they want to do.")
                    text("You should determine only GetDashboards or GetDashboardDetail.")
                    text("Consider the user's message. Determine whether the previous question is complete. If yes, ask the user for the next question.")
                }
                tag("initial_user_message") {
                    +input
                }
            }
        })

    val getDashboardSubGraph by subgraphWithTask<ClassifiedDashboardRequest, DashboardsResultConvert>(
        tools = listOf(dashboardTools) + userTools.asTools(),
        defineTask = { input ->
            xml {
                markdown {
                    h1("Requirements:")
                    h2("Tool usage guidelines")
                    bulleted {
                        item("Get dashboards list:") {
                            text("Use the get_dashboards tool to get dashboards list. This tool does not require any parameters, just trigger it.")
                            text("Always format the result before showing it to the user. Make it clean and user-friendly.")
                        }
                    }
                    h2("After fetching data, provide a clear summary of what you found.")
                }
                tag("user_input") {
                    +"""
                ${input.toMarkdownString()}
                """.trimIndent()
                }
            }
        })

    val clarifyGetDashboardDetailRequest by subgraphWithTask<ClassifiedDashboardRequest, GetWidgetsParams>(
        tools = userTools.asTools()
    ) { input ->
        val dashboardsAsString = storage.get(dashboardKey)
        xml {
            tag("instructions")
            markdown {
                h1("Requirements:")
                text("Ask the user which dashboard they want to get detailed information for. You only support dashboards that are saved in the list below.")
            }
            tag("dashboards") {
                dashboardsAsString?.forEach { it.toMarkdownString() }
            }
            tag("user_message:") {
                +input.toMarkdownString()
            }
        }
    }
    val getDashboardDetailSubGraph by subgraphWithTask<GetWidgetsParams, KoogGetWidgetResult>(
        tools = userTools.asTools() + getWidgetsTools
    ) { input ->
        llm.writeSession {
            appendPrompt {
                user {
                    xml {
                        markdown {
                            h1("Requirements:")
                            text("Get detail information for specific dashboard")
                            h2("Tool usage guidelines")
                            bulleted {
                                item("Get dashboards detail:") {
                                    item("Use get_widgets tool to get dashboard detail information.")
                                    item("You may need to call get_widgets multiple times for different dashboards.")
                                    item("Always format the result before showing it to the user. Make it clean and user-friendly.")
                                    item("If the widget includes data, try to analyze it and return an overview.")
                                    item("Don't ask for user information like IDs, as users may not be familiar with IDs.")
                                }
                            }
                            h2("After fetching data, provide a clear summary of what you found.")
                        }
                        tag("user_input") {
                            +"""
                            ${json.encodeToString(input)}
                            """.trimIndent()
                        }
                    }
                }
            }
            input.toMarkdownString()
        }
    }

    val saveDashboards by node<DashboardsResultConvert, DashboardsResultConvert> { dashboards ->
        storage.set(dashboardKey, dashboards.dashboard)
        dashboards
    }

    val showMessageToUser by node<String, Message.Response> { message ->
        llm.writeSession {
            appendPrompt {
                user(
                    "Don't chat with plain text! Call `showMessage` tolls instead"
                )
            }
            requestLLM()
        }
    }

    nodeStart then setup then clarifyUserQuestion

    edge(
        edgeIntermediate = clarifyUserQuestion forwardTo getDashboardSubGraph
                onCondition { it.requestType == RequestType.GetDashboards }
    )
    edge(
        edgeIntermediate = clarifyUserQuestion forwardTo clarifyGetDashboardDetailRequest
                onCondition { it.requestType == RequestType.GetDashboardDetail })

    edge(
        edgeIntermediate = clarifyUserQuestion forwardTo nodeExecuteTool
                onToolCall { true }
    )

    edge(edgeIntermediate = getDashboardSubGraph forwardTo saveDashboards)

    edge(
        edgeIntermediate = saveDashboards forwardTo showMessageToUser
                transformed { json.encodeToString(it) }
    )

    edge(edgeIntermediate = clarifyGetDashboardDetailRequest forwardTo getDashboardDetailSubGraph)

    edge(
        edgeIntermediate = getDashboardDetailSubGraph forwardTo showMessageToUser
                transformed { json.encodeToString(it) }
    )

    edge(edgeIntermediate = showMessageToUser forwardTo showMessageToUser onAssistantMessage { true }
    )

    edge(edgeIntermediate = showMessageToUser forwardTo nodeExecuteTool onToolCall { true }
    )

    edge(edgeIntermediate = nodeExecuteTool forwardTo nodeSendToolResult)

    edge(
        edgeIntermediate = nodeSendToolResult forwardTo clarifyUserQuestion
                transformed { it.content }
    )

    edge(
        edgeIntermediate = nodeSendToolResult forwardTo nodeFinish
                onToolCall { tc -> tc.tool == "__exit__" }
                transformed { "Chat finished" }
    )

}

class UserTools(private val showUserMessage: suspend (String) -> String) : ToolSet {
    @Tool
    @LLMDescription("Show user a message from the agent and wait for a response. Call this tool to ask the user something.")
    suspend fun showMessage(
        @LLMDescription("The message to show to the user.")
        message: String
    ): String {
        return showUserMessage(message)
    }
}
🧵 1
r
Seems pretty clear and ideomatic. I don't see why you should wrap markdown in xml, but this not critical I guess 🙂
👍 1
And yea, you can combine llmRequestMutliple + nodeSendMultipleToolResult(checkout
koogs
predefined
singleRunStrategy
), maybe this can improve performance
t
Thanks for your feedback @rcd27
🤝 1
r
I got the point in wrapping markdown into xml
And I love this
onToolCall { tc -> tc.tool == "__exit__"}
!
t
I just copied from another Koog example. 😄
r
This doesn't make it less prettier 🙂
t
Can you share the point of wrapping Markdown in XML?
r
The point is that you split
user_input
,
system_messages
and so on from "pretty formed" requirements for a particular task.
thank you color 1
I think this is more for a human than for LLMs which can read both