Add status to show the reasoning log

This commit is contained in:
Ettore Di Giacinto
2024-04-13 00:16:28 +02:00
parent 4b41653d00
commit 74b158e9f1
4 changed files with 144 additions and 18 deletions

View File

@@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"sync"
"time" "time"
"github.com/mudler/local-agent-framework/example/webui/connector" "github.com/mudler/local-agent-framework/example/webui/connector"
@@ -44,15 +45,34 @@ type AgentConfig struct {
} }
type AgentPool struct { type AgentPool struct {
sync.Mutex
file string file string
pooldir string pooldir string
pool AgentPoolData pool AgentPoolData
agents map[string]*Agent agents map[string]*Agent
managers map[string]Manager managers map[string]Manager
agentStatus map[string]*Status
apiURL, model string apiURL, model string
ragDB RAGDB ragDB RAGDB
} }
type Status struct {
results []ActionState
}
func (s *Status) addResult(result ActionState) {
// If we have more than 10 results, remove the oldest one
if len(s.results) > 10 {
s.results = s.results[1:]
}
s.results = append(s.results, result)
}
func (s *Status) Results() []ActionState {
return s.results
}
type AgentPoolData map[string]AgentConfig type AgentPoolData map[string]AgentConfig
func loadPoolFromFile(path string) (*AgentPoolData, error) { func loadPoolFromFile(path string) (*AgentPoolData, error) {
@@ -75,14 +95,15 @@ func NewAgentPool(model, apiURL, directory string, RagDB RAGDB) (*AgentPool, err
if _, err := os.Stat(poolfile); err != nil { if _, err := os.Stat(poolfile); err != nil {
// file does not exist, create a new pool // file does not exist, create a new pool
return &AgentPool{ return &AgentPool{
file: poolfile, file: poolfile,
pooldir: directory, pooldir: directory,
apiURL: apiURL, apiURL: apiURL,
model: model, model: model,
ragDB: RagDB, ragDB: RagDB,
agents: make(map[string]*Agent), agents: make(map[string]*Agent),
pool: make(map[string]AgentConfig), pool: make(map[string]AgentConfig),
managers: make(map[string]Manager), agentStatus: make(map[string]*Status),
managers: make(map[string]Manager),
}, nil }, nil
} }
@@ -91,14 +112,15 @@ func NewAgentPool(model, apiURL, directory string, RagDB RAGDB) (*AgentPool, err
return nil, err return nil, err
} }
return &AgentPool{ return &AgentPool{
file: poolfile, file: poolfile,
apiURL: apiURL, apiURL: apiURL,
pooldir: directory, pooldir: directory,
ragDB: RagDB, ragDB: RagDB,
model: model, model: model,
agents: make(map[string]*Agent), agents: make(map[string]*Agent),
managers: make(map[string]Manager), managers: make(map[string]Manager),
pool: *poolData, agentStatus: map[string]*Status{},
pool: *poolData,
}, nil }, nil
} }
@@ -224,6 +246,12 @@ func (a *AgentConfig) availableConnectors() []Connector {
return connectors return connectors
} }
func (a *AgentPool) GetStatusHistory(name string) *Status {
a.Lock()
defer a.Unlock()
return a.agentStatus[name]
}
func (a *AgentPool) startAgentWithConfig(name string, config *AgentConfig) error { func (a *AgentPool) startAgentWithConfig(name string, config *AgentConfig) error {
manager := NewManager(5) manager := NewManager(5)
ctx := context.Background() ctx := context.Background()
@@ -275,6 +303,13 @@ func (a *AgentPool) startAgentWithConfig(name string, config *AgentConfig) error
}), }),
WithSystemPrompt(config.SystemPrompt), WithSystemPrompt(config.SystemPrompt),
WithAgentResultCallback(func(state ActionState) { WithAgentResultCallback(func(state ActionState) {
a.Lock()
if _, ok := a.agentStatus[name]; !ok {
a.agentStatus[name] = &Status{}
}
a.agentStatus[name].addResult(state)
a.Unlock()
fmt.Println("Reasoning", state.Reasoning) fmt.Println("Reasoning", state.Reasoning)
text := fmt.Sprintf(`Reasoning: %s text := fmt.Sprintf(`Reasoning: %s

View File

@@ -6,6 +6,7 @@ import (
fiber "github.com/gofiber/fiber/v2" fiber "github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/filesystem" "github.com/gofiber/fiber/v2/middleware/filesystem"
"github.com/mudler/local-agent-framework/agent"
) )
func RegisterRoutes(webapp *fiber.App, pool *AgentPool, db *InMemoryDatabase, app *App) { func RegisterRoutes(webapp *fiber.App, pool *AgentPool, db *InMemoryDatabase, app *App) {
@@ -54,6 +55,19 @@ func RegisterRoutes(webapp *fiber.App, pool *AgentPool, db *InMemoryDatabase, ap
return nil return nil
}) })
webapp.Get("/status/:name", func(c *fiber.Ctx) error {
history := pool.GetStatusHistory(c.Params("name"))
if history == nil {
history = &Status{results: []agent.ActionState{}}
}
// reverse history
return c.Render("views/status", fiber.Map{
"Name": c.Params("name"),
"History": Reverse(history.Results()),
})
})
webapp.Get("/notify/:name", app.Notify(pool)) webapp.Get("/notify/:name", app.Notify(pool))
webapp.Post("/chat/:name", app.Chat(pool)) webapp.Post("/chat/:name", app.Chat(pool))
webapp.Post("/create", app.Create(pool)) webapp.Post("/create", app.Create(pool))
@@ -81,3 +95,15 @@ func randStringRunes(n int) string {
} }
return string(b) return string(b)
} }
func Reverse[T any](original []T) (reversed []T) {
reversed = make([]T, len(original))
copy(reversed, original)
for i := len(reversed)/2 - 1; i >= 0; i-- {
tmp := len(reversed) - 1 - i
reversed[i], reversed[tmp] = reversed[tmp], reversed[i]
}
return
}

