- Add streamWriter to LLMAgent for real-time output - Support streaming mode in chatWithToolLoop - Add SetStreamWriter to Kernel and LLMAgent - CLI displays streaming responses immediately - Tool calling still works with streaming
206 lines
4.9 KiB
Go
206 lines
4.9 KiB
Go
// Orca is a Go-based Agent framework with a microkernel architecture.
|
|
//
|
|
// It supports multi-agent collaboration, persistent session memory,
|
|
// skill-based automation, sandboxed execution, custom tool registration,
|
|
// and local LLM integration via Ollama.
|
|
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"os/signal"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/orca/orca/internal/config"
|
|
"github.com/orca/orca/pkg/kernel"
|
|
)
|
|
|
|
func main() {
|
|
// Load configuration from environment variables
|
|
cfg := config.LoadConfigFromEnv()
|
|
|
|
// Support shorter env var names for Ollama (without ORCA_ prefix)
|
|
if v := os.Getenv("OLLAMA_BASE_URL"); v != "" {
|
|
cfg.Ollama.BaseURL = v
|
|
}
|
|
if v := os.Getenv("OLLAMA_MODEL"); v != "" {
|
|
cfg.Ollama.Model = v
|
|
}
|
|
if v := os.Getenv("OLLAMA_TIMEOUT"); v != "" {
|
|
if d, err := time.ParseDuration(v); err == nil {
|
|
cfg.Ollama.Timeout = d
|
|
}
|
|
}
|
|
|
|
// Create and start kernel
|
|
k := kernel.NewWithConfig(cfg)
|
|
|
|
if err := k.Start(); err != nil {
|
|
log.Fatalf("Failed to start kernel: %v", err)
|
|
}
|
|
|
|
k.SetStreamWriter(os.Stdout)
|
|
|
|
fmt.Println("Orca Agent Framework")
|
|
fmt.Println("Kernel started successfully")
|
|
fmt.Printf(" LLM Model: %s\n", cfg.Ollama.Model)
|
|
fmt.Printf(" Ollama URL: %s\n", cfg.Ollama.BaseURL)
|
|
fmt.Println("Type your message or /help for commands.")
|
|
fmt.Println()
|
|
|
|
// Handle graceful shutdown
|
|
sig := make(chan os.Signal, 1)
|
|
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
|
|
|
|
// REPL loop in a goroutine so we can catch signals
|
|
done := make(chan struct{})
|
|
|
|
go func() {
|
|
scanner := bufio.NewScanner(os.Stdin)
|
|
for {
|
|
fmt.Print("> ")
|
|
if !scanner.Scan() {
|
|
break
|
|
}
|
|
|
|
input := strings.TrimSpace(scanner.Text())
|
|
if input == "" {
|
|
continue
|
|
}
|
|
|
|
// Handle commands
|
|
if strings.HasPrefix(input, "/") {
|
|
handleCommand(input, k)
|
|
continue
|
|
}
|
|
|
|
// Send message to LLM agent via kernel
|
|
_, err := k.SendMessage("user", "llm", input)
|
|
if err != nil {
|
|
fmt.Printf("Error: %v\n", err)
|
|
continue
|
|
}
|
|
fmt.Println()
|
|
}
|
|
|
|
if err := scanner.Err(); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error reading input: %v\n", err)
|
|
}
|
|
close(done)
|
|
}()
|
|
|
|
// Wait for either SIGINT or REPL exit
|
|
select {
|
|
case <-sig:
|
|
fmt.Println("\nShutting down Orca kernel...")
|
|
case <-done:
|
|
fmt.Println("\nInput closed. Shutting down Orca kernel...")
|
|
}
|
|
|
|
if err := k.Stop(); err != nil {
|
|
log.Fatalf("Failed to stop kernel: %v", err)
|
|
}
|
|
fmt.Println("Orca kernel shut down gracefully.")
|
|
}
|
|
|
|
// handleCommand processes REPL commands.
|
|
func handleCommand(cmd string, k *kernel.Kernel) {
|
|
switch cmd {
|
|
case "/help":
|
|
fmt.Println("Available commands:")
|
|
fmt.Println(" /help - Show this help message")
|
|
fmt.Println(" /exit - Exit the program")
|
|
fmt.Println(" /quit - Exit the program")
|
|
fmt.Println(" /plugins - List registered plugins")
|
|
fmt.Println(" /agents - List active agents")
|
|
fmt.Println(" /tools - List registered tools")
|
|
fmt.Println(" /skills - List loaded skills")
|
|
fmt.Println(" /status - Show kernel status")
|
|
fmt.Println()
|
|
fmt.Println("Any other input is sent to the LLM agent for processing.")
|
|
|
|
case "/exit", "/quit":
|
|
fmt.Println("Goodbye!")
|
|
os.Exit(0)
|
|
|
|
case "/plugins":
|
|
plugins := k.ListPlugins()
|
|
if len(plugins) == 0 {
|
|
fmt.Println("No plugins registered.")
|
|
} else {
|
|
fmt.Println("Registered plugins:")
|
|
for _, p := range plugins {
|
|
fmt.Printf(" - %s (%s)\n", p.Name(), p.Version())
|
|
}
|
|
}
|
|
|
|
case "/agents":
|
|
as := k.ActorSystem()
|
|
if as == nil {
|
|
fmt.Println("Actor system not initialized.")
|
|
return
|
|
}
|
|
infos := as.AgentInfos()
|
|
if len(infos) == 0 {
|
|
fmt.Println("No agents running.")
|
|
} else {
|
|
fmt.Println("Active agents:")
|
|
for _, info := range infos {
|
|
fmt.Printf(" - %s [%s] (status: %s)\n", info.ID, info.Role, info.Status)
|
|
}
|
|
}
|
|
|
|
case "/tools":
|
|
tm := k.ToolManager()
|
|
if tm == nil {
|
|
fmt.Println("Tool manager not initialized.")
|
|
return
|
|
}
|
|
tools := tm.List()
|
|
if len(tools) == 0 {
|
|
fmt.Println("No tools registered.")
|
|
} else {
|
|
fmt.Println("Registered tools:")
|
|
for _, t := range tools {
|
|
fmt.Printf(" - %s: %s\n", t.Name(), t.Description())
|
|
}
|
|
}
|
|
|
|
case "/skills":
|
|
sm := k.SkillManager()
|
|
if sm == nil {
|
|
fmt.Println("Skill manager not initialized.")
|
|
return
|
|
}
|
|
skills := sm.ListSkills()
|
|
if len(skills) == 0 {
|
|
fmt.Println("No skills loaded.")
|
|
} else {
|
|
fmt.Println("Loaded skills:")
|
|
for _, s := range skills {
|
|
fmt.Printf(" - %s: %s\n", s.Name, s.Description)
|
|
}
|
|
}
|
|
|
|
case "/status":
|
|
fmt.Printf("Kernel running: %v\n", k.IsRunning())
|
|
if tm := k.ToolManager(); tm != nil {
|
|
fmt.Printf("Tools registered: %d\n", tm.Count())
|
|
}
|
|
if as := k.ActorSystem(); as != nil {
|
|
fmt.Printf("Agents active: %d\n", as.AgentCount())
|
|
}
|
|
if sm := k.SkillManager(); sm != nil {
|
|
fmt.Printf("Skills loaded: %d\n", len(sm.ListSkills()))
|
|
}
|
|
|
|
default:
|
|
fmt.Printf("Unknown command: %s\n", cmd)
|
|
fmt.Println("Type /help for available commands.")
|
|
}
|
|
}
|