Files
poimen-workflows/internal/routing/knowledge_base.go
T

260 lines
7.4 KiB
Go
Raw Normal View History

package routing
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
)
// KnowledgeBase represents the activity knowledge base
type KnowledgeBase struct {
Version string `json:"version"`
Activities []ActivityMetadata `json:"activities"`
Metadata KnowledgeBaseMetadata `json:"metadata"`
// Index for fast lookups
byName map[string]*ActivityMetadata
}
// KnowledgeBaseMetadata tracks KB metadata
type KnowledgeBaseMetadata struct {
TotalActivities int `json:"totalActivities"`
LastUpdated string `json:"lastUpdated"`
Categories map[string]int `json:"categories"`
}
// LoadKnowledgeBase loads the activity knowledge base from a JSON file
func LoadKnowledgeBase(filePath string) (*KnowledgeBase, error) {
// Read file
data, err := ioutil.ReadFile(filePath)
if err != nil {
return nil, fmt.Errorf("failed to read knowledge base file: %w", err)
}
// Parse JSON
var kb KnowledgeBase
err = json.Unmarshal(data, &kb)
if err != nil {
return nil, fmt.Errorf("failed to parse knowledge base JSON: %w", err)
}
// Build index
kb.byName = make(map[string]*ActivityMetadata)
for i := range kb.Activities {
kb.byName[kb.Activities[i].Name] = &kb.Activities[i]
}
return &kb, nil
}
// LoadKnowledgeBaseFromDefaultPath loads KB from default location
// Looks for activity_knowledge_base.json in same directory as caller
func LoadKnowledgeBaseFromDefaultPath() (*KnowledgeBase, error) {
// Try to find from package directory
execDir, err := os.Executable()
if err == nil {
// Try in same directory as binary
path := filepath.Join(filepath.Dir(execDir), "activity_knowledge_base.json")
if _, err := os.Stat(path); err == nil {
return LoadKnowledgeBase(path)
}
}
// Try from current working directory
if _, err := os.Stat("activity_knowledge_base.json"); err == nil {
return LoadKnowledgeBase("activity_knowledge_base.json")
}
// Try from internal/routing directory relative to cwd
if _, err := os.Stat("internal/routing/activity_knowledge_base.json"); err == nil {
return LoadKnowledgeBase("internal/routing/activity_knowledge_base.json")
}
return nil, fmt.Errorf("activity_knowledge_base.json not found in any expected location")
}
// GetActivity returns metadata for a specific activity
func (kb *KnowledgeBase) GetActivity(name string) *ActivityMetadata {
return kb.byName[name]
}
// ListActivities returns all activities
func (kb *KnowledgeBase) ListActivities() []ActivityMetadata {
return kb.Activities
}
// ListActivitiesByCategory returns all activities in a category
func (kb *KnowledgeBase) ListActivitiesByCategory(category string) []ActivityMetadata {
var result []ActivityMetadata
for _, activity := range kb.Activities {
if activity.Category == category {
result = append(result, activity)
}
}
return result
}
// GetActivityNames returns all activity names
func (kb *KnowledgeBase) GetActivityNames() []string {
names := make([]string, len(kb.Activities))
for i, activity := range kb.Activities {
names[i] = activity.Name
}
return names
}
// HasActivity checks if an activity exists
func (kb *KnowledgeBase) HasActivity(name string) bool {
_, exists := kb.byName[name]
return exists
}
// GetDependencies returns all dependencies for an activity
func (kb *KnowledgeBase) GetDependencies(activityName string) []string {
activity := kb.GetActivity(activityName)
if activity == nil {
return []string{}
}
return activity.Constraints.Dependencies
}
// GetTimeoutForActivity returns the timeout for an activity
func (kb *KnowledgeBase) GetTimeoutForActivity(activityName string) string {
activity := kb.GetActivity(activityName)
if activity == nil {
return "5m" // Default timeout
}
return activity.Constraints.DefaultTimeout
}
// GetRetryPolicyForActivity returns retry configuration for an activity
func (kb *KnowledgeBase) GetRetryPolicyForActivity(activityName string) *RetryPolicy {
activity := kb.GetActivity(activityName)
if activity == nil {
return &RetryPolicy{
MaxAttempts: 1,
BackoffRate: 1.0,
InitialInterval: "1s",
}
}
return &RetryPolicy{
MaxAttempts: int32(activity.Constraints.RecommendedRetries),
BackoffRate: activity.Constraints.RetryBackoff,
InitialInterval: "1s",
MaxInterval: "30s",
}
}
// IsFlaky returns whether an activity is marked as flaky
func (kb *KnowledgeBase) IsFlaky(activityName string) bool {
activity := kb.GetActivity(activityName)
if activity == nil {
return false
}
return activity.Constraints.IsFlaky
}
// GetNotes returns implementation notes for an activity
func (kb *KnowledgeBase) GetNotes(activityName string) string {
activity := kb.GetActivity(activityName)
if activity == nil {
return ""
}
return activity.Constraints.Notes
}
// Validate checks the knowledge base for consistency
func (kb *KnowledgeBase) Validate() error {
// Check for circular dependencies
visited := make(map[string]bool)
for _, activity := range kb.Activities {
if err := kb.checkDependencies(activity.Name, visited, []string{}); err != nil {
return err
}
}
// Check that all dependencies exist
for _, activity := range kb.Activities {
for _, dep := range activity.Constraints.Dependencies {
if !kb.HasActivity(dep) {
return fmt.Errorf("activity %s depends on non-existent activity %s", activity.Name, dep)
}
}
}
return nil
}
// checkDependencies validates activity dependencies for cycles
func (kb *KnowledgeBase) checkDependencies(activityName string, visited map[string]bool, path []string) error {
// Check for cycles
for _, p := range path {
if p == activityName {
cycleStr := ""
found := false
for _, n := range path {
if found {
cycleStr += " -> " + n
}
if n == activityName {
found = true
cycleStr += n
}
}
cycleStr += " -> " + activityName
return fmt.Errorf("circular dependency detected: %s", cycleStr)
}
}
if visited[activityName] {
return nil // Already checked this branch
}
visited[activityName] = true
newPath := append(path, activityName)
activity := kb.GetActivity(activityName)
if activity == nil {
return nil // Non-existent activity will be caught elsewhere
}
for _, dep := range activity.Constraints.Dependencies {
if err := kb.checkDependencies(dep, visited, newPath); err != nil {
return err
}
}
return nil
}
// String returns a human-readable description of the knowledge base
func (kb *KnowledgeBase) String() string {
return fmt.Sprintf("KnowledgeBase(v%s, %d activities)", kb.Version, kb.Metadata.TotalActivities)
}
// PrintSummary prints a summary of available activities
func (kb *KnowledgeBase) PrintSummary() string {
summary := fmt.Sprintf("=== Activity Knowledge Base ===\nVersion: %s\nTotal Activities: %d\n\n", kb.Version, kb.Metadata.TotalActivities)
summary += "Activities by Category:\n"
for category, count := range kb.Metadata.Categories {
summary += fmt.Sprintf(" %s: %d\n", category, count)
}
summary += "\nActivity Details:\n"
for _, activity := range kb.Activities {
summary += fmt.Sprintf("\n[%s] %s\n", activity.Name, activity.Description)
summary += fmt.Sprintf(" Category: %s\n", activity.Category)
summary += fmt.Sprintf(" Timeout: %s\n", activity.Constraints.DefaultTimeout)
summary += fmt.Sprintf(" Flaky: %v (Retries: %d)\n", activity.Constraints.IsFlaky, activity.Constraints.RecommendedRetries)
if len(activity.Constraints.Dependencies) > 0 {
summary += fmt.Sprintf(" Dependencies: %v\n", activity.Constraints.Dependencies)
}
}
return summary
}