Skip to content

Commit cd50156

Browse files
authored
Improve Resolve-Error command and allow default system prompt for the openai-gpt agent (#397)
1 parent 2a2212f commit cd50156

File tree

5 files changed

+159
-92
lines changed

5 files changed

+159
-92
lines changed

shell/AIShell.Integration/Commands/ResolveErrorCommand.cs

Lines changed: 70 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,6 @@ public class ResolveErrorCommand : PSCmdlet
2020

2121
protected override void EndProcessing()
2222
{
23-
bool questionMarkValue = (bool)GetVariableValue("?");
24-
if (questionMarkValue)
25-
{
26-
WriteWarning("No error to resolve. The last command execution was successful.");
27-
return;
28-
}
29-
30-
object value = GetVariableValue("LASTEXITCODE");
31-
int lastExitCode = value is null ? 0 : (int)value;
32-
3323
using var pwsh = PowerShell.Create(RunspaceMode.CurrentRunspace);
3424
var results = pwsh
3525
.AddCommand("Get-History")
@@ -42,41 +32,64 @@ protected override void EndProcessing()
4232
return;
4333
}
4434

45-
string query = null, context = null;
4635
HistoryInfo lastHistory = results[0];
36+
ArrayList errors = (ArrayList)GetVariableValue("Error");
37+
bool questionMarkValue = (bool)GetVariableValue("?");
38+
object value = GetVariableValue("LASTEXITCODE");
39+
int lastExitCode = value is null ? 0 : (int)value;
40+
41+
ErrorRecord lastError = null;
42+
bool useLastError = false;
43+
44+
if (errors.Count > 0)
45+
{
46+
object last = errors[0];
47+
lastError = last switch
48+
{
49+
ParseException pe => pe.ErrorRecord,
50+
ErrorRecord er => er,
51+
_ => throw new NotSupportedException($"Unexpected type of object '{last.GetType().FullName}' is found in '$Error'.")
52+
};
53+
54+
// Use the last error for troubleshooting when
55+
// - last error maps to the last history command, OR
56+
// - they don't map but 'LastExitCode' is 0 (meaning no native command failure).
57+
useLastError = lastError.InvocationInfo.HistoryId == lastHistory.Id || lastExitCode is 0;
58+
}
59+
60+
string query = null;
4761
Channel channel = Channel.Singleton;
4862
string commandLine = lastHistory.CommandLine;
4963

50-
if (TryGetLastError(lastHistory, out ErrorRecord lastError))
64+
if (useLastError)
5165
{
52-
query = ErrorFeedback.CreateQueryForError(commandLine, lastError, channel);
66+
query = ErrorFeedback.CreateQueryForError(lastError);
5367
}
5468
else if (lastExitCode is 0)
5569
{
56-
// Cannot find the ErrorRecord associated with the last command, and no native command failure, so we don't know why '$?' was set to False.
57-
ErrorRecord error = new(
58-
new NotSupportedException($"Failed to detect the actual error even though '$?' is 'False'. No 'ErrorRecord' can be found that is associated with the last command line '{commandLine}' and no executable failure was found."),
59-
errorId: "FailedToDetectActualError",
60-
ErrorCategory.ObjectNotFound,
61-
targetObject: null);
62-
ThrowTerminatingError(error);
70+
// $Error is empty and no failure from native command execution.
71+
WriteWarning("Cannot find an error to resolve.");
72+
return;
6373
}
64-
else
74+
else if (!questionMarkValue)
6575
{
66-
// '$? == False' but no 'ErrorRecord' can be found that is associated with the last command line,
67-
// and '$LASTEXITCODE' is non-zero, which indicates the last failed command is a native command.
68-
query = $"""
69-
Running the command line `{commandLine}` in PowerShell v{channel.PSVersion} failed.
70-
Please try to explain the failure and suggest the right fix.
71-
Output of the command line can be found in the context information below.
72-
""";
73-
74-
context = ScrapeScreenForNativeCommandOutput(commandLine);
75-
if (context is null)
76+
// In this scenario, we have:
77+
// - Last error doesn't map to the last history command;
78+
// - $LastExitCode is non-zero;
79+
// - $? is false.
80+
// It indicates the last command is a native command and it failed.
81+
string output = ScrapeScreenForNativeCommandOutput(commandLine);
82+
83+
if (output is null)
7684
{
7785
if (UseClipboardForCommandOutput())
7886
{
79-
IncludeOutputFromClipboard = true;
87+
pwsh.Commands.Clear();
88+
output = pwsh
89+
.AddCommand("Get-Clipboard")
90+
.AddParameter("Raw")
91+
.Invoke<string>()
92+
.FirstOrDefault();
8093
}
8194
else
8295
{
@@ -88,20 +101,35 @@ Output of the command line can be found in the context information below.
88101
));
89102
}
90103
}
91-
}
92104

