summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--continuedev/src/continuedev/core/abstract_sdk.py12
-rw-r--r--continuedev/src/continuedev/core/autopilot.py1
-rw-r--r--continuedev/src/continuedev/core/main.py24
-rw-r--r--continuedev/src/continuedev/core/policy.py8
-rw-r--r--continuedev/src/continuedev/core/sdk.py14
-rw-r--r--continuedev/src/continuedev/libs/llm/__init__.py6
-rw-r--r--continuedev/src/continuedev/libs/llm/hf_inference_api.py4
-rw-r--r--continuedev/src/continuedev/libs/llm/openai.py15
-rw-r--r--continuedev/src/continuedev/recipes/AddTransformRecipe/README.md8
-rw-r--r--continuedev/src/continuedev/recipes/AddTransformRecipe/dlt_transform_docs.md135
-rw-r--r--continuedev/src/continuedev/recipes/AddTransformRecipe/main.py27
-rw-r--r--continuedev/src/continuedev/recipes/AddTransformRecipe/steps.py89
-rw-r--r--continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py2
-rw-r--r--continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py8
-rw-r--r--continuedev/src/continuedev/server/ide.py2
-rw-r--r--continuedev/src/continuedev/steps/core/core.py37
-rw-r--r--continuedev/src/continuedev/steps/main.py13
-rw-r--r--continuedev/src/continuedev/steps/steps_on_startup.py5
-rw-r--r--docs/docs/walkthroughs/create-a-recipe.md4
19 files changed, 371 insertions, 43 deletions
diff --git a/continuedev/src/continuedev/core/abstract_sdk.py b/continuedev/src/continuedev/core/abstract_sdk.py
index 1c800875..417971cd 100644
--- a/continuedev/src/continuedev/core/abstract_sdk.py
+++ b/continuedev/src/continuedev/core/abstract_sdk.py
@@ -1,10 +1,10 @@
-from abc import ABC, abstractmethod
+from abc import ABC, abstractmethod, abstractproperty
from typing import Coroutine, List, Union
from .config import ContinueConfig
from ..models.filesystem_edit import FileSystemEdit
from .observation import Observation
-from .main import History, Step
+from .main import ChatMessage, History, Step, ChatMessageRole
"""
@@ -83,3 +83,11 @@ class AbstractContinueSDK(ABC):
@abstractmethod
def set_loading_message(self, message: str):
pass
+
+ @abstractmethod
+ def add_chat_context(self, content: str, role: ChatMessageRole = "assistent"):
+ pass
+
+ @abstractproperty
+ def chat_context(self) -> List[ChatMessage]:
+ pass
diff --git a/continuedev/src/continuedev/core/autopilot.py b/continuedev/src/continuedev/core/autopilot.py
index b82e1fef..c979d53a 100644
--- a/continuedev/src/continuedev/core/autopilot.py
+++ b/continuedev/src/continuedev/core/autopilot.py
@@ -35,6 +35,7 @@ class Autopilot(ContinueBaseModel):
class Config:
arbitrary_types_allowed = True
+ keep_untouched = (cached_property,)
def get_full_state(self) -> FullState:
return FullState(history=self.history, active=self._active, user_input_queue=self._main_user_input_queue)
diff --git a/continuedev/src/continuedev/core/main.py b/continuedev/src/continuedev/core/main.py
index 37d80de3..3053e5a1 100644
--- a/continuedev/src/continuedev/core/main.py
+++ b/continuedev/src/continuedev/core/main.py
@@ -1,10 +1,17 @@
-from typing import Callable, Coroutine, Dict, Generator, List, Tuple, Union
+from textwrap import dedent
+from typing import Callable, Coroutine, Dict, Generator, List, Literal, Tuple, Union
from ..models.main import ContinueBaseModel
from pydantic import validator
-from ..libs.llm import LLM
from .observation import Observation
+ChatMessageRole = Literal["assistant", "user", "system"]
+
+
+class ChatMessage(ContinueBaseModel):
+ role: ChatMessageRole
+ content: str
+
class HistoryNode(ContinueBaseModel):
"""A point in history, a list of which make up History"""
@@ -12,12 +19,24 @@ class HistoryNode(ContinueBaseModel):
observation: Union[Observation, None]
depth: int
+ def to_chat_messages(self) -> List[ChatMessage]:
+ if self.step.description is None:
+ return self.step.chat_context
+ return self.step.chat_context + [ChatMessage(role="assistant", content=self.step.description)]
+
class History(ContinueBaseModel):
"""A history of steps taken and their results"""
timeline: List[HistoryNode]
current_index: int
+ def to_chat_history(self) -> List[ChatMessage]:
+ msgs = []
+ for node in self.timeline:
+ if not node.step.hide:
+ msgs += node.to_chat_messages()
+ return msgs
+
def add_node(self, node: HistoryNode):
self.timeline.insert(self.current_index + 1, node)
self.current_index += 1
@@ -113,6 +132,7 @@ class Step(ContinueBaseModel):
description: Union[str, None] = None
system_message: Union[str, None] = None
+ chat_context: List[ChatMessage] = []
class Config:
copy_on_model_validation = False
diff --git a/continuedev/src/continuedev/core/policy.py b/continuedev/src/continuedev/core/policy.py
index 8aea8de7..7661f0c4 100644
--- a/continuedev/src/continuedev/core/policy.py
+++ b/continuedev/src/continuedev/core/policy.py
@@ -3,12 +3,14 @@ from typing import List, Tuple, Type
from ..steps.chroma import AnswerQuestionChroma, EditFileChroma, CreateCodebaseIndexChroma
from ..steps.steps_on_startup import StepsOnStartupStep
from ..recipes.CreatePipelineRecipe.main import CreatePipelineRecipe
+from ..recipes.AddTransformRecipe.main import AddTransformRecipe
from .main import Step, Validator, History, Policy
from .observation import Observation, TracebackObservation, UserInputObservation
-from ..steps.main import EditHighlightedCodeStep, SolveTracebackStep, RunCodeStep, FasterEditHighlightedCodeStep, StarCoderEditHighlightedCodeStep, MessageStep, EmptyStep, SetupContinueWorkspaceStep
+from ..steps.main import EditHighlightedCodeStep, SolveTracebackStep, RunCodeStep, FasterEditHighlightedCodeStep, StarCoderEditHighlightedCodeStep, EmptyStep, SetupContinueWorkspaceStep
from ..recipes.WritePytestsRecipe.main import WritePytestsRecipe
from ..recipes.ContinueRecipeRecipe.main import ContinueStepStep
from ..steps.comment_code import CommentCodeStep
+from ..steps.core.core import MessageStep
class DemoPolicy(Policy):
@@ -28,8 +30,10 @@ class DemoPolicy(Policy):
# This could be defined with ObservationTypePolicy. Ergonomics not right though.
if "/pytest" in observation.user_input.lower():
return WritePytestsRecipe(instructions=observation.user_input)
- elif "/dlt" in observation.user_input.lower() or " dlt" in observation.user_input.lower():
+ elif "/dlt" in observation.user_input.lower():
return CreatePipelineRecipe()
+ elif "/transform" in observation.user_input.lower():
+ return AddTransformRecipe()
elif "/comment" in observation.user_input.lower():
return CommentCodeStep()
elif "/ask" in observation.user_input:
diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py
index ea90a13a..59bfc0f2 100644
--- a/continuedev/src/continuedev/core/sdk.py
+++ b/continuedev/src/continuedev/core/sdk.py
@@ -14,7 +14,7 @@ from ..libs.llm.hf_inference_api import HuggingFaceInferenceAPI
from ..libs.llm.openai import OpenAI
from .observation import Observation
from ..server.ide_protocol import AbstractIdeProtocolServer
-from .main import Context, ContinueCustomException, History, Step
+from .main import Context, ContinueCustomException, History, Step, ChatMessage, ChatMessageRole
from ..steps.core.core import *
@@ -77,9 +77,9 @@ class ContinueSDK(AbstractContinueSDK):
async def wait_for_user_confirmation(self, prompt: str):
return await self.run_step(WaitForUserConfirmationStep(prompt=prompt))
- async def run(self, commands: Union[List[str], str], cwd: str = None, name: str = None, description: str = None) -> Coroutine[str, None, None]:
+ async def run(self, commands: Union[List[str], str], cwd: str = None, name: str = None, description: str = None, handle_error: bool = True) -> Coroutine[str, None, None]:
commands = commands if isinstance(commands, List) else [commands]
- return (await self.run_step(ShellCommandsStep(cmds=commands, cwd=cwd, description=description, **({'name': name} if name else {})))).text
+ return (await self.run_step(ShellCommandsStep(cmds=commands, cwd=cwd, description=description, handle_error=handle_error, **({'name': name} if name else {})))).text
async def edit_file(self, filename: str, prompt: str, name: str = None, description: str = None, range: Range = None):
filepath = await self._ensure_absolute_path(filename)
@@ -136,3 +136,11 @@ class ContinueSDK(AbstractContinueSDK):
def raise_exception(self, message: str, title: str, with_step: Union[Step, None] = None):
raise ContinueCustomException(message, title, with_step)
+
+ def add_chat_context(self, content: str, role: ChatMessageRole = "assistent"):
+ self.history.timeline[self.history.current_index].step.chat_context.append(
+ ChatMessage(content=content, role=role))
+
+ @property
+ def chat_context(self) -> List[ChatMessage]:
+ return self.history.to_chat_history()
diff --git a/continuedev/src/continuedev/libs/llm/__init__.py b/continuedev/src/continuedev/libs/llm/__init__.py
index 6bae2222..24fd34be 100644
--- a/continuedev/src/continuedev/libs/llm/__init__.py
+++ b/continuedev/src/continuedev/libs/llm/__init__.py
@@ -1,4 +1,6 @@
-from typing import Union
+from typing import List, Union
+
+from ...core.main import ChatMessage
from ...models.main import AbstractModel
from pydantic import BaseModel
@@ -6,7 +8,7 @@ from pydantic import BaseModel
class LLM(BaseModel):
system_message: Union[str, None] = None
- def complete(self, prompt: str, **kwargs):
+ def complete(self, prompt: str, with_history: List[ChatMessage] = [], **kwargs):
"""Return the completion of the text with the given temperature."""
raise
diff --git a/continuedev/src/continuedev/libs/llm/hf_inference_api.py b/continuedev/src/continuedev/libs/llm/hf_inference_api.py
index 734da160..1586c620 100644
--- a/continuedev/src/continuedev/libs/llm/hf_inference_api.py
+++ b/continuedev/src/continuedev/libs/llm/hf_inference_api.py
@@ -1,3 +1,5 @@
+from typing import List
+from ...core.main import ChatMessage
from ..llm import LLM
import requests
@@ -9,7 +11,7 @@ class HuggingFaceInferenceAPI(LLM):
api_key: str
model: str = "bigcode/starcoder"
- def complete(self, prompt: str, **kwargs):
+ def complete(self, prompt: str, with_history: List[ChatMessage] = [], **kwargs):
"""Return the completion of the text with the given temperature."""
API_URL = f"https://api-inference.huggingface.co/models/{self.model}"
headers = {
diff --git a/continuedev/src/continuedev/libs/llm/openai.py b/continuedev/src/continuedev/libs/llm/openai.py
index 10801465..6a537afd 100644
--- a/continuedev/src/continuedev/libs/llm/openai.py
+++ b/continuedev/src/continuedev/libs/llm/openai.py
@@ -1,6 +1,7 @@
import asyncio
import time
from typing import Any, Dict, Generator, List, Union
+from ...core.main import ChatMessage
import openai
import aiohttp
from ..llm import LLM
@@ -62,7 +63,7 @@ class OpenAI(LLM):
for chunk in generator:
yield chunk.choices[0].text
- def complete(self, prompt: str, **kwargs) -> str:
+ def complete(self, prompt: str, with_history: List[ChatMessage] = [], **kwargs) -> str:
t1 = time.time()
self.completion_count += 1
@@ -70,15 +71,17 @@ class OpenAI(LLM):
"frequency_penalty": 0, "presence_penalty": 0, "stream": False} | kwargs
if args["model"] == "gpt-3.5-turbo":
- messages = [{
- "role": "user",
- "content": prompt
- }]
+ messages = []
if self.system_message:
- messages.insert(0, {
+ messages.append({
"role": "system",
"content": self.system_message
})
+ messages += [msg.dict() for msg in with_history]
+ messages.append({
+ "role": "user",
+ "content": prompt
+ })
resp = openai.ChatCompletion.create(
messages=messages,
**args,
diff --git a/continuedev/src/continuedev/recipes/AddTransformRecipe/README.md b/continuedev/src/continuedev/recipes/AddTransformRecipe/README.md
new file mode 100644
index 00000000..d735e0cd
--- /dev/null
+++ b/continuedev/src/continuedev/recipes/AddTransformRecipe/README.md
@@ -0,0 +1,8 @@
+# AddTransformRecipe
+
+Uses the Chess.com API example to show how to add map and filter Python transforms to a dlt pipeline.
+
+Background
+- https://dlthub.com/docs/general-usage/resource#filter-transform-and-pivot-data
+- https://dlthub.com/docs/customizations/customizing-pipelines/renaming_columns
+- https://dlthub.com/docs/customizations/customizing-pipelines/pseudonymizing_columns \ No newline at end of file
diff --git a/continuedev/src/continuedev/recipes/AddTransformRecipe/dlt_transform_docs.md b/continuedev/src/continuedev/recipes/AddTransformRecipe/dlt_transform_docs.md
new file mode 100644
index 00000000..658b285f
--- /dev/null
+++ b/continuedev/src/continuedev/recipes/AddTransformRecipe/dlt_transform_docs.md
@@ -0,0 +1,135 @@
+# Customize resources
+## Filter, transform and pivot data
+
+You can attach any number of transformations that are evaluated on item per item basis to your resource. The available transformation types:
+- map - transform the data item (resource.add_map)
+- filter - filter the data item (resource.add_filter)
+- yield map - a map that returns iterator (so single row may generate many rows - resource.add_yield_map)
+
+Example: We have a resource that loads a list of users from an api endpoint. We want to customize it so:
+- we remove users with user_id == 'me'
+- we anonymize user data
+Here's our resource:
+```python
+import dlt
+
+@dlt.resource(write_disposition='replace')
+def users():
+ ...
+ users = requests.get(...)
+ ...
+ yield users
+```
+
+Here's our script that defines transformations and loads the data.
+```python
+from pipedrive import users
+
+def anonymize_user(user_data):
+ user_data['user_id'] = hash_str(user_data['user_id'])
+ user_data['user_email'] = hash_str(user_data['user_email'])
+ return user_data
+
+# add the filter and anonymize function to users resource and enumerate
+for user in users().add_filter(lambda user: user['user_id'] != 'me').add_map(anonymize_user):
+print(user)
+```
+
+Here is a more complex example of a filter transformation:
+
+ # Renaming columns
+ ## Renaming columns by replacing the special characters
+
+ In the example below, we create a dummy source with special characters in the name. We then write a function that we intend to apply to the resource to modify its output (i.e. replacing the German umlaut): replace_umlauts_in_dict_keys.
+ ```python
+ import dlt
+
+ # create a dummy source with umlauts (special characters) in key names (um)
+ @dlt.source
+ def dummy_source(prefix: str = None):
+ @dlt.resource
+ def dummy_data():
+ for _ in range(100):
+ yield {f'Objekt_{_}':{'Größe':_, 'Äquivalenzprüfung':True}}
+ return dummy_data(),
+
+ def replace_umlauts_in_dict_keys(d):
+ # Replaces umlauts in dictionary keys with standard characters.
+ umlaut_map = {'ä': 'ae', 'ö': 'oe', 'ü': 'ue', 'ß': 'ss', 'Ä': 'Ae', 'Ö': 'Oe', 'Ü': 'Ue'}
+ result = {}
+ for k, v in d.items():
+ new_key = ''.join(umlaut_map.get(c, c) for c in k)
+ if isinstance(v, dict):
+ result[new_key] = replace_umlauts_in_dict_keys(v)
+ else:
+ result[new_key] = v
+ return result
+
+ # We can add the map function to the resource
+
+ # 1. Create an instance of the source so you can edit it.
+ data_source = dummy_source()
+
+ # 2. Modify this source instance's resource
+ data_source = data_source.dummy_data().add_map(replace_umlauts_in_dict_keys)
+
+ # 3. Inspect your result
+ for row in data_source:
+ print(row)
+
+ # {'Objekt_0': {'Groesse': 0, 'Aequivalenzpruefung': True}}
+ # ...
+ ```
+
+Here is a more complex example of a map transformation:
+
+# Pseudonymizing columns
+## Pseudonymizing (or anonymizing) columns by replacing the special characters
+Pseudonymization is a deterministic way to hide personally identifiable info (PII), enabling us to consistently achieve the same mapping. If instead you wish to anonymize, you can delete the data, or replace it with a constant. In the example below, we create a dummy source with a PII column called 'name', which we replace with deterministic hashes (i.e. replacing the German umlaut).
+
+```python
+import dlt
+import hashlib
+
+@dlt.source
+def dummy_source(prefix: str = None):
+ @dlt.resource
+ def dummy_data():
+ for _ in range(3):
+ yield {'id':_, 'name': f'Jane Washington {_}'}
+ return dummy_data(),
+
+def pseudonymize_name(doc):
+ Pseudonmyisation is a deterministic type of PII-obscuring
+ Its role is to allow identifying users by their hash, without revealing the underlying info.
+
+ # add a constant salt to generate
+ salt = 'WI@N57%zZrmk#88c'
+ salted_string = doc['name'] + salt
+ sh = hashlib.sha256()
+ sh.update(salted_string.encode())
+ hashed_string = sh.digest().hex()
+ doc['name'] = hashed_string
+ return doc
+
+ # run it as is
+ for row in dummy_source().dummy_data().add_map(pseudonymize_name):
+ print(row)
+
+ #{'id': 0, 'name': '96259edb2b28b48bebce8278c550e99fbdc4a3fac8189e6b90f183ecff01c442'}
+ #{'id': 1, 'name': '92d3972b625cbd21f28782fb5c89552ce1aa09281892a2ab32aee8feeb3544a1'}
+ #{'id': 2, 'name': '443679926a7cff506a3b5d5d094dc7734861352b9e0791af5d39db5a7356d11a'}
+
+ # Or create an instance of the data source, modify the resource and run the source.
+
+ # 1. Create an instance of the source so you can edit it.
+ data_source = dummy_source()
+ # 2. Modify this source instance's resource
+ data_source = data_source.dummy_data().add_map(replace_umlauts_in_dict_keys)
+ # 3. Inspect your result
+ for row in data_source:
+ print(row)
+
+ pipeline = dlt.pipeline(pipeline_name='example', destination='bigquery', dataset_name='normalized_data')
+ load_info = pipeline.run(data_source)
+``` \ No newline at end of file
diff --git a/continuedev/src/continuedev/recipes/AddTransformRecipe/main.py b/continuedev/src/continuedev/recipes/AddTransformRecipe/main.py
new file mode 100644
index 00000000..e9a998e3
--- /dev/null
+++ b/continuedev/src/continuedev/recipes/AddTransformRecipe/main.py
@@ -0,0 +1,27 @@
+from textwrap import dedent
+
+from ...core.main import Step
+from ...core.sdk import ContinueSDK
+from ...steps.core.core import WaitForUserInputStep
+from ...steps.core.core import MessageStep
+from .steps import SetUpChessPipelineStep, AddTransformStep
+
+
+class AddTransformRecipe(Step):
+ hide: bool = True
+
+ async def run(self, sdk: ContinueSDK):
+ text_observation = await sdk.run_step(
+ MessageStep(message=dedent("""\
+ This recipe will walk you through the process of adding a transform to a dlt pipeline that uses the chess.com API source. With the help of Continue, you will:
+ - Set up a dlt pipeline for the chess.com API
+ - Add a filter or map transform to the pipeline
+ - Run the pipeline and view the transformed data in a Streamlit app"""), name="Add transformation to a dlt pipeline") >>
+ SetUpChessPipelineStep() >>
+ WaitForUserInputStep(
+ prompt="How do you want to transform the Chess.com API data before loading it? For example, you could use the `python-chess` library to decode the moves or filter out certain games")
+ )
+ await sdk.run_step(
+ AddTransformStep(
+ transform_description=text_observation.text)
+ )
diff --git a/continuedev/src/continuedev/recipes/AddTransformRecipe/steps.py b/continuedev/src/continuedev/recipes/AddTransformRecipe/steps.py
new file mode 100644
index 00000000..7bb0fc23
--- /dev/null
+++ b/continuedev/src/continuedev/recipes/AddTransformRecipe/steps.py
@@ -0,0 +1,89 @@
+import os
+from textwrap import dedent
+
+from ...models.main import Range
+from ...models.filesystem import RangeInFile
+from ...steps.core.core import MessageStep
+from ...core.sdk import Models
+from ...core.observation import DictObservation
+from ...models.filesystem_edit import AddFile
+from ...core.main import Step
+from ...core.sdk import ContinueSDK
+
+AI_ASSISTED_STRING = "(✨ AI-Assisted ✨)"
+
+
+class SetUpChessPipelineStep(Step):
+ hide: bool = True
+ name: str = "Setup Chess.com API dlt Pipeline"
+
+ async def describe(self, models: Models):
+ return "This step will create a new dlt pipeline that loads data from the chess.com API."
+
+ async def run(self, sdk: ContinueSDK):
+
+ # running commands to get started when creating a new dlt pipeline
+ await sdk.run([
+ 'python3 -m venv env',
+ 'source env/bin/activate',
+ 'pip install dlt',
+ 'dlt --non-interactive init chess duckdb',
+ 'pip install -r requirements.txt',
+ 'pip install pandas streamlit' # Needed for the pipeline show step later
+ ], name="Set up Python environment", description=dedent(f"""\
+ Running the following commands:
+ - `python3 -m venv env`: Create a Python virtual environment
+ - `source env/bin/activate`: Activate the virtual environment
+ - `pip install dlt`: Install dlt
+ - `dlt init chess duckdb`: Create a new dlt pipeline called "chess" that loads data into a local DuckDB instance
+ - `pip install -r requirements.txt`: Install the Python dependencies for the pipeline"""))
+
+
+class AddTransformStep(Step):
+ hide: bool = True
+
+ # e.g. "Use the `python-chess` library to decode the moves in the game data"
+ transform_description: str
+
+ async def run(self, sdk: ContinueSDK):
+ source_name = 'chess'
+ filename = f'{source_name}_pipeline.py'
+ abs_filepath = os.path.join(sdk.ide.workspace_directory, filename)
+
+ await sdk.run_step(MessageStep(message=dedent("""\
+ This step will customize your resource function with a transform of your choice:
+ - Add a filter or map transformation depending on your request
+ - Load the data into a local DuckDB instance
+ - Open up a Streamlit app for you to view the data"""), name="Write transformation function"))
+
+ # Open the file and highlight the function to be edited
+ await sdk.ide.setFileOpen(abs_filepath)
+ await sdk.ide.highlightCode(range_in_file=RangeInFile(
+ filepath=abs_filepath,
+ range=Range.from_shorthand(47, 0, 51, 0)
+ ))
+
+ with open(os.path.join(os.path.dirname(__file__), 'dlt_transform_docs.md')) as f:
+ dlt_transform_docs = f.read()
+
+ prompt = dedent(f"""\
+ Task: Write a transform function using the description below and then use `add_map` or `add_filter` from the `dlt` library to attach it a resource.
+
+ Description: {self.transform_description}
+
+ Here are some docs pages that will help you better understand how to use `dlt`.
+
+ {dlt_transform_docs}""")
+
+ # edit the pipeline to add a tranform function and attach it to a resource
+ await sdk.edit_file(
+ filename=filename,
+ prompt=prompt,
+ name=f"Writing transform function {AI_ASSISTED_STRING}"
+ )
+
+ # run the pipeline and load the data
+ await sdk.run(f'python3 {filename}', name="Run the pipeline", description=f"Running `python3 {filename}` to load the data into a local DuckDB instance")
+
+ # run a streamlit app to show the data
+ await sdk.run(f'dlt pipeline {source_name}_pipeline show', name="Show data in a Streamlit app", description=f"Running `dlt pipeline {source_name} show` to show the data in a Streamlit app, where you can view and play with the data.")
diff --git a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py
index 39e1ba42..818168ba 100644
--- a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py
+++ b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/main.py
@@ -3,7 +3,7 @@ from textwrap import dedent
from ...core.main import Step
from ...core.sdk import ContinueSDK
from ...steps.core.core import WaitForUserInputStep
-from ...steps.main import MessageStep
+from ...steps.core.core import MessageStep
from .steps import SetupPipelineStep, ValidatePipelineStep, RunQueryStep
diff --git a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py
index 3b9a8c85..e59cc51c 100644
--- a/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py
+++ b/continuedev/src/continuedev/recipes/CreatePipelineRecipe/steps.py
@@ -5,7 +5,7 @@ import time
from ...models.main import Range
from ...models.filesystem import RangeInFile
-from ...steps.main import MessageStep
+from ...steps.core.core import MessageStep
from ...core.sdk import Models
from ...core.observation import DictObservation, InternalErrorObservation
from ...models.filesystem_edit import AddFile, FileEdit
@@ -51,7 +51,7 @@ class SetupPipelineStep(Step):
# editing the resource function to call the requested API
resource_function_range = Range.from_shorthand(15, 0, 29, 0)
- await sdk.ide.highlightCode(RangeInFile(filepath=os.path.join(await sdk.ide.getWorkspaceDirectory(), filename), range=resource_function_range), "#00ff0022")
+ await sdk.ide.highlightCode(RangeInFile(filepath=os.path.join(await sdk.ide.getWorkspaceDirectory(), filename), range=resource_function_range))
# sdk.set_loading_message("Writing code to call the API...")
await sdk.edit_file(
@@ -86,7 +86,7 @@ class ValidatePipelineStep(Step):
# """)))
# test that the API call works
- output = await sdk.run(f'python3 {filename}', name="Test the pipeline", description=f"Running `python3 {filename}` to test loading data from the API")
+ output = await sdk.run(f'python3 {filename}', name="Test the pipeline", description=f"Running `python3 {filename}` to test loading data from the API", handle_error=False)
# If it fails, return the error
if "Traceback" in output or "SyntaxError" in output:
@@ -157,7 +157,7 @@ class RunQueryStep(Step):
hide: bool = True
async def run(self, sdk: ContinueSDK):
- output = await sdk.run('env/bin/python3 query.py', name="Run test query", description="Running `env/bin/python3 query.py` to test that the data was loaded into DuckDB as expected")
+ output = await sdk.run('env/bin/python3 query.py', name="Run test query", description="Running `env/bin/python3 query.py` to test that the data was loaded into DuckDB as expected", handle_error=False)
if "Traceback" in output or "SyntaxError" in output:
suggestion = sdk.models.gpt35.complete(dedent(f"""\
diff --git a/continuedev/src/continuedev/server/ide.py b/continuedev/src/continuedev/server/ide.py
index 5826f15f..f4ea1071 100644
--- a/continuedev/src/continuedev/server/ide.py
+++ b/continuedev/src/continuedev/server/ide.py
@@ -138,7 +138,7 @@ class IdeProtocolServer(AbstractIdeProtocolServer):
"sessionId": session_id
})
- async def highlightCode(self, range_in_file: RangeInFile, color: str):
+ async def highlightCode(self, range_in_file: RangeInFile, color: str = "#00ff0022"):
await self._send_json("highlightCode", {
"rangeInFile": range_in_file.dict(),
"color": color
diff --git a/continuedev/src/continuedev/steps/core/core.py b/continuedev/src/continuedev/steps/core/core.py
index dfd765eb..40e992e7 100644
--- a/continuedev/src/continuedev/steps/core/core.py
+++ b/continuedev/src/continuedev/steps/core/core.py
@@ -1,4 +1,5 @@
# These steps are depended upon by ContinueSDK
+import os
import subprocess
from textwrap import dedent
from typing import Coroutine, List, Union
@@ -23,6 +24,17 @@ class ReversibleStep(Step):
raise NotImplementedError
+class MessageStep(Step):
+ name: str = "Message"
+ message: str
+
+ async def describe(self, models: Models) -> Coroutine[str, None, None]:
+ return self.message
+
+ async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]:
+ return TextObservation(text=self.message)
+
+
class FileSystemEditStep(ReversibleStep):
edit: FileSystemEdit
_diff: Union[EditDiff, None] = None
@@ -38,10 +50,18 @@ class FileSystemEditStep(ReversibleStep):
# Where and when should file saves happen?
+def output_contains_error(output: str) -> bool:
+ return "Traceback" in output or "SyntaxError" in output
+
+
+AI_ASSISTED_STRING = "(✨ AI-Assisted ✨)"
+
+
class ShellCommandsStep(Step):
cmds: List[str]
cwd: Union[str, None] = None
name: str = "Run Shell Commands"
+ handle_error: bool = True
_err_text: Union[str, None] = None
@@ -50,13 +70,26 @@ class ShellCommandsStep(Step):
return f"Error when running shell commands:\n```\n{self._err_text}\n```"
cmds_str = "\n".join(self.cmds)
- return (await models.gpt35()).complete(f"{cmds_str}\n\nSummarize what was done in these shell commands, using markdown bullet points:")
+ return models.gpt35.complete(f"{cmds_str}\n\nSummarize what was done in these shell commands, using markdown bullet points:")
async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]:
cwd = await sdk.ide.getWorkspaceDirectory() if self.cwd is None else self.cwd
for cmd in self.cmds:
output = await sdk.ide.runCommand(cmd)
+ if self.handle_error and output is not None and output_contains_error(output):
+ suggestion = sdk.models.gpt35.complete(dedent(f"""\
+ While running the command `{cmd}`, the following error occurred:
+
+ ```ascii
+ {output}
+ ```
+
+ This is a brief summary of the error followed by a suggestion on how it can be fixed:"""), with_context=sdk.chat_context)
+
+ sdk.raise_exception(
+ title="Error while running query", message=output, with_step=MessageStep(name=f"Suggestion to solve error {AI_ASSISTED_STRING}", message=suggestion)
+ )
return TextObservation(text=output)
@@ -116,7 +149,7 @@ class Gpt35EditCodeStep(Step):
_prompt_and_completion: str = ""
async def describe(self, models: Models) -> Coroutine[str, None, None]:
- return (await models.gpt35()).complete(f"{self._prompt_and_completion}\n\nPlease give brief a description of the changes made above using markdown bullet points:")
+ return models.gpt35.complete(f"{self._prompt_and_completion}\n\nPlease give brief a description of the changes made above using markdown bullet points:")
async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]:
rif_with_contents = []
diff --git a/continuedev/src/continuedev/steps/main.py b/continuedev/src/continuedev/steps/main.py
index 81a1e3a9..24335b4f 100644
--- a/continuedev/src/continuedev/steps/main.py
+++ b/continuedev/src/continuedev/steps/main.py
@@ -212,7 +212,7 @@ class StarCoderEditHighlightedCodeStep(Step):
_prompt_and_completion: str = ""
async def describe(self, models: Models) -> Coroutine[str, None, None]:
- return (await models.gpt35()).complete(f"{self._prompt_and_completion}\n\nPlease give brief a description of the changes made above using markdown bullet points:")
+ return models.gpt35.complete(f"{self._prompt_and_completion}\n\nPlease give brief a description of the changes made above using markdown bullet points:")
async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]:
range_in_files = await sdk.ide.getHighlightedCode()
@@ -317,17 +317,6 @@ class SolveTracebackStep(Step):
return None
-class MessageStep(Step):
- name: str = "Message"
- message: str
-
- async def describe(self, models: Models) -> Coroutine[str, None, None]:
- return self.message
-
- async def run(self, sdk: ContinueSDK) -> Coroutine[Observation, None, None]:
- return TextObservation(text=self.message)
-
-
class EmptyStep(Step):
hide: bool = True
diff --git a/continuedev/src/continuedev/steps/steps_on_startup.py b/continuedev/src/continuedev/steps/steps_on_startup.py
index cd40ff56..63dedd82 100644
--- a/continuedev/src/continuedev/steps/steps_on_startup.py
+++ b/continuedev/src/continuedev/steps/steps_on_startup.py
@@ -1,11 +1,12 @@
from ..core.main import ContinueSDK, Models, Step
from .main import UserInputStep
from ..recipes.CreatePipelineRecipe.main import CreatePipelineRecipe
-
+from ..recipes.AddTransformRecipe.main import AddTransformRecipe
step_name_to_step_class = {
"UserInputStep": UserInputStep,
- "CreatePipelineRecipe": CreatePipelineRecipe
+ "CreatePipelineRecipe": CreatePipelineRecipe,
+ "AddTransformRecipe": AddTransformRecipe
}
diff --git a/docs/docs/walkthroughs/create-a-recipe.md b/docs/docs/walkthroughs/create-a-recipe.md
index 60bfe9a8..3b80df8a 100644
--- a/docs/docs/walkthroughs/create-a-recipe.md
+++ b/docs/docs/walkthroughs/create-a-recipe.md
@@ -17,8 +17,6 @@ continue/continuedev/src/continuedev/recipes
## 1. Create a step
-
-
### a. Start by creating a subclass of Step
You should first consider what will be the parameters of your recipe. These are defined as attributes in the step, as with `input_file_path: str` below
@@ -33,7 +31,7 @@ If you'd like to override the default description of your steps, which is just t
- Return a static string
- Store state in a class attribute (prepend with a double underscore, which signifies (through Pydantic) that this is not a parameter for the Step, just internal state) during the run method, and then grab this in the describe method.
-- Use state in conjunction with the `models` parameter of the describe method to autogenerate a description with a language model. For example, if you'd used an attribute called `__code_written` to store a string representing some code that was written, you could implement describe as `return (await models.gpt35()).complete(f"{self.\_\_code_written}\n\nSummarize the changes made in the above code.")`.
+- Use state in conjunction with the `models` parameter of the describe method to autogenerate a description with a language model. For example, if you'd used an attribute called `__code_written` to store a string representing some code that was written, you could implement describe as `return models.gpt35.complete(f"{self.\_\_code_written}\n\nSummarize the changes made in the above code.")`.
## 2. Compose steps together into a complete recipe