Compare commits
3 Commits
chore/para
...
readme-obs
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
92688cf498 | ||
|
|
ce997d2425 | ||
|
|
56cd0e05ca |
@@ -2,7 +2,7 @@
|
||||
<img src="./webui/react-ui/public/logo_1.png" alt="LocalAGI Logo" width="220"/>
|
||||
</p>
|
||||
|
||||
<h3 align="center"><em>Your AI. Your Hardware. Your Rules.</em></h3>
|
||||
<h3 align="center"><em>Your AI. Your Hardware. Your Rules</em></h3>
|
||||
|
||||
<div align="center">
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
|
||||
</div>
|
||||
|
||||
We empower you building AI Agents that you can run locally, without coding.
|
||||
Create customizable AI assistants, automations, chat bots and agents that run 100% locally. No need for agentic Python libraries or cloud service keys, just bring your GPU (or even just CPU) and a web browser.
|
||||
|
||||
**LocalAGI** is a powerful, self-hostable AI Agent platform designed for maximum privacy and flexibility. A complete drop-in replacement for OpenAI's Responses APIs with advanced agentic capabilities. No clouds. No data leaks. Just pure local AI that works on consumer-grade hardware (CPU and GPU).
|
||||
**LocalAGI** is a powerful, self-hostable AI Agent platform that allows you to design AI automations without writing code. A complete drop-in replacement for OpenAI's Responses APIs with advanced agentic capabilities. No clouds. No data leaks. Just pure local AI that works on consumer-grade hardware (CPU and GPU).
|
||||
|
||||
## 🛡️ Take Back Your Privacy
|
||||
|
||||
@@ -37,6 +37,7 @@ LocalAGI ensures your data stays exactly where you want it—on your hardware. N
|
||||
- 🖼 **Multimodal Support**: Ready for vision, text, and more.
|
||||
- 🔧 **Extensible Custom Actions**: Easily script dynamic agent behaviors in Go (interpreted, no compilation!).
|
||||
- 🛠 **Fully Customizable Models**: Use your own models or integrate seamlessly with [LocalAI](https://github.com/mudler/LocalAI).
|
||||
- 📊 **Observability**: Monitor agent status and view detailed observable updates in real-time.
|
||||
|
||||
## 🛠️ Quickstart
|
||||
|
||||
@@ -194,6 +195,8 @@ LocalAGI is part of the powerful Local family of privacy-focused AI tools:
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
|
||||
### Connectors Ready-to-Go
|
||||
|
||||
|
||||
@@ -990,7 +990,6 @@ func (a *Agent) periodicallyRun(timer *time.Timer) {
|
||||
}
|
||||
|
||||
func (a *Agent) Run() error {
|
||||
|
||||
a.startNewConversationsConsumer()
|
||||
xlog.Debug("Agent is now running", "agent", a.Character.Name)
|
||||
// The agent run does two things:
|
||||
@@ -1013,7 +1012,12 @@ func (a *Agent) Run() error {
|
||||
var muErr sync.Mutex
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for i := 0; i <= a.options.parallelJobs; i++ {
|
||||
parallelJobs := a.options.parallelJobs
|
||||
if a.options.parallelJobs == 0 {
|
||||
parallelJobs = 1
|
||||
}
|
||||
|
||||
for i := 0; i < parallelJobs; i++ {
|
||||
xlog.Debug("Starting agent worker", "worker", i)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
|
||||
@@ -36,7 +36,8 @@ func NewSSEObserver(agent string, manager sse.Manager) *SSEObserver {
|
||||
}
|
||||
|
||||
func (s *SSEObserver) NewObservable() *types.Observable {
|
||||
id := atomic.AddInt32(&s.maxID, 1)
|
||||
id := atomic.AddInt32(&s.maxID, 1)
|
||||
|
||||
return &types.Observable{
|
||||
ID: id - 1,
|
||||
Agent: s.agent,
|
||||
|
||||
@@ -64,6 +64,7 @@ func (o *options) SeparatedMultimodalModel() bool {
|
||||
|
||||
func defaultOptions() *options {
|
||||
return &options{
|
||||
parallelJobs: 1,
|
||||
periodicRuns: 15 * time.Minute,
|
||||
LLMAPI: llmOptions{
|
||||
APIURL: "http://localhost:8080",
|
||||
|
||||
@@ -25,6 +25,7 @@ var _ = Describe("Agent test", func() {
|
||||
agent, err = New(
|
||||
WithLLMAPIURL(apiURL),
|
||||
WithModel(testModel),
|
||||
WithTimeout("10m"),
|
||||
WithRandomIdentity(),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
@@ -166,7 +166,56 @@ func (a *AgentPool) CreateAgent(name string, agentConfig *AgentConfig) error {
|
||||
}
|
||||
}(a.pool[name])
|
||||
|
||||
return a.startAgentWithConfig(name, agentConfig)
|
||||
return a.startAgentWithConfig(name, agentConfig, nil)
|
||||
}
|
||||
|
||||
func (a *AgentPool) RecreateAgent(name string, agentConfig *AgentConfig) error {
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
|
||||
oldAgent := a.agents[name]
|
||||
var o *types.Observable
|
||||
obs := oldAgent.Observer()
|
||||
if obs != nil {
|
||||
o = obs.NewObservable()
|
||||
o.Name = "Restarting Agent"
|
||||
o.Icon = "sync"
|
||||
o.Creation = &types.Creation{}
|
||||
obs.Update(*o)
|
||||
}
|
||||
|
||||
stateFile, characterFile := a.stateFiles(name)
|
||||
|
||||
os.Remove(stateFile)
|
||||
os.Remove(characterFile)
|
||||
|
||||
oldAgent.Stop()
|
||||
|
||||
a.pool[name] = *agentConfig
|
||||
delete(a.agents, name)
|
||||
|
||||
if err := a.save(); err != nil {
|
||||
if obs != nil {
|
||||
o.Completion = &types.Completion{Error: err.Error()}
|
||||
obs.Update(*o)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if err := a.startAgentWithConfig(name, agentConfig, obs); err != nil {
|
||||
if obs != nil {
|
||||
o.Completion = &types.Completion{Error: err.Error()}
|
||||
obs.Update(*o)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if obs != nil {
|
||||
o.Completion = &types.Completion{}
|
||||
obs.Update(*o)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createAgentAvatar(APIURL, APIKey, model, imageModel, avatarDir string, agent AgentConfig) error {
|
||||
@@ -268,8 +317,13 @@ func (a *AgentPool) GetStatusHistory(name string) *Status {
|
||||
return a.agentStatus[name]
|
||||
}
|
||||
|
||||
func (a *AgentPool) startAgentWithConfig(name string, config *AgentConfig) error {
|
||||
manager := sse.NewManager(5)
|
||||
func (a *AgentPool) startAgentWithConfig(name string, config *AgentConfig, obs Observer) error {
|
||||
var manager sse.Manager
|
||||
if m, ok := a.managers[name]; ok {
|
||||
manager = m
|
||||
} else {
|
||||
manager = sse.NewManager(5)
|
||||
}
|
||||
ctx := context.Background()
|
||||
model := a.defaultModel
|
||||
multimodalModel := a.defaultMultimodalModel
|
||||
@@ -331,6 +385,10 @@ func (a *AgentPool) startAgentWithConfig(name string, config *AgentConfig) error
|
||||
// dynamicPrompts = append(dynamicPrompts, p.ToMap())
|
||||
// }
|
||||
|
||||
if obs == nil {
|
||||
obs = NewSSEObserver(name, manager)
|
||||
}
|
||||
|
||||
opts := []Option{
|
||||
WithModel(model),
|
||||
WithLLMAPIURL(a.apiURL),
|
||||
@@ -407,7 +465,7 @@ func (a *AgentPool) startAgentWithConfig(name string, config *AgentConfig) error
|
||||
c.AgentResultCallback()(state)
|
||||
}
|
||||
}),
|
||||
WithObserver(NewSSEObserver(name, manager)),
|
||||
WithObserver(obs),
|
||||
}
|
||||
|
||||
if config.HUD {
|
||||
@@ -514,7 +572,7 @@ func (a *AgentPool) StartAll() error {
|
||||
if a.agents[name] != nil { // Agent already started
|
||||
continue
|
||||
}
|
||||
if err := a.startAgentWithConfig(name, &config); err != nil {
|
||||
if err := a.startAgentWithConfig(name, &config, nil); err != nil {
|
||||
xlog.Error("Failed to start agent", "name", name, "error", err)
|
||||
}
|
||||
}
|
||||
@@ -552,7 +610,7 @@ func (a *AgentPool) Start(name string) error {
|
||||
return nil
|
||||
}
|
||||
if config, ok := a.pool[name]; ok {
|
||||
return a.startAgentWithConfig(name, &config)
|
||||
return a.startAgentWithConfig(name, &config, nil)
|
||||
}
|
||||
|
||||
return fmt.Errorf("agent %s not found", name)
|
||||
|
||||
@@ -77,8 +77,9 @@ func (i *IRC) Start(a *agent.Agent) {
|
||||
}
|
||||
i.conn.UseTLS = false
|
||||
i.conn.AddCallback("001", func(e *irc.Event) {
|
||||
xlog.Info("Connected to IRC server", "server", i.server)
|
||||
xlog.Info("Connected to IRC server", "server", i.server, "arguments", e.Arguments)
|
||||
i.conn.Join(i.channel)
|
||||
i.nickname = e.Arguments[0]
|
||||
xlog.Info("Joined channel", "channel", i.channel)
|
||||
})
|
||||
|
||||
@@ -207,6 +208,13 @@ func (i *IRC) Start(a *agent.Agent) {
|
||||
|
||||
// Start the IRC client in a goroutine
|
||||
go i.conn.Loop()
|
||||
go func() {
|
||||
select {
|
||||
case <-a.Context().Done():
|
||||
i.conn.Quit()
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// IRCConfigMeta returns the metadata for IRC connector configuration fields
|
||||
|
||||
12
webui/app.go
12
webui/app.go
@@ -176,17 +176,7 @@ func (a *App) UpdateAgentConfig(pool *state.AgentPool) func(c *fiber.Ctx) error
|
||||
return errorJSONMessage(c, err.Error())
|
||||
}
|
||||
|
||||
// Remove the agent first
|
||||
if err := pool.Remove(agentName); err != nil {
|
||||
return errorJSONMessage(c, "Error removing agent: "+err.Error())
|
||||
}
|
||||
|
||||
// Create agent with new config
|
||||
if err := pool.CreateAgent(agentName, &newConfig); err != nil {
|
||||
// Try to restore the old configuration if update fails
|
||||
if restoreErr := pool.CreateAgent(agentName, oldConfig); restoreErr != nil {
|
||||
return errorJSONMessage(c, fmt.Sprintf("Failed to update agent and restore failed: %v, %v", err, restoreErr))
|
||||
}
|
||||
if err := pool.RecreateAgent(agentName, &newConfig); err != nil {
|
||||
return errorJSONMessage(c, "Error updating agent: "+err.Error())
|
||||
}
|
||||
|
||||
|
||||
@@ -99,8 +99,17 @@ function AgentStatus() {
|
||||
creation: data.creation,
|
||||
progress: data.progress,
|
||||
completion: data.completion,
|
||||
// children are always built client-side
|
||||
};
|
||||
// Events can be received out of order
|
||||
if (data.creation)
|
||||
updated.creation = data.creation;
|
||||
if (data.completion)
|
||||
updated.completion = data.completion;
|
||||
if (data.parent_id && !prevMap[data.parent_id])
|
||||
prevMap[data.parent_id] = {
|
||||
id: data.parent_id,
|
||||
name: "unknown",
|
||||
};
|
||||
const newMap = { ...prevMap, [data.id]: updated };
|
||||
setObservableTree(buildObservableTree(newMap));
|
||||
return newMap;
|
||||
|
||||
Reference in New Issue
Block a user