93-
if (context is null && IncludeOutputFromClipboard)
94-
{
95-
pwsh.Commands.Clear();
96-
var r = pwsh
97-
.AddCommand("Get-Clipboard")
98-
.AddParameter("Raw")
99-
.Invoke<string>();
105+
query = $"""
106+
Please troubleshoot the command-line error that happend in the connected PowerShell session, and suggest the fix. The error details are given below:
107+
108+
---
100109
101-
context = r?.Count > 0 ? r[0] : null;
110+
## Command Line
111+
```
112+
{commandLine}
113+
```
114+
115+
## Error Output
116+
```
117+
{output}
118+
```
119+
""";
120+
}
121+
else
122+
{
123+
// When reaching here, we have
124+
// - Last error doesn't map to the last history command, or $Error is empty;
125+
// - $LastExitCode is non-zero;
126+
// - $? is true.
127+
// The user may want to fix a command that failed previously, but it's unknown whether
128+
// that was a native command failure or PowerShell command failure.
129+
query = "Take a look at the terminal output of the connected PowerShell session and try resolving the last error you see.";
102130
}
103131

104-
channel.PostQuery(new PostQueryMessage(query, context, Agent));
132+
channel.PostQuery(new PostQueryMessage(query, context: null, Agent));
105133
}
106134

107135
private bool UseClipboardForCommandOutput()
@@ -115,29 +143,6 @@ private bool UseClipboardForCommandOutput()
115143
return ShouldContinue(query, caption: "Include output from the clipboard");
116144
}
117145

