-
Notifications
You must be signed in to change notification settings - Fork 246
agentic error handling workflow #5521
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Changes from all commits
Commits
Show all changes
130 commits
Select commit
Hold shift + click to select a range
7347ee6
WIP
vhvb1989 0ffff37
wip
vhvb1989 8900565
Ading llm package wrapping langchain lib
vhvb1989 60b75ed
Merge branch 'main' of https://github.com/azure/azure-dev into use-llm
vhvb1989 ed0d5fa
Merge branch 'main' of https://github.com/azure/azure-dev into use-llm
vhvb1989 fe55644
revert
vhvb1989 50018b8
Merge branch 'main' of https://github.com/azure/azure-dev into use-llm
vhvb1989 5e48f4a
copyright
vhvb1989 f6e1dbc
WIP
vhvb1989 f23545b
WIP
vhvb1989 75e5ca4
Merge branch 'main' of https://github.com/azure/azure-dev into hooks-…
vhvb1989 036f690
wip
vhvb1989 3ffce68
wip
vhvb1989 593af36
wip
vhvb1989 8d593a3
Merge branch 'main' of https://github.com/azure/azure-dev into hooks-…
vhvb1989 43db53a
Merge branch 'main' of https://github.com/azure/azure-dev into hooks-…
vhvb1989 514d4e2
wip
vhvb1989 c6c3ee4
Merge branch 'main' of https://github.com/azure/azure-dev into hooks-…
vhvb1989 62246e7
mcp sampling
vhvb1989 e592c65
separate from victor's pr
hemarina 802a203
move part of hooks new code here and tweak for agentRunner
hemarina 38a5694
AI error suggestion
hemarina 8866ab0
clean from hooks new
hemarina 7a8d66f
WIP: azd ai chat
wbreza ee6c099
azd agent
wbreza c148d11
Upadates agent and tools
wbreza 00befc3
Adds MCP tool support
wbreza 08d9e32
UX updates, tool JSON payloads
wbreza 95feb33
Adds c2c similar tools
wbreza 9a36c51
Wire up noop sampling handler
wbreza 8960505
Adds sampling handler
wbreza de487a4
Fixed sampling
wbreza 9cffc50
Adds azd helper tools
wbreza bf7f39f
Moved files around
wbreza 972dcb2
WIP: Initial integration of agent mode for init
wbreza bfd5d13
Updates io tools to remove callback handler
wbreza a001e39
Adds feature flag usage
wbreza 99f195f
Updates final prompts and output
wbreza c7b41de
Fixes all linter & spelling issues
wbreza 56a68ea
Fixes more spell linter issues
wbreza 10638e3
Moves azd commands to MCP server tools
wbreza ed8df26
Fixes lint issues
wbreza 718112b
Adds github copilot instructions
wbreza 36d888b
Updates inline go docs
wbreza 339fe59
Merge branch 'azd-ai-agent-int' into error
hemarina 4e23251
Merge branch 'main' of https://github.com/Azure/azure-dev into error
hemarina 6e1ad1b
WIP: azd ai chat
wbreza add633c
azd agent
wbreza 2090338
Upadates agent and tools
wbreza f8c1b20
Adds MCP tool support
wbreza 1761578
UX updates, tool JSON payloads
wbreza 9167aa0
Adds c2c similar tools
wbreza e04ed74
Wire up noop sampling handler
wbreza 0c5e3db
Adds sampling handler
wbreza 8377ecb
Fixed sampling
wbreza 0637809
Adds azd helper tools
wbreza 4f5b28e
Moved files around
wbreza 42e1324
WIP: Initial integration of agent mode for init
wbreza 70170af
Updates io tools to remove callback handler
wbreza 1cbdb1e
Adds feature flag usage
wbreza 1c19950
Updates final prompts and output
wbreza d45f608
Fixes all linter & spelling issues
wbreza 21a6559
Fixes more spell linter issues
wbreza 8bc28bc
Moves azd commands to MCP server tools
wbreza 889cd2b
Fixes lint issues
wbreza af2cf70
Adds github copilot instructions
wbreza 459fc3c
Updates inline go docs
wbreza 88e2ced
WIP: user consent
wbreza b17be7c
Adds more consent validation
wbreza cac0351
Adds unit tests for reading files, LLM multi-partial write scenarios.
wbreza b24e242
Updates write tool description about partial writes
wbreza 79c0485
Fixes MCP tools
wbreza f498b4c
Adds consent system for sampling requests
wbreza 106c55d
Revision of consent system
wbreza 31805e3
Revises tool prompts and adds annotations to azd tools
wbreza d5dfe2b
Moves flag parsing to consent package
wbreza 73aab29
Made updates to consent filtering APIs
wbreza 22476af
Renamed 'clear' to 'revoke'
wbreza 59c5694
Addresses linter issues
wbreza e324121
Update azure yaml validation tool
wbreza 4724432
Updates prompts and markdown output
wbreza 5ee0b02
Updates MCP command registration after rebase
wbreza 584ae77
Updates tool prompts
wbreza 6f98217
add azd error troubleshooting tool md
hemarina fddea1b
Merge branch 'azd-ai-agent-int' into error
hemarina 8c0f631
wip
wbreza 4574d49
visual render orchestration
wbreza 74e3159
Updates agent output for init
wbreza eecef9f
Updates read/write file tests
wbreza c8ef23e
Merge branch 'azd-ai-agent-int' into error
hemarina e0871ef
debug
hemarina 762bffd
Fixes issue where agent hangs on blocking thought channel
wbreza 22d23b2
Merge branch 'azd-ai-agent-int' into error
hemarina 644117f
fix error middleware workflow bug
hemarina f19d828
Merge branch 'main' of https://github.com/Azure/azure-dev into error
hemarina a69bb37
clean up merge
hemarina b026808
clean up
hemarina adb754b
minor fix
hemarina 8e0df2d
add user confirmation prompt
hemarina 590015b
lll
hemarina 5f48532
fix the bug
hemarina 5937df3
revert this ux fix, cause other bug
hemarina 80effbc
lll
hemarina 0e3905b
minor fix on comment and skipAnalyzingErrors
hemarina c778dcd
remove comment
hemarina e5fe1b4
fix provision print twice ux in up bug
hemarina ef17fd0
make sure validation file changes is removed
hemarina c9fe908
print out file changes
hemarina 9ad6fd6
add prompt consent for error handling, update tool color, add ai disc…
hemarina 4de0c1e
separate error handling consent from consent system
hemarina 3f3a8af
clean up and lll
hemarina df6ff52
fix bug for prompt consent
hemarina aea8215
minor description update
hemarina 545bf7c
fileschange printed fix
hemarina 2c01342
fix azd.exe multiple calling at background bug
hemarina 3f29682
nit
hemarina 5f94059
remove debugger
hemarina f0b927e
fix files changed, address comment
hemarina e23a0cd
fix golangci-lint warning
hemarina 0efbe50
minor UX fix
hemarina 5221d60
lll
hemarina d8e9f61
fix spinner UX bug for "Comparing deployment state" running forever
hemarina a431fd4
UX update on troubleshoot steps
hemarina a5f21f1
minor ux update
hemarina 59fc732
Merge branch 'main' of https://github.com/Azure/azure-dev into error
hemarina d6be32e
minor ux updates
hemarina bbc1769
address feedback 1
hemarina c55c7cd
address feedback 2
hemarina ae9ecf7
address feedback
hemarina 758d12f
address feedback
hemarina File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,340 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
package middleware | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"strings" | ||
|
||
"github.com/azure/azure-dev/cli/azd/cmd/actions" | ||
"github.com/azure/azure-dev/cli/azd/internal" | ||
"github.com/azure/azure-dev/cli/azd/internal/agent" | ||
"github.com/azure/azure-dev/cli/azd/pkg/alpha" | ||
"github.com/azure/azure-dev/cli/azd/pkg/config" | ||
"github.com/azure/azure-dev/cli/azd/pkg/input" | ||
"github.com/azure/azure-dev/cli/azd/pkg/llm" | ||
"github.com/azure/azure-dev/cli/azd/pkg/output" | ||
"github.com/azure/azure-dev/cli/azd/pkg/tools" | ||
uxlib "github.com/azure/azure-dev/cli/azd/pkg/ux" | ||
"github.com/fatih/color" | ||
) | ||
|
||
type ErrorMiddleware struct { | ||
options *Options | ||
console input.Console | ||
agentFactory *agent.AgentFactory | ||
global *internal.GlobalCommandOptions | ||
featuresManager *alpha.FeatureManager | ||
userConfigManager config.UserConfigManager | ||
} | ||
|
||
func NewErrorMiddleware( | ||
options *Options, console input.Console, | ||
agentFactory *agent.AgentFactory, | ||
global *internal.GlobalCommandOptions, | ||
featuresManager *alpha.FeatureManager, | ||
userConfigManager config.UserConfigManager, | ||
) Middleware { | ||
return &ErrorMiddleware{ | ||
options: options, | ||
console: console, | ||
agentFactory: agentFactory, | ||
global: global, | ||
featuresManager: featuresManager, | ||
userConfigManager: userConfigManager, | ||
} | ||
} | ||
|
||
func (e *ErrorMiddleware) Run(ctx context.Context, next NextFn) (*actions.ActionResult, error) { | ||
actionResult, err := next(ctx) | ||
if !e.featuresManager.IsEnabled(llm.FeatureLlm) { | ||
return actionResult, err | ||
} | ||
|
||
// Stop the spinner always to un-hide cursor | ||
e.console.StopSpinner(ctx, "", input.Step) | ||
if err == nil || e.options.IsChildAction(ctx) { | ||
return actionResult, err | ||
} | ||
|
||
// Error already has a suggestion, no need for AI | ||
var suggestionErr *internal.ErrorWithSuggestion | ||
if errors.As(err, &suggestionErr) { | ||
e.console.Message(ctx, suggestionErr.Suggestion) | ||
return actionResult, err | ||
} | ||
|
||
// Skip certain errors, no need for AI | ||
skipAnalyzingErrors := []string{ | ||
"environment already initialized", | ||
"interrupt", | ||
"no project exists", | ||
} | ||
for _, s := range skipAnalyzingErrors { | ||
if strings.Contains(err.Error(), s) { | ||
return actionResult, err | ||
} | ||
} | ||
|
||
// Warn user that this is an alpha feature | ||
e.console.WarnForFeature(ctx, llm.FeatureLlm) | ||
|
||
originalError := err | ||
azdAgent, err := e.agentFactory.Create( | ||
agent.WithDebug(e.global.EnableDebugLogging), | ||
agent.WithFileWatching(true), | ||
) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
defer azdAgent.Stop() | ||
|
||
attempt := 0 | ||
var previousError error | ||
var errorWithTraceId *internal.ErrorWithTraceId | ||
AIDisclaimer := output.WithGrayFormat("The following content is AI-generated. AI responses may be incorrect.") | ||
agentName := "agent mode" | ||
|
||
for { | ||
if originalError == nil { | ||
break | ||
} | ||
|
||
e.console.Message(ctx, output.WithErrorFormat("\nERROR: %s", originalError.Error())) | ||
|
||
if previousError != nil && errors.Is(originalError, previousError) { | ||
attempt++ | ||
if attempt >= 3 { | ||
e.console.Message(ctx, fmt.Sprintf("Please review the error and fix it manually, "+ | ||
"%s was unable to resolve the error after multiple attempts.", agentName)) | ||
return actionResult, originalError | ||
} | ||
} | ||
|
||
if errors.As(originalError, &errorWithTraceId) { | ||
e.console.Message(ctx, output.WithErrorFormat("TraceID: %s", errorWithTraceId.TraceId)) | ||
} | ||
|
||
errorInput := originalError.Error() | ||
|
||
e.console.Message(ctx, "") | ||
confirm, err := e.checkErrorHandlingConsent( | ||
ctx, | ||
"mcp.errorHandling.troubleshooting", | ||
fmt.Sprintf("Generate troubleshooting steps using %s?", agentName), | ||
fmt.Sprintf("This action will run AI tools to generate troubleshooting steps."+ | ||
" Edit permissions for AI tools anytime by running %s.", | ||
output.WithHighLightFormat("azd mcp consent")), | ||
true, | ||
) | ||
if err != nil { | ||
return nil, fmt.Errorf("prompting to provide troubleshooting steps: %w", err) | ||
} | ||
|
||
if confirm { | ||
// Provide manual steps for troubleshooting | ||
agentOutput, err := azdAgent.SendMessage(ctx, fmt.Sprintf( | ||
`Steps to follow: | ||
1. Use available tool including azd_error_troubleshooting tool to identify and explain the error. | ||
Diagnose its root cause when running azd command. | ||
2. Provide actionable troubleshooting steps. Do not perform any file changes. | ||
Error details: %s`, errorInput)) | ||
|
||
if err != nil { | ||
if agentOutput != "" { | ||
e.console.Message(ctx, AIDisclaimer) | ||
e.console.Message(ctx, output.WithMarkdown(agentOutput)) | ||
} | ||
|
||
return nil, err | ||
} | ||
|
||
e.console.Message(ctx, AIDisclaimer) | ||
e.console.Message(ctx, "") | ||
e.console.Message(ctx, fmt.Sprintf("%s:", output.AzdAgentLabel())) | ||
e.console.Message(ctx, output.WithMarkdown(agentOutput)) | ||
e.console.Message(ctx, "") | ||
} | ||
|
||
// Ask user if they want to let AI fix the | ||
confirm, err = e.checkErrorHandlingConsent( | ||
ctx, | ||
"mcp.errorHandling.fix", | ||
fmt.Sprintf("Fix this error using %s?", agentName), | ||
fmt.Sprintf("This action will run AI tools to help fix the error."+ | ||
" Edit permissions for AI tools anytime by running %s.", | ||
output.WithHighLightFormat("azd mcp consent")), | ||
false, | ||
) | ||
if err != nil { | ||
return nil, fmt.Errorf("prompting to fix error using %s: %w", agentName, err) | ||
} | ||
|
||
if !confirm { | ||
return actionResult, err | ||
} | ||
|
||
previousError = originalError | ||
agentOutput, err := azdAgent.SendMessage(ctx, fmt.Sprintf( | ||
`Steps to follow: | ||
1. Use available tool to identify, explain and diagnose this error when running azd command and its root cause. | ||
2. Resolve the error by making the minimal, targeted change required to the code or configuration. | ||
Avoid unnecessary modifications and focus only on what is essential to restore correct functionality. | ||
3. Remove any changes that were created solely for validation and are not part of the actual error fix. | ||
Error details: %s`, errorInput)) | ||
|
||
if err != nil { | ||
if agentOutput != "" { | ||
e.console.Message(ctx, AIDisclaimer) | ||
e.console.Message(ctx, output.WithMarkdown(agentOutput)) | ||
} | ||
|
||
return nil, err | ||
} | ||
|
||
// Ask the user to add feedback | ||
if err := e.collectAndApplyFeedback(ctx, azdAgent, AIDisclaimer); err != nil { | ||
return nil, err | ||
} | ||
|
||
// Clear check cache to prevent skip of tool related error | ||
ctx = tools.WithInstalledCheckCache(ctx) | ||
|
||
actionResult, err = next(ctx) | ||
originalError = err | ||
} | ||
|
||
return actionResult, err | ||
} | ||
|
||
// collectAndApplyFeedback prompts for user feedback and applies it using the agent | ||
func (e *ErrorMiddleware) collectAndApplyFeedback( | ||
ctx context.Context, | ||
azdAgent agent.Agent, | ||
AIDisclaimer string, | ||
) error { | ||
userInputPrompt := uxlib.NewPrompt(&uxlib.PromptOptions{ | ||
Message: "Any changes you'd like to make?", | ||
Hint: "Describe your changes or press enter to skip.", | ||
Required: false, | ||
}) | ||
|
||
userInput, err := userInputPrompt.Ask(ctx) | ||
if err != nil { | ||
return fmt.Errorf("failed to collect feedback for user input: %w", err) | ||
} | ||
|
||
if userInput == "" { | ||
e.console.Message(ctx, "") | ||
return nil | ||
} | ||
|
||
e.console.Message(ctx, "") | ||
e.console.Message(ctx, color.MagentaString("Feedback")) | ||
|
||
feedbackOutput, err := azdAgent.SendMessage(ctx, userInput) | ||
if err != nil { | ||
if feedbackOutput != "" { | ||
e.console.Message(ctx, AIDisclaimer) | ||
e.console.Message(ctx, output.WithMarkdown(feedbackOutput)) | ||
} | ||
return err | ||
} | ||
|
||
e.console.Message(ctx, AIDisclaimer) | ||
e.console.Message(ctx, "") | ||
e.console.Message(ctx, fmt.Sprintf("%s:", output.AzdAgentLabel())) | ||
e.console.Message(ctx, output.WithMarkdown(feedbackOutput)) | ||
e.console.Message(ctx, "") | ||
|
||
return nil | ||
} | ||
|
||
func (e *ErrorMiddleware) checkErrorHandlingConsent( | ||
ctx context.Context, | ||
promptName string, | ||
message string, | ||
helpMessage string, | ||
skip bool, | ||
) (bool, error) { | ||
userConfig, err := e.userConfigManager.Load() | ||
if err != nil { | ||
return false, fmt.Errorf("failed to load user config: %w", err) | ||
} | ||
|
||
if exists, ok := userConfig.GetString(promptName); !ok && exists == "" { | ||
choice, err := promptForErrorHandlingConsent(ctx, message, helpMessage, skip) | ||
if err != nil { | ||
return false, fmt.Errorf("prompting for error handling consent: %w", err) | ||
} | ||
|
||
if choice == "skip" || choice == "deny" { | ||
return false, nil | ||
} | ||
|
||
if choice == "always" { | ||
if err := userConfig.Set(promptName, "allow"); err != nil { | ||
return false, fmt.Errorf("failed to set consent config: %w", err) | ||
} | ||
|
||
if err := e.userConfigManager.Save(userConfig); err != nil { | ||
return false, err | ||
} | ||
} | ||
} | ||
|
||
return true, nil | ||
} | ||
|
||
func promptForErrorHandlingConsent( | ||
ctx context.Context, | ||
message string, | ||
helpMessage string, | ||
skip bool, | ||
) (string, error) { | ||
choices := []*uxlib.SelectChoice{ | ||
{ | ||
Value: "once", | ||
Label: "Yes, allow once", | ||
}, | ||
{ | ||
Value: "always", | ||
Label: "Yes, allow always", | ||
}, | ||
} | ||
|
||
if skip { | ||
choices = append(choices, &uxlib.SelectChoice{ | ||
Value: "skip", | ||
Label: "No, skip to next step", | ||
}) | ||
} else { | ||
choices = append(choices, &uxlib.SelectChoice{ | ||
Value: "deny", | ||
Label: "No, cancel this interaction (esc)", | ||
}) | ||
} | ||
|
||
selector := uxlib.NewSelect(&uxlib.SelectOptions{ | ||
Message: message, | ||
HelpMessage: helpMessage, | ||
Choices: choices, | ||
EnableFiltering: uxlib.Ptr(false), | ||
DisplayCount: 5, | ||
}) | ||
|
||
choiceIndex, err := selector.Ask(ctx) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
if choiceIndex == nil || *choiceIndex < 0 || *choiceIndex >= len(choices) { | ||
return "", fmt.Errorf("invalid choice selected") | ||
} | ||
|
||
return choices[*choiceIndex].Value, nil | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.