Compare commits
1 Commits
readme-obs
...
browser
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb3eb33e6e |
2
Makefile
2
Makefile
@@ -9,7 +9,7 @@ cleanup-tests:
|
|||||||
docker compose down
|
docker compose down
|
||||||
|
|
||||||
tests: prepare-tests
|
tests: prepare-tests
|
||||||
LOCALAGI_MODEL="gemma-3-12b-it-qat" LOCALAI_API_URL="http://localhost:8081" LOCALAGI_API_URL="http://localhost:8080" $(GOCMD) run github.com/onsi/ginkgo/v2/ginkgo --fail-fast -v -r ./...
|
LOCALAGI_MODEL="arcee-agent" LOCALAI_API_URL="http://localhost:8081" LOCALAGI_API_URL="http://localhost:8080" $(GOCMD) run github.com/onsi/ginkgo/v2/ginkgo --fail-fast -v -r ./...
|
||||||
|
|
||||||
run-nokb:
|
run-nokb:
|
||||||
$(MAKE) run KBDISABLEINDEX=true
|
$(MAKE) run KBDISABLEINDEX=true
|
||||||
|
|||||||
15
README.md
15
README.md
@@ -2,7 +2,7 @@
|
|||||||
<img src="./webui/react-ui/public/logo_1.png" alt="LocalAGI Logo" width="220"/>
|
<img src="./webui/react-ui/public/logo_1.png" alt="LocalAGI Logo" width="220"/>
|
||||||
</p>
|
</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">
|
<div align="center">
|
||||||
|
|
||||||
@@ -13,9 +13,9 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
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.
|
We empower you building AI Agents that you can run locally, without coding.
|
||||||
|
|
||||||
**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).
|
**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).
|
||||||
|
|
||||||
## 🛡️ Take Back Your Privacy
|
## 🛡️ Take Back Your Privacy
|
||||||
|
|
||||||
@@ -37,7 +37,6 @@ LocalAGI ensures your data stays exactly where you want it—on your hardware. N
|
|||||||
- 🖼 **Multimodal Support**: Ready for vision, text, and more.
|
- 🖼 **Multimodal Support**: Ready for vision, text, and more.
|
||||||
- 🔧 **Extensible Custom Actions**: Easily script dynamic agent behaviors in Go (interpreted, no compilation!).
|
- 🔧 **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).
|
- 🛠 **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
|
## 🛠️ Quickstart
|
||||||
|
|
||||||
@@ -115,7 +114,7 @@ LocalAGI supports multiple hardware configurations through Docker Compose profil
|
|||||||
- Supports text, multimodal, and image generation models
|
- Supports text, multimodal, and image generation models
|
||||||
- Run with: `docker compose -f docker-compose.nvidia.yaml up`
|
- Run with: `docker compose -f docker-compose.nvidia.yaml up`
|
||||||
- Default models:
|
- Default models:
|
||||||
- Text: `gemma-3-12b-it-qat`
|
- Text: `arcee-agent`
|
||||||
- Multimodal: `minicpm-v-2_6`
|
- Multimodal: `minicpm-v-2_6`
|
||||||
- Image: `sd-1.5-ggml`
|
- Image: `sd-1.5-ggml`
|
||||||
- Environment variables:
|
- Environment variables:
|
||||||
@@ -131,7 +130,7 @@ LocalAGI supports multiple hardware configurations through Docker Compose profil
|
|||||||
- Supports text, multimodal, and image generation models
|
- Supports text, multimodal, and image generation models
|
||||||
- Run with: `docker compose -f docker-compose.intel.yaml up`
|
- Run with: `docker compose -f docker-compose.intel.yaml up`
|
||||||
- Default models:
|
- Default models:
|
||||||
- Text: `gemma-3-12b-it-qat`
|
- Text: `arcee-agent`
|
||||||
- Multimodal: `minicpm-v-2_6`
|
- Multimodal: `minicpm-v-2_6`
|
||||||
- Image: `sd-1.5-ggml`
|
- Image: `sd-1.5-ggml`
|
||||||
- Environment variables:
|
- Environment variables:
|
||||||
@@ -162,7 +161,7 @@ docker compose -f docker-compose.intel.yaml up
|
|||||||
```
|
```
|
||||||
|
|
||||||
If no models are specified, it will use the defaults:
|
If no models are specified, it will use the defaults:
|
||||||
- Text model: `gemma-3-12b-it-qat`
|
- Text model: `arcee-agent`
|
||||||
- Multimodal model: `minicpm-v-2_6`
|
- Multimodal model: `minicpm-v-2_6`
|
||||||
- Image model: `sd-1.5-ggml`
|
- Image model: `sd-1.5-ggml`
|
||||||
|
|
||||||
@@ -195,8 +194,6 @@ LocalAGI is part of the powerful Local family of privacy-focused AI tools:
|
|||||||