View File

@@ -48,9 +48,13 @@
<tbody class="bg-gray-800 divide-y divide-gray-700"> <tbody class="bg-gray-800 divide-y divide-gray-700">
<!-- Dynamic agent rows go here --> <!-- Dynamic agent rows go here -->
{{ range .Agents }} {{ range .Agents }}
<tr hx-ext="sse" sse-connect="/sse/{{.}}"> <tr hx-ext="sse">
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-300">{{.}}</td> <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-300">{{.}}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-300"><div sse-swap="status" ></div></td> <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-300">
<a href="/status/{{.}}" class="text-indigo-500 hover:text-indigo-400">
<i class="fas fa-info"></i> Status
</a>
</td>
<td class="px-6 py-4 whitespace-nowrap text-center text-sm font-medium"> <td class="px-6 py-4 whitespace-nowrap text-center text-sm font-medium">
<a href="/talk/{{.}}" class="text-indigo-500 hover:text-indigo-400"> <a href="/talk/{{.}}" class="text-indigo-500 hover:text-indigo-400">
<i class="fas fa-comments"></i> Talk <i class="fas fa-comments"></i> Talk

View File

@@ -0,0 +1,61 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Smart Agent status</title>
{{template "views/partials/header"}}
<style>
body { overflow: hidden; }
.chat-container { height: 90vh; display: flex; flex-direction: column; }
.chat-messages { overflow-y: auto; flex-grow: 1; }
.htmx-indicator{
opacity:0;
transition: opacity 10ms ease-in;
}
.htmx-request .htmx-indicator{
opacity:1
}
/* Loader (https://cssloaders.github.io/) */
.loader {
width: 12px;
height: 12px;
border-radius: 50%;
display: block;
margin:15px auto;
position: relative;
color: #FFF;
box-sizing: border-box;
animation: animloader 2s linear infinite;
}
@keyframes animloader {
0% { box-shadow: 14px 0 0 -2px, 38px 0 0 -2px, -14px 0 0 -2px, -38px 0 0 -2px; }
25% { box-shadow: 14px 0 0 -2px, 38px 0 0 -2px, -14px 0 0 -2px, -38px 0 0 2px; }
50% { box-shadow: 14px 0 0 -2px, 38px 0 0 -2px, -14px 0 0 2px, -38px 0 0 -2px; }
75% { box-shadow: 14px 0 0 2px, 38px 0 0 -2px, -14px 0 0 -2px, -38px 0 0 -2px; }
100% { box-shadow: 14px 0 0 -2px, 38px 0 0 2px, -14px 0 0 -2px, -38px 0 0 -2px; }
}
</style>
</head>
<body class="bg-gray-900 p-4 text-white font-sans" hx-ext="sse" sse-connect="/sse/{{.Name}}">
{{template "views/partials/menu"}}
<div class="chat-container bg-gray-800 shadow-lg rounded-lg" >
<!-- Chat Header -->
<div class="border-b border-gray-700 p-4">
<h1 class="text-lg font-semibold">{{.Name}}</h1>
</div>
<!-- Chat Messages -->
<div class="chat-messages p-4">
<div sse-swap="status" hx-swap="afterbegin" id="status"></div>
{{ range .History }}
<!-- Agent Status Box -->
<div class="bg-gray-700 p-4">
<h2 class="text-sm font-semibold">Agent:</h2>
<div id="agentStatus" class="text-sm text-gray-300">
Result: {{.Result}} Action: {{.Action}} Params: {{.Params}} Reasoning: {{.Reasoning}}
</div>
</div>
{{end}}
</div>
</div>
</body>
</html>