diff options
author | Nate Sesti <33237525+sestinj@users.noreply.github.com> | 2023-10-09 18:37:27 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-10-09 18:37:27 -0700 |
commit | f09150617ed2454f3074bcf93f53aae5ae637d40 (patch) | |
tree | 5cfe614a64d921dfe58b049f426d67a8b832c71f /server/continuedev/plugins/steps/on_traceback.py | |
parent | 985304a213f620cdff3f8f65f74ed7e3b79be29d (diff) | |
download | sncontinue-f09150617ed2454f3074bcf93f53aae5ae637d40.tar.gz sncontinue-f09150617ed2454f3074bcf93f53aae5ae637d40.tar.bz2 sncontinue-f09150617ed2454f3074bcf93f53aae5ae637d40.zip |
Preview (#541)
* Strong typing (#533)
* refactor: :recycle: get rid of continuedev.src.continuedev structure
* refactor: :recycle: switching back to server folder
* feat: :sparkles: make config.py imports shorter
* feat: :bookmark: publish as pre-release vscode extension
* refactor: :recycle: refactor and add more completion params to ui
* build: :building_construction: download from preview S3
* fix: :bug: fix paths
* fix: :green_heart: package:pre-release
* ci: :green_heart: more time for tests
* fix: :green_heart: fix build scripts
* fix: :bug: fix import in run.py
* fix: :bookmark: update version to try again
* ci: 💚 Update package.json version [skip ci]
* refactor: :fire: don't check for old extensions version
* fix: :bug: small bug fixes
* fix: :bug: fix config.py import paths
* ci: 💚 Update package.json version [skip ci]
* ci: :green_heart: platform-specific builds test #1
* feat: :green_heart: ship with binary
* fix: :green_heart: fix copy statement to include.exe for windows
* fix: :green_heart: cd extension before packaging
* chore: :loud_sound: count tokens generated
* fix: :green_heart: remove npm_config_arch
* fix: :green_heart: publish as pre-release!
* chore: :bookmark: update version
* perf: :green_heart: hardcode distro paths
* fix: :bug: fix yaml syntax error
* chore: :bookmark: update version
* fix: :green_heart: update permissions and version
* feat: :bug: kill old server if needed
* feat: :lipstick: update marketplace icon for pre-release
* ci: 💚 Update package.json version [skip ci]
* feat: :sparkles: auto-reload for config.py
* feat: :wrench: update default config.py imports
* feat: :sparkles: codelens in config.py
* feat: :sparkles: select model param count from UI
* ci: 💚 Update package.json version [skip ci]
* feat: :sparkles: more model options, ollama error handling
* perf: :zap: don't show server loading immediately
* fix: :bug: fixing small UI details
* ci: 💚 Update package.json version [skip ci]
* feat: :rocket: headers param on LLM class
* fix: :bug: fix headers for openai.;y
* feat: :sparkles: highlight code on cmd+shift+L
* ci: 💚 Update package.json version [skip ci]
* feat: :lipstick: sticky top bar in gui.tsx
* fix: :loud_sound: websocket logging and horizontal scrollbar
* ci: 💚 Update package.json version [skip ci]
* feat: :sparkles: allow AzureOpenAI Service through GGML
* ci: 💚 Update package.json version [skip ci]
* fix: :bug: fix automigration
* ci: 💚 Update package.json version [skip ci]
* ci: :green_heart: upload binaries in ci, download apple silicon
* chore: :fire: remove notes
* fix: :green_heart: use curl to download binary
* fix: :green_heart: set permissions on apple silicon binary
* fix: :green_heart: testing
* fix: :green_heart: cleanup file
* fix: :green_heart: fix preview.yaml
* fix: :green_heart: only upload once per binary
* fix: :green_heart: install rosetta
* ci: :green_heart: download binary after tests
* ci: 💚 Update package.json version [skip ci]
* ci: :green_heart: prepare ci for merge to main
---------
Co-authored-by: GitHub Action <action@github.com>
Diffstat (limited to 'server/continuedev/plugins/steps/on_traceback.py')
-rw-r--r-- | server/continuedev/plugins/steps/on_traceback.py | 206 |
1 files changed, 206 insertions, 0 deletions
diff --git a/server/continuedev/plugins/steps/on_traceback.py b/server/continuedev/plugins/steps/on_traceback.py new file mode 100644 index 00000000..b72ce809 --- /dev/null +++ b/server/continuedev/plugins/steps/on_traceback.py @@ -0,0 +1,206 @@ +import os +from textwrap import dedent +from typing import Dict, List, Optional, Tuple + +from ...core.main import ChatMessage, ContinueCustomException, Step +from ...core.sdk import ContinueSDK +from ...core.steps import UserInputStep +from ...libs.util.filter_files import should_filter_path +from ...libs.util.traceback.traceback_parsers import ( + get_javascript_traceback, + get_python_traceback, + parse_python_traceback, +) +from ...models.filesystem import RangeInFile +from ...models.main import Range, Traceback, TracebackFrame +from .chat import SimpleChatStep + + +def extract_traceback_str(output: str) -> str: + tb = output.strip() + for tb_parser in [get_python_traceback, get_javascript_traceback]: + if parsed_tb := tb_parser(tb): + return parsed_tb + + +class DefaultOnTracebackStep(Step): + output: str + name: str = "Help With Traceback" + hide: bool = True + + async def find_relevant_files(self, sdk: ContinueSDK): + # Add context for any files in the traceback that are in the workspace + for line in self.output.split("\n"): + segs = line.split(" ") + for seg in segs: + if ( + seg.startswith(os.path.sep) + and os.path.exists(seg) # TODO: Use sdk.ide.fileExists + and os.path.commonprefix([seg, sdk.ide.workspace_directory]) + == sdk.ide.workspace_directory + ): + file_contents = await sdk.ide.readFile(seg) + self.chat_context.append( + ChatMessage( + role="user", + content=f"The contents of {seg}:\n```\n{file_contents}\n```", + summary="", + ) + ) + # TODO: The ideal is that these are added as context items, so then the user can see them + # And this function is where you can get arbitrarily fancy about adding context + + async def run(self, sdk: ContinueSDK): + if self.output.strip() == "": + raise ContinueCustomException( + title="No terminal open", + message="You must have a terminal open in order to automatically debug with Continue.", + ) + + if get_python_traceback(self.output) is not None and sdk.lsp is not None: + await sdk.run_step(SolvePythonTracebackStep(output=self.output)) + return + + tb = extract_traceback_str(self.output) or self.output[-8000:] + + await sdk.run_step( + UserInputStep( + user_input=f"""I got the following error, can you please help explain how to fix it?\n\n{tb}""", + ) + ) + await sdk.run_step(SimpleChatStep(name="Help With Traceback")) + + +def filter_frames(frames: List[TracebackFrame]) -> List[TracebackFrame]: + """Filter out frames that are not relevant to the user's code.""" + return list(filter(lambda x: should_filter_path(x.filepath), frames)) + + +def find_external_call( + frames: List[TracebackFrame], +) -> Optional[Tuple[TracebackFrame, TracebackFrame]]: + """Moving up from the bottom of the stack, if the frames are not user code, then find the last frame before it becomes user code.""" + if not should_filter_path(frames[-1].filepath): + # No external call, error comes directly from user code + return None + + for i in range(len(frames) - 2, -1, -1): + if not should_filter_path(frames[i].filepath): + return frames[i], frames[i + 1] + + +def get_func_source_for_frame(frame: Dict) -> str: + """Get the source for the function called in the frame.""" + pass + + +async def fetch_docs_for_external_call(external_call: Dict, next_frame: Dict) -> str: + """Fetch docs for the external call.""" + pass + + +class SolvePythonTracebackStep(Step): + output: str + name: str = "Solve Traceback" + hide: bool = True + + async def external_call_prompt( + self, sdk: ContinueSDK, external_call: Tuple[Dict, Dict], tb_string: str + ) -> str: + external_call, next_frame = external_call + source_line = external_call["source_line"] + external_func_source = get_func_source_for_frame(next_frame) + docs = await fetch_docs_for_external_call(external_call, next_frame) + + prompt = dedent( + f"""\ + I got the following error: + + {tb_string} + + I tried to call an external library like this: + + ```python + {source_line} + ``` + + This is the definition of the function I tried to call: + + ```python + {external_func_source} + ``` + + Here's the documentation for the external library I tried to call: + + {docs} + + Explain how to fix the error. + """ + ) + + return prompt + + async def normal_traceback_prompt( + self, sdk: ContinueSDK, tb: Traceback, tb_string: str + ) -> str: + function_bodies = await get_functions_from_traceback(tb, sdk) + + prompt = ( + "Here are the functions from the traceback (most recent call last):\n\n" + ) + for i, function_body in enumerate(function_bodies): + prompt += f'File "{tb.frames[i].filepath}", line {tb.frames[i].lineno}, in {tb.frames[i].function}\n\n```python\n{function_body or tb.frames[i].code}\n```\n\n' + + prompt += ( + "Here is the traceback:\n\n```\n" + + tb_string + + "\n```\n\nExplain how to fix the error." + ) + + return prompt + + async def run(self, sdk: ContinueSDK): + tb_string = get_python_traceback(self.output) + tb = parse_python_traceback(tb_string) + + if external_call := find_external_call(tb.frames): + prompt = await self.external_call_prompt(sdk, external_call, tb_string) + else: + prompt = await self.normal_traceback_prompt(sdk, tb, tb_string) + + await sdk.run_step( + UserInputStep( + user_input=prompt, + ) + ) + await sdk.run_step(SimpleChatStep(name="Help With Traceback")) + + +async def get_function_body(frame: TracebackFrame, sdk: ContinueSDK) -> Optional[str]: + """Get the function body from the traceback frame.""" + if sdk.lsp is None: + return None + + document_symbols = await sdk.lsp.document_symbol(frame.filepath) + for symbol in document_symbols: + if symbol.name == frame.function: + r = symbol.location.range + return await sdk.ide.readRangeInFile( + RangeInFile( + filepath=frame.filepath, + range=Range.from_shorthand( + r.start.line, r.start.character, r.end.line, r.end.character + ), + ) + ) + return None + + +async def get_functions_from_traceback(tb: Traceback, sdk: ContinueSDK) -> List[str]: + """Get the function bodies from the traceback.""" + function_bodies = [] + for frame in tb.frames: + if frame.function: + function_bodies.append(await get_function_body(frame, sdk)) + + return function_bodies |