118-
private bool TryGetLastError(HistoryInfo lastHistory, out ErrorRecord lastError)
119-
{
120-
lastError = null;
121-
ArrayList errors = (ArrayList)GetVariableValue("Error");
122-
if (errors.Count == 0)
123-
{
124-
return false;
125-
}
126-
127-
lastError = errors[0] as ErrorRecord;
128-
if (lastError is null && errors[0] is RuntimeException rtEx)
129-
{
130-
lastError = rtEx.ErrorRecord;
131-
}
132-
133-
if (lastError?.InvocationInfo is null || lastError.InvocationInfo.HistoryId != lastHistory.Id)
134-
{
135-
return false;
136-
}
137-
138-
return true;
139-
}
140-
141146
private string ScrapeScreenForNativeCommandOutput(string lastCommandLine)
142147
{
143148
if (!OperatingSystem.IsWindows())

shell/AIShell.Integration/ErrorFeedback.cs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public FeedbackItem GetFeedback(FeedbackContext context, CancellationToken token
3434
Channel channel = Channel.Singleton;
3535
if (channel.CheckConnection(blocking: false, out _))
3636
{
37-
string query = CreateQueryForError(context.CommandLine, context.LastError, channel);
37+
string query = CreateQueryForError(context.LastError);
3838
PostQueryMessage message = new(query, context: null, agent: null);
3939
channel.PostQuery(message);
4040

@@ -44,37 +44,37 @@ public FeedbackItem GetFeedback(FeedbackContext context, CancellationToken token
4444
return null;
4545
}
4646

47-
internal static string CreateQueryForError(string commandLine, ErrorRecord lastError, Channel channel)
47+
internal static string CreateQueryForError(ErrorRecord lastError)
4848
{
4949
Exception exception = lastError.Exception;
5050
StringBuilder sb = new StringBuilder(capacity: 100)
51-
.Append(
52-
$"""
53-
Running the command line `{commandLine}` in PowerShell v{channel.PSVersion} failed.
54-
Please try to explain the failure and suggest the right fix.
55-
The error details can be found below in the markdown format.
56-
""")
57-
.Append("\n\n")
58-
.Append("# Error Details\n")
51+
.Append("Please troubleshoot the command-line error that happend in the connected PowerShell session, and suggest the fix. The error details are given below:\n\n---\n\n")
52+
.Append("## Command Line\n")
53+
.Append("```\n")
54+
.Append(lastError.InvocationInfo.Line).Append('\n')
55+
.Append("```\n\n")
5956
.Append("## Exception Messages\n")
6057
.Append($"{exception.GetType().FullName}: {exception.Message}\n");
6158

6259
exception = exception.InnerException;
6360
if (exception is not null)
6461
{
65-
sb.Append("Inner Exceptions:\n");
62+
sb.Append("\nInner Exceptions:\n");
6663
do
6764
{
68-
sb.Append($" - {exception.GetType().FullName}: {exception.Message}\n");
65+
sb.Append($"- {exception.GetType().FullName}: {exception.Message}\n");
6966
exception = exception.InnerException;
7067
}
7168
while (exception is not null);
7269
}
7370

74-
string positionMessage = lastError.InvocationInfo?.PositionMessage;
71+
string positionMessage = lastError.InvocationInfo.PositionMessage;
7572
if (!string.IsNullOrEmpty(positionMessage))
7673
{
77-
sb.Append("## Error Position\n").Append(positionMessage).Append('\n');
74+
sb.Append("\n## Error Position\n")
75+
.Append("```\n")
76+
.Append(positionMessage).Append('\n')
77+
.Append("```\n");
7878
}
7979

8080
return sb.ToString();

shell/agents/AIShell.OpenAI.Agent/GPT.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ public GPT(
5050
ArgumentException.ThrowIfNullOrEmpty(name);
5151
ArgumentException.ThrowIfNullOrEmpty(description);
5252
ArgumentException.ThrowIfNullOrEmpty(modelName);
53-
ArgumentException.ThrowIfNullOrEmpty(systemPrompt);
5453

5554
Name = name;
5655
Description = description;

shell/agents/AIShell.OpenAI.Agent/Helpers.cs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,3 +189,66 @@ private bool ShouldRetryImpl(PipelineMessage message, Exception exception)
189189
return default;
190190
}
191191
}
192+
193+
internal static class Prompt
194+
{
195+
internal static string SystemPromptWithConnectedPSSession = $"""
196+
You are a virtual assistant in **AIShell**, specializing in PowerShell and other command-line tools.
197+
198+
You are connected to an interactive PowerShell session and can retrieve session context and interact with the session using built-in tools. When user queries are ambiguous or minimal, rely on session context to better understand intent and deliver accurate, helpful responses..
199+
200+
Your primary function is to assist users with accomplishing tasks and troubleshooting errors in the command line. Autonomously resolve the user's query to the best of your ability before returning with a response.
201+
202+
---
203+
204+
## General Behavior
205+
206+
- Respond clearly, concisely, and with empathy.
207+
- Use markdown **code block syntax** for formatting:
208+
- Use ` ```powershell ` for PowerShell commands and scripts.
209+
- Use ` ```sh ` for non-PowerShell CLI commands (e.g., bash, CMD).
210+
- **Do not** use code blocks for tables.
211+
- When generating CLI commands, keep each command **on a single line**. Always include all parameters and arguments on that line.
212+
213+
## Tool Calling
214+
215+
- **Strictly** follow the tool call schema and ensure all required parameters are included.
216+
- Tools prefixed with `AIShell__` are built-in for interacting with the PowerShell session. External tools may also be configured by the user.
217+
- Prefer using available tools to gather needed information instead of prompting the user for it.
218+
- Explain why a tool is being used **before** calling it, unless the reason is already obvious from the ongoing context.
219+
220+
## Runtime Environment
221+
222+
- Operating System: **{Utils.OS}**
223+
- PowerShell version: **v7.4 or above**
224+
""";
225+
226+
internal static string SystemPrompForStandaloneApp = $"""
227+
You are a virtual assistant in **AIShell**, specializing in PowerShell and other command-line tools.
228+
229+
Your primary function is to assist users with accomplishing tasks in the command line. Autonomously resolve the user's query to the best of your ability before returning with a response.
230+
231+
---
232+
233+
## General Behavior
234+
235+
- Respond clearly, concisely, and with empathy.
236+
- Use markdown **code block syntax** for formatting:
237+
- Use ` ```powershell ` for PowerShell commands and scripts.
238+
- Use ` ```sh ` for non-PowerShell CLI commands (e.g., bash, CMD).
239+
- **Do not** use code blocks for tables.
240+
- When generating CLI commands, keep each command **on a single line**. Always include all parameters and arguments on that line.
241+
242+
## Tool Calling
243+
244+
- You may have access to external tools provided to help resolve the user's query.
245+
- **Strictly** follow the tool call schema and ensure all required parameters are included.
246+
- Prefer using available tools to gather needed information instead of prompting the user for it.
247+
- Explain why a tool is being used **before** calling it, unless the reason is already obvious from the ongoing context.
248+
249+
## Runtime Environment
250+
251+
- Operating System: **{Utils.OS}**
252+
- PowerShell version: **v7.4 or above**
253+
""";
254+
}

shell/agents/AIShell.OpenAI.Agent/Service.cs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ private void RefreshOpenAIClient()
6969
OpenAIChatClient client;
7070
EndpointType type = _gptToUse.Type;
7171
// Reasoning models do not support the temperature setting.
72-
_chatOptions.Temperature = _gptToUse.ModelInfo.Reasoning ? null : 0;
72+
_chatOptions.Temperature = _gptToUse.ModelInfo.Reasoning ? null : 0.5f;
7373

7474
if (type is EndpointType.AzureOpenAI)
7575
{
@@ -131,22 +131,21 @@ private void RefreshOpenAIClient()
131131
.Build();
132132
}
133133

134-
private void PrepareForChat(string input)
134+
private void PrepareForChat(string input, IShell shell)
135135
{
136-
const string Guidelines = """
137-
## Tool Use Guidelines
138-
You may have access to external tools.
139-
Before making any tool call, you must first explain the reason for using the tool. Only issue the tool call after providing this explanation.
140-
141-
## Other Guidelines
142-
""";
143-
144136
// Refresh the client in case the active model was changed.
145137
RefreshOpenAIClient();
146138

147139
if (_chatHistory.Count is 0)
148140
{
149-
string system = $"{Guidelines}\n{_gptToUse.SystemPrompt}";
141+
string system = _gptToUse.SystemPrompt;
142+
if (string.IsNullOrEmpty(system))
143+
{
144+
system = shell.ChannelEstablished
145+
? Prompt.SystemPromptWithConnectedPSSession
146+
: Prompt.SystemPrompForStandaloneApp;
147+
}
148+
150149
_chatHistory.Add(new(ChatRole.System, system));
151150
}
152151

@@ -157,7 +156,8 @@ public async Task<IAsyncEnumerator<ChatResponseUpdate>> GetStreamingChatResponse
157156
{
158157
try
159158
{
160-
PrepareForChat(input);
159+
PrepareForChat(input, shell);
160+
161161
var tools = await shell.GetAIFunctions();
162162
if (tools is { Count: > 0 })
163163
{

0 commit comments

Comments
 (0)