|

|
||||||

|

|
||||||

|

|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
### Connectors Ready-to-Go
|
### Connectors Ready-to-Go
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package agent
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -990,6 +989,7 @@ func (a *Agent) periodicallyRun(timer *time.Timer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *Agent) Run() error {
|
func (a *Agent) Run() error {
|
||||||
|
|
||||||
a.startNewConversationsConsumer()
|
a.startNewConversationsConsumer()
|
||||||
xlog.Debug("Agent is now running", "agent", a.Character.Name)
|
xlog.Debug("Agent is now running", "agent", a.Character.Name)
|
||||||
// The agent run does two things:
|
// The agent run does two things:
|
||||||
@@ -1004,68 +1004,36 @@ func (a *Agent) Run() error {
|
|||||||
|
|
||||||
// Expose a REST API to interact with the agent to ask it things
|
// Expose a REST API to interact with the agent to ask it things
|
||||||
|
|
||||||
|
//todoTimer := time.NewTicker(a.options.periodicRuns)
|
||||||
timer := time.NewTimer(a.options.periodicRuns)
|
timer := time.NewTimer(a.options.periodicRuns)
|
||||||
|
|
||||||
// we fire the periodicalRunner only once.
|
|
||||||
go a.periodicalRunRunner(timer)
|
|
||||||
var errs []error
|
|
||||||
var muErr sync.Mutex
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
|
|
||||||
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() {
|
|
||||||
e := a.run(timer)
|
|
||||||
muErr.Lock()
|
|
||||||
errs = append(errs, e)
|
|
||||||
muErr.Unlock()
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
return errors.Join(errs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Agent) run(timer *time.Timer) error {
|
|
||||||
for {
|
for {
|
||||||
xlog.Debug("Agent is now waiting for a new job", "agent", a.Character.Name)
|
xlog.Debug("Agent is now waiting for a new job", "agent", a.Character.Name)
|
||||||
select {
|
select {
|
||||||
case job := <-a.jobQueue:
|
case job := <-a.jobQueue:
|
||||||
if !timer.Stop() {
|
a.loop(timer, job)
|
||||||
<-timer.C
|
|
||||||
}
|
|
||||||
xlog.Debug("Agent is consuming a job", "agent", a.Character.Name, "job", job)
|
|
||||||
a.consumeJob(job, UserRole)
|
|
||||||
timer.Reset(a.options.periodicRuns)
|
|
||||||
case <-a.context.Done():
|
case <-a.context.Done():
|
||||||
// Agent has been canceled, return error
|
// Agent has been canceled, return error
|
||||||
xlog.Warn("Agent has been canceled", "agent", a.Character.Name)
|
xlog.Warn("Agent has been canceled", "agent", a.Character.Name)
|
||||||
return ErrContextCanceled
|
return ErrContextCanceled
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Agent) periodicalRunRunner(timer *time.Timer) {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-a.context.Done():
|
|
||||||
// Agent has been canceled, return error
|
|
||||||
xlog.Warn("periodicalRunner has been canceled", "agent", a.Character.Name)
|
|
||||||
return
|
|
||||||
case <-timer.C:
|
case <-timer.C:
|
||||||
a.periodicallyRun(timer)
|
a.periodicallyRun(timer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *Agent) loop(timer *time.Timer, job *types.Job) {
|
||||||
|
// Remember always to reset the timer - if we don't the agent will stop..
|
||||||
|
defer timer.Reset(a.options.periodicRuns)
|
||||||
|
// Consume the job and generate a response
|
||||||
|
// TODO: Give a short-term memory to the agent
|
||||||
|
// stop and drain the timer
|
||||||
|
if !timer.Stop() {
|
||||||
|
<-timer.C
|
||||||
|
}
|
||||||
|
xlog.Debug("Agent is consuming a job", "agent", a.Character.Name, "job", job)
|
||||||
|
a.consumeJob(job, UserRole)
|
||||||
|
}
|
||||||
|
|
||||||
func (a *Agent) Observer() Observer {
|
func (a *Agent) Observer() Observer {
|
||||||
return a.observer
|
return a.observer
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -226,10 +226,7 @@ var _ = Describe("Agent test", func() {
|
|||||||
WithLLMAPIKey(apiKeyURL),
|
WithLLMAPIKey(apiKeyURL),
|
||||||
WithTimeout("10m"),
|
WithTimeout("10m"),
|
||||||
WithActions(
|
WithActions(
|
||||||
&TestAction{response: map[string]string{
|
actions.NewSearch(map[string]string{}),
|
||||||
"boston": testActionResult,
|
|
||||||
"milan": testActionResult2,
|
|
||||||
}},
|
|
||||||
),
|
),
|
||||||
EnablePlanning,
|
EnablePlanning,
|
||||||
EnableForceReasoning,
|
EnableForceReasoning,
|
||||||
@@ -241,21 +238,18 @@ var _ = Describe("Agent test", func() {
|
|||||||
defer agent.Stop()
|
defer agent.Stop()
|
||||||
|
|
||||||
result := agent.Ask(
|
result := agent.Ask(
|
||||||
types.WithText("Use the plan tool to do two actions in sequence: search for the weather in boston and search for the weather in milan"),
|
types.WithText("Thoroughly plan a trip to San Francisco from Venice, Italy; check flight times, visa requirements and whether electrical items are allowed in cabin luggage."),
|
||||||
)
|
)
|
||||||
Expect(len(result.State)).To(BeNumerically(">", 1))
|
Expect(len(result.State)).To(BeNumerically(">", 1))
|
||||||
|
|
||||||
actionsExecuted := []string{}
|
actionsExecuted := []string{}
|
||||||
actionResults := []string{}
|
|
||||||
for _, r := range result.State {
|
for _, r := range result.State {
|
||||||
xlog.Info(r.Result)
|
xlog.Info(r.Result)
|
||||||
actionsExecuted = append(actionsExecuted, r.Action.Definition().Name.String())
|
actionsExecuted = append(actionsExecuted, r.Action.Definition().Name.String())
|
||||||
actionResults = append(actionResults, r.ActionResult.Result)
|
|
||||||
}
|
}
|
||||||
Expect(actionsExecuted).To(ContainElement("get_weather"), fmt.Sprint(result))
|
Expect(actionsExecuted).To(ContainElement("search_internet"), fmt.Sprint(result))
|
||||||
Expect(actionsExecuted).To(ContainElement("plan"), fmt.Sprint(result))
|
Expect(actionsExecuted).To(ContainElement("plan"), fmt.Sprint(result))
|
||||||
Expect(actionResults).To(ContainElement(testActionResult), fmt.Sprint(result))
|
|
||||||
Expect(actionResults).To(ContainElement(testActionResult2), fmt.Sprint(result))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Can initiate conversations", func() {
|
It("Can initiate conversations", func() {
|
||||||
|
|||||||
@@ -36,8 +36,7 @@ func NewSSEObserver(agent string, manager sse.Manager) *SSEObserver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *SSEObserver) NewObservable() *types.Observable {
|
func (s *SSEObserver) NewObservable() *types.Observable {
|
||||||
id := atomic.AddInt32(&s.maxID, 1)
|
id := atomic.AddInt32(&s.maxID, 1)
|
||||||
|
|
||||||
return &types.Observable{
|
return &types.Observable{
|
||||||
ID: id - 1,
|
ID: id - 1,
|
||||||
Agent: s.agent,
|
Agent: s.agent,
|
||||||
|
|||||||
@@ -54,8 +54,7 @@ type options struct {
|
|||||||
|
|
||||||
newConversationsSubscribers []func(openai.ChatCompletionMessage)
|
newConversationsSubscribers []func(openai.ChatCompletionMessage)
|
||||||
|
|
||||||
observer Observer
|
observer Observer
|
||||||
parallelJobs int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *options) SeparatedMultimodalModel() bool {
|
func (o *options) SeparatedMultimodalModel() bool {
|
||||||
@@ -64,7 +63,6 @@ func (o *options) SeparatedMultimodalModel() bool {
|
|||||||
|
|
||||||
func defaultOptions() *options {
|
func defaultOptions() *options {
|
||||||
return &options{
|
return &options{
|
||||||
parallelJobs: 1,
|
|
||||||
periodicRuns: 15 * time.Minute,
|
periodicRuns: 15 * time.Minute,
|
||||||
LLMAPI: llmOptions{
|
LLMAPI: llmOptions{
|
||||||
APIURL: "http://localhost:8080",
|
APIURL: "http://localhost:8080",
|
||||||
@@ -140,13 +138,6 @@ func EnableKnowledgeBaseWithResults(results int) Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithParallelJobs(jobs int) Option {
|
|
||||||
return func(o *options) error {
|
|
||||||
o.parallelJobs = jobs
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithNewConversationSubscriber(sub func(openai.ChatCompletionMessage)) Option {
|
func WithNewConversationSubscriber(sub func(openai.ChatCompletionMessage)) Option {
|
||||||
return func(o *options) error {
|
return func(o *options) error {
|
||||||
o.newConversationsSubscribers = append(o.newConversationsSubscribers, sub)
|
o.newConversationsSubscribers = append(o.newConversationsSubscribers, sub)
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ var _ = Describe("Agent test", func() {
|
|||||||
agent, err = New(
|
agent, err = New(
|
||||||
WithLLMAPIURL(apiURL),
|
WithLLMAPIURL(apiURL),
|
||||||
WithModel(testModel),
|
WithModel(testModel),
|
||||||
WithTimeout("10m"),
|
|
||||||
WithRandomIdentity(),
|
WithRandomIdentity(),
|
||||||
)
|
)
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ type AgentConfig struct {
|
|||||||
SystemPrompt string `json:"system_prompt" form:"system_prompt"`
|
SystemPrompt string `json:"system_prompt" form:"system_prompt"`
|
||||||
LongTermMemory bool `json:"long_term_memory" form:"long_term_memory"`
|
LongTermMemory bool `json:"long_term_memory" form:"long_term_memory"`
|
||||||
SummaryLongTermMemory bool `json:"summary_long_term_memory" form:"summary_long_term_memory"`
|
SummaryLongTermMemory bool `json:"summary_long_term_memory" form:"summary_long_term_memory"`
|
||||||
ParallelJobs int `json:"parallel_jobs" form:"parallel_jobs"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AgentConfigMeta struct {
|
type AgentConfigMeta struct {
|
||||||
@@ -261,16 +260,6 @@ func NewAgentConfigMeta(
|
|||||||
Step: 1,
|
Step: 1,
|
||||||
Tags: config.Tags{Section: "AdvancedSettings"},
|
Tags: config.Tags{Section: "AdvancedSettings"},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
Name: "parallel_jobs",
|
|
||||||
Label: "Parallel Jobs",
|
|
||||||
Type: "number",
|
|
||||||
DefaultValue: 5,
|
|
||||||
Min: 1,
|
|
||||||
Step: 1,
|
|
||||||
HelpText: "Number of concurrent tasks that can run in parallel",
|
|
||||||
Tags: config.Tags{Section: "AdvancedSettings"},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
MCPServers: []config.Field{
|
MCPServers: []config.Field{
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -166,56 +166,7 @@ func (a *AgentPool) CreateAgent(name string, agentConfig *AgentConfig) error {
|
|||||||
}
|
}
|
||||||
}(a.pool[name])
|
}(a.pool[name])
|
||||||
|
|
||||||
return a.startAgentWithConfig(name, agentConfig, nil)
|
return a.startAgentWithConfig(name, agentConfig)
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
func createAgentAvatar(APIURL, APIKey, model, imageModel, avatarDir string, agent AgentConfig) error {
|
||||||
@@ -317,13 +268,8 @@ func (a *AgentPool) GetStatusHistory(name string) *Status {
|
|||||||
return a.agentStatus[name]
|
return a.agentStatus[name]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AgentPool) startAgentWithConfig(name string, config *AgentConfig, obs Observer) error {
|
func (a *AgentPool) startAgentWithConfig(name string, config *AgentConfig) error {
|
||||||
var manager sse.Manager
|
manager := sse.NewManager(5)
|
||||||
if m, ok := a.managers[name]; ok {
|
|
||||||
manager = m
|
|
||||||
} else {
|
|
||||||
manager = sse.NewManager(5)
|
|
||||||
}
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
model := a.defaultModel
|
model := a.defaultModel
|
||||||
multimodalModel := a.defaultMultimodalModel
|
multimodalModel := a.defaultMultimodalModel
|
||||||
@@ -385,10 +331,6 @@ func (a *AgentPool) startAgentWithConfig(name string, config *AgentConfig, obs O
|
|||||||
// dynamicPrompts = append(dynamicPrompts, p.ToMap())
|
// dynamicPrompts = append(dynamicPrompts, p.ToMap())
|
||||||
// }
|
// }
|
||||||
|
|
||||||
if obs == nil {
|
|
||||||
obs = NewSSEObserver(name, manager)
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := []Option{
|
opts := []Option{
|
||||||
WithModel(model),
|
WithModel(model),
|
||||||
WithLLMAPIURL(a.apiURL),
|
WithLLMAPIURL(a.apiURL),
|
||||||
@@ -465,7 +407,7 @@ func (a *AgentPool) startAgentWithConfig(name string, config *AgentConfig, obs O
|
|||||||
c.AgentResultCallback()(state)
|
c.AgentResultCallback()(state)
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
WithObserver(obs),
|
WithObserver(NewSSEObserver(name, manager)),
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.HUD {
|
if config.HUD {
|
||||||
@@ -524,10 +466,6 @@ func (a *AgentPool) startAgentWithConfig(name string, config *AgentConfig, obs O
|
|||||||
opts = append(opts, WithLoopDetectionSteps(config.LoopDetectionSteps))
|
opts = append(opts, WithLoopDetectionSteps(config.LoopDetectionSteps))
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.ParallelJobs > 0 {
|
|
||||||
opts = append(opts, WithParallelJobs(config.ParallelJobs))
|
|
||||||
}
|
|
||||||
|
|
||||||
xlog.Info("Starting agent", "name", name, "config", config)
|
xlog.Info("Starting agent", "name", name, "config", config)
|
||||||
|
|
||||||
agent, err := New(opts...)
|
agent, err := New(opts...)
|
||||||
@@ -572,7 +510,7 @@ func (a *AgentPool) StartAll() error {
|
|||||||
if a.agents[name] != nil { // Agent already started
|
if a.agents[name] != nil { // Agent already started
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := a.startAgentWithConfig(name, &config, nil); err != nil {
|
if err := a.startAgentWithConfig(name, &config); err != nil {
|
||||||
xlog.Error("Failed to start agent", "name", name, "error", err)
|
xlog.Error("Failed to start agent", "name", name, "error", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -610,7 +548,7 @@ func (a *AgentPool) Start(name string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if config, ok := a.pool[name]; ok {
|
if config, ok := a.pool[name]; ok {
|
||||||
return a.startAgentWithConfig(name, &config, nil)
|
return a.startAgentWithConfig(name, &config)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("agent %s not found", name)
|
return fmt.Errorf("agent %s not found", name)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ services:
|
|||||||
# Image list (dockerhub): https://hub.docker.com/r/localai/localai
|
# Image list (dockerhub): https://hub.docker.com/r/localai/localai
|
||||||
image: localai/localai:master-ffmpeg-core
|
image: localai/localai:master-ffmpeg-core
|
||||||
command:
|
command:
|
||||||
- ${MODEL_NAME:-gemma-3-12b-it-qat}
|
- ${MODEL_NAME:-arcee-agent}
|
||||||
- ${MULTIMODAL_MODEL:-minicpm-v-2_6}
|
- ${MULTIMODAL_MODEL:-minicpm-v-2_6}
|
||||||
- ${IMAGE_MODEL:-sd-1.5-ggml}
|
- ${IMAGE_MODEL:-sd-1.5-ggml}
|
||||||
- granite-embedding-107m-multilingual
|
- granite-embedding-107m-multilingual
|
||||||
@@ -59,7 +59,7 @@ services:
|
|||||||
- 8080:3000
|
- 8080:3000
|
||||||
#image: quay.io/mudler/localagi:master
|
#image: quay.io/mudler/localagi:master
|
||||||
environment:
|
environment:
|
||||||
- LOCALAGI_MODEL=${MODEL_NAME:-gemma-3-12b-it-qat}
|
- LOCALAGI_MODEL=${MODEL_NAME:-arcee-agent}
|
||||||
- LOCALAGI_MULTIMODAL_MODEL=${MULTIMODAL_MODEL:-minicpm-v-2_6}
|
- LOCALAGI_MULTIMODAL_MODEL=${MULTIMODAL_MODEL:-minicpm-v-2_6}
|
||||||
- LOCALAGI_IMAGE_MODEL=${IMAGE_MODEL:-sd-1.5-ggml}
|
- LOCALAGI_IMAGE_MODEL=${IMAGE_MODEL:-sd-1.5-ggml}
|
||||||
- LOCALAGI_LLM_API_URL=http://localai:8080
|
- LOCALAGI_LLM_API_URL=http://localai:8080
|
||||||
|
|||||||
@@ -77,9 +77,8 @@ func (i *IRC) Start(a *agent.Agent) {
|
|||||||
}
|
}
|
||||||
i.conn.UseTLS = false
|
i.conn.UseTLS = false
|
||||||
i.conn.AddCallback("001", func(e *irc.Event) {
|
i.conn.AddCallback("001", func(e *irc.Event) {
|
||||||
xlog.Info("Connected to IRC server", "server", i.server, "arguments", e.Arguments)
|
xlog.Info("Connected to IRC server", "server", i.server)
|
||||||
i.conn.Join(i.channel)
|
i.conn.Join(i.channel)
|
||||||
i.nickname = e.Arguments[0]
|
|
||||||
xlog.Info("Joined channel", "channel", i.channel)
|
xlog.Info("Joined channel", "channel", i.channel)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -208,13 +207,6 @@ func (i *IRC) Start(a *agent.Agent) {
|
|||||||
|
|
||||||
// Start the IRC client in a goroutine
|
// Start the IRC client in a goroutine
|
||||||
go i.conn.Loop()
|
go i.conn.Loop()
|
||||||
go func() {
|
|
||||||
select {
|
|
||||||
case <-a.Context().Done():
|
|
||||||
i.conn.Quit()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IRCConfigMeta returns the metadata for IRC connector configuration fields
|
// IRCConfigMeta returns the metadata for IRC connector configuration fields
|
||||||
|
|||||||
12
webui/app.go
12
webui/app.go
@@ -176,7 +176,17 @@ func (a *App) UpdateAgentConfig(pool *state.AgentPool) func(c *fiber.Ctx) error
|
|||||||
return errorJSONMessage(c, err.Error())
|
return errorJSONMessage(c, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := pool.RecreateAgent(agentName, &newConfig); err != nil {
|
// 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))
|
||||||
|
}
|
||||||
return errorJSONMessage(c, "Error updating agent: "+err.Error())
|
return errorJSONMessage(c, "Error updating agent: "+err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
"eslint-plugin-react-refresh": "^0.4.19",
|
"eslint-plugin-react-refresh": "^0.4.19",
|
||||||
"globals": "^16.0.0",
|
"globals": "^16.0.0",
|
||||||
"react-router-dom": "^7.5.1",
|
"react-router-dom": "^7.5.1",
|
||||||
"vite": "^6.3.2",
|
"vite": "^6.3.1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -427,7 +427,7 @@
|
|||||||
|
|
||||||
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
||||||
|
|
||||||
"vite": ["vite@6.3.2", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.3", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.12" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-ZSvGOXKGceizRQIZSz7TGJ0pS3QLlVY/9hwxVh17W3re67je1RKYzFHivZ/t0tubU78Vkyb9WnHPENSBCzbckg=="],
|
"vite": ["vite@6.3.1", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.3", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.12" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-kkzzkqtMESYklo96HKKPE5KKLkC1amlsqt+RjFMlX2AvbRB/0wghap19NdBxxwGZ+h/C6DLCrcEphPIItlGrRQ=="],
|
||||||
|
|
||||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,6 @@
|
|||||||
"eslint-plugin-react-refresh": "^0.4.19",
|
"eslint-plugin-react-refresh": "^0.4.19",
|
||||||
"globals": "^16.0.0",
|
"globals": "^16.0.0",
|
||||||
"react-router-dom": "^7.5.1",
|
"react-router-dom": "^7.5.1",
|
||||||
"vite": "^6.3.2"
|
"vite": "^6.3.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,17 +99,8 @@ function AgentStatus() {
|
|||||||
creation: data.creation,
|
creation: data.creation,
|
||||||
progress: data.progress,
|
progress: data.progress,
|
||||||
completion: data.completion,
|
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 };
|
const newMap = { ...prevMap, [data.id]: updated };
|
||||||
setObservableTree(buildObservableTree(newMap));
|
setObservableTree(buildObservableTree(newMap));
|
||||||
return newMap;
|
return newMap;
|
||||||
|
|||||||
Reference in New Issue
Block a user