package actions import ( "context" "fmt" "time" "github.com/mudler/LocalAGI/core/types" "github.com/mudler/LocalAGI/pkg/config" api "github.com/mudler/LocalAGI/pkg/localoperator" "github.com/sashabaranov/go-openai/jsonschema" ) const ( MetadataDeepResearchResult = "deep_research_result" ) type DeepResearchRunner struct { baseURL, customActionName string client *api.Client } func NewDeepResearchRunner(config map[string]string, defaultURL string) *DeepResearchRunner { if config["baseURL"] == "" { config["baseURL"] = defaultURL } timeout := "15m" if config["timeout"] != "" { timeout = config["timeout"] } duration, err := time.ParseDuration(timeout) if err != nil { // If parsing fails, use default 15 minutes duration = 15 * time.Minute } client := api.NewClient(config["baseURL"], duration) return &DeepResearchRunner{ client: client, baseURL: config["baseURL"], customActionName: config["customActionName"], } } func (d *DeepResearchRunner) Run(ctx context.Context, sharedState *types.AgentSharedState, params types.ActionParams) (types.ActionResult, error) { result := api.DeepResearchRequest{} err := params.Unmarshal(&result) if err != nil { return types.ActionResult{}, fmt.Errorf("failed to unmarshal params: %w", err) } req := api.DeepResearchRequest{ Topic: result.Topic, MaxCycles: result.MaxCycles, MaxNoActionAttempts: result.MaxNoActionAttempts, MaxResults: result.MaxResults, } researchResult, err := d.client.RunDeepResearch(req) if err != nil { return types.ActionResult{}, fmt.Errorf("failed to run deep research: %w", err) } // Format the research result into a readable string var resultStr string resultStr += "Deep research result\n" resultStr += fmt.Sprintf("Topic: %s\n", researchResult.Topic) resultStr += fmt.Sprintf("Summary: %s\n", researchResult.Summary) resultStr += fmt.Sprintf("Research Cycles: %d\n", researchResult.ResearchCycles) resultStr += fmt.Sprintf("Completion Time: %s\n\n", researchResult.CompletionTime) if len(researchResult.Sources) > 0 { resultStr += "Sources:\n" for _, source := range researchResult.Sources { resultStr += fmt.Sprintf("- %s (%s)\n %s\n", source.Title, source.URL, source.Description) } } return types.ActionResult{ Result: fmt.Sprintf("Deep research completed successfully.\n%s", resultStr), Metadata: map[string]interface{}{MetadataDeepResearchResult: researchResult}, }, nil } func (d *DeepResearchRunner) Definition() types.ActionDefinition { actionName := "run_deep_research" if d.customActionName != "" { actionName = d.customActionName } description := "Run a deep research on a specific topic, gathering information from multiple sources and providing a comprehensive summary" return types.ActionDefinition{ Name: types.ActionDefinitionName(actionName), Description: description, Properties: map[string]jsonschema.Definition{ "topic": { Type: jsonschema.String, Description: "The topic to research", }, "max_cycles": { Type: jsonschema.Number, Description: "Maximum number of research cycles to perform (optional)", }, "max_no_action_attempts": { Type: jsonschema.Number, Description: "Maximum number of attempts without taking an action (optional)", }, "max_results": { Type: jsonschema.Number, Description: "Maximum number of results to collect (optional)", }, }, Required: []string{"topic"}, } } func (d *DeepResearchRunner) Plannable() bool { return true } // DeepResearchRunnerConfigMeta returns the metadata for Deep Research Runner action configuration fields func DeepResearchRunnerConfigMeta() []config.Field { return []config.Field{ { Name: "baseURL", Label: "Base URL", Type: config.FieldTypeText, Required: false, HelpText: "Base URL of the LocalOperator API", }, { Name: "customActionName", Label: "Custom Action Name", Type: config.FieldTypeText, HelpText: "Custom name for this action", }, { Name: "timeout", Label: "Client Timeout", Type: config.FieldTypeText, Required: false, HelpText: "Client timeout duration (e.g. '15m', '1h'). Defaults to '15m' if not specified.", }, } }