Skip to content

Commit 2d912ff

Browse files
authored
feat: Relace edit tool (#647)
# Motivation <!-- Why is this change necessary? --> # Content <!-- Please include a summary of the change --> # Testing <!-- How was the change tested? --> # Please check the following before marking your PR as ready for review - [ ] I have added tests for my changes - [ ] I have updated the documentation or added new documentation as needed --------- Co-authored-by: kopekC <[email protected]>
1 parent f302af3 commit 2d912ff

File tree

4 files changed

+272
-5
lines changed

4 files changed

+272
-5
lines changed

src/codegen/extensions/langchain/agent.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@
1313
from .tools import (
1414
CreateFileTool,
1515
DeleteFileTool,
16-
EditFileTool,
1716
ListDirectoryTool,
1817
MoveSymbolTool,
18+
RelaceEditTool,
1919
RenameFileTool,
2020
ReplacementEditTool,
2121
RevealSymbolTool,
2222
SearchTool,
23-
SemanticEditTool,
23+
# SemanticEditTool,
2424
ViewFileTool,
2525
)
2626

@@ -62,14 +62,15 @@ def create_codebase_agent(
6262
ViewFileTool(codebase),
6363
ListDirectoryTool(codebase),
6464
SearchTool(codebase),
65-
EditFileTool(codebase),
65+
# EditFileTool(codebase),
6666
CreateFileTool(codebase),
6767
DeleteFileTool(codebase),
6868
RenameFileTool(codebase),
6969
MoveSymbolTool(codebase),
7070
RevealSymbolTool(codebase),
71-
SemanticEditTool(codebase),
71+
# SemanticEditTool(codebase),
7272
ReplacementEditTool(codebase),
73+
RelaceEditTool(codebase),
7374
# SemanticSearchTool(codebase),
7475
# =====[ Github Integration ]=====
7576
# Enable Github integration

src/codegen/extensions/langchain/tools.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
linear_search_issues_tool,
1717
)
1818
from codegen.extensions.tools.link_annotation import add_links_to_message
19+
from codegen.extensions.tools.relace_edit import relace_edit
1920
from codegen.extensions.tools.replacement_edit import replacement_edit
2021
from codegen.extensions.tools.reveal_symbol import reveal_symbol
2122
from codegen.extensions.tools.search import search
@@ -37,6 +38,7 @@
3738
view_file,
3839
view_pr,
3940
)
41+
from ..tools.relace_edit_prompts import RELACE_EDIT_PROMPT
4042
from ..tools.semantic_edit_prompts import FILE_EDIT_PROMPT
4143

4244

@@ -729,9 +731,10 @@ def get_workspace_tools(codebase: Codebase) -> list["BaseTool"]:
729731
RevealSymbolTool(codebase),
730732
RunBashCommandTool(), # Note: This tool doesn't need the codebase
731733
SearchTool(codebase),
732-
SemanticEditTool(codebase),
734+
# SemanticEditTool(codebase),
733735
SemanticSearchTool(codebase),
734736
ViewFileTool(codebase),
737+
RelaceEditTool(codebase),
735738
# Github
736739
GithubCreatePRTool(codebase),
737740
GithubCreatePRCommentTool(codebase),
@@ -788,3 +791,47 @@ def _run(
788791
count=count,
789792
)
790793
return result.render()
794+
795+
796+
# Brief description for the Relace Edit tool
797+
_RELACE_EDIT_BRIEF = """Tool for file editing using the Relace Instant Apply API.
798+
This high-speed code generation engine optimizes for real-time performance at 2000 tokens/second.
799+
800+
Provide an edit snippet that describes the changes you want to make, with helpful comments to indicate unchanged sections, like so:
801+
```
802+
// ... keep existing imports ...
803+
804+
// Add new function
805+
function calculateDiscount(price, discountPercent) {
806+
return price * (discountPercent / 100);
807+
}
808+
809+
// ... keep existing code ...
810+
```
811+
812+
The API will merge your edit snippet with the existing code to produce the final result.
813+
The API key will be automatically retrieved from the RELACE_API environment variable.
814+
"""
815+
816+
817+
class RelaceEditInput(BaseModel):
818+
"""Input for Relace editing."""
819+
820+
filepath: str = Field(..., description="Path of the file relative to workspace root")
821+
edit_snippet: str = Field(..., description=RELACE_EDIT_PROMPT)
822+
823+
824+
class RelaceEditTool(BaseTool):
825+
"""Tool for editing files using the Relace Instant Apply API."""
826+
827+
name: ClassVar[str] = "relace_edit"
828+
description: ClassVar[str] = _RELACE_EDIT_BRIEF
829+
args_schema: ClassVar[type[BaseModel]] = RelaceEditInput
830+
codebase: Codebase = Field(exclude=True)
831+
832+
def __init__(self, codebase: Codebase) -> None:
833+
super().__init__(codebase=codebase)
834+
835+
def _run(self, filepath: str, edit_snippet: str) -> str:
836+
result = relace_edit(self.codebase, filepath, edit_snippet)
837+
return result.render()
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
"""Tool for making edits to files using the Relace Instant Apply API."""
2+
3+
import difflib
4+
import os
5+
from typing import ClassVar, Optional
6+
7+
import requests
8+
from pydantic import Field
9+
10+
from codegen.sdk.core.codebase import Codebase
11+
12+
from .observation import Observation
13+
from .view_file import add_line_numbers
14+
15+
16+
class RelaceEditObservation(Observation):
17+
"""Response from making edits to a file using Relace Instant Apply API."""
18+
19+
filepath: str = Field(
20+
description="Path to the edited file",
21+
)
22+
diff: Optional[str] = Field(
23+
default=None,
24+
description="Unified diff showing the changes made",
25+
)
26+
new_content: Optional[str] = Field(
27+
default=None,
28+
description="New content with line numbers",
29+
)
30+
line_count: Optional[int] = Field(
31+
default=None,
32+
description="Total number of lines in file",
33+
)
34+
35+
str_template: ClassVar[str] = "Edited file {filepath} using Relace Instant Apply"
36+
37+
38+
def generate_diff(original: str, modified: str) -> str:
39+
"""Generate a unified diff between two strings.
40+
41+
Args:
42+
original: Original content
43+
modified: Modified content
44+
45+
Returns:
46+
Unified diff as a string
47+
"""
48+
original_lines = original.splitlines(keepends=True)
49+
modified_lines = modified.splitlines(keepends=True)
50+
51+
diff = difflib.unified_diff(
52+
original_lines,
53+
modified_lines,
54+
fromfile="original",
55+
tofile="modified",
56+
lineterm="",
57+
)
58+
59+
return "".join(diff)
60+
61+
62+
def get_relace_api_key() -> str:
63+
"""Get the Relace API key from environment variables.
64+
65+
Returns:
66+
The Relace API key
67+
68+
Raises:
69+
ValueError: If the API key is not found
70+
"""
71+
api_key = os.environ.get("RELACE_API")
72+
if not api_key:
73+
msg = "RELACE_API environment variable not found. Please set it in your .env file."
74+
raise ValueError(msg)
75+
return api_key
76+
77+
78+
def apply_relace_edit(api_key: str, initial_code: str, edit_snippet: str, stream: bool = False) -> str:
79+
"""Apply an edit using the Relace Instant Apply API.
80+
81+
Args:
82+
api_key: Relace API key
83+
initial_code: The existing code to modify
84+
edit_snippet: The edit snippet containing the modifications
85+
stream: Whether to enable streaming response
86+
87+
Returns:
88+
The merged code
89+
90+
Raises:
91+
Exception: If the API request fails
92+
"""
93+
url = "https://instantapply.endpoint.relace.run/v1/code/apply"
94+
headers = {"Content-Type": "application/json", "Authorization": f"Bearer {api_key}"}
95+
96+
data = {"initialCode": initial_code, "editSnippet": edit_snippet, "stream": stream}
97+
98+
try:
99+
response = requests.post(url, headers=headers, json=data)
100+
response.raise_for_status()
101+
return response.json()["mergedCode"]
102+
except Exception as e:
103+
msg = f"Relace API request failed: {e!s}"
104+
raise Exception(msg)
105+
106+
107+
def relace_edit(codebase: Codebase, filepath: str, edit_snippet: str, api_key: Optional[str] = None) -> RelaceEditObservation:
108+
"""Edit a file using the Relace Instant Apply API.
109+
110+
Args:
111+
codebase: Codebase object
112+
filepath: Path to the file to edit
113+
edit_snippet: The edit snippet containing the modifications
114+
api_key: Optional Relace API key. If not provided, will be retrieved from environment variables.
115+
116+
Returns:
117+
RelaceEditObservation with the results
118+
"""
119+
try:
120+
file = codebase.get_file(filepath)
121+
except ValueError:
122+
msg = f"File not found: {filepath}"
123+
raise FileNotFoundError(msg)
124+
125+
# Get the original content
126+
original_content = file.content
127+
original_lines = original_content.split("\n")
128+
129+
# Get API key if not provided
130+
if api_key is None:
131+
try:
132+
api_key = get_relace_api_key()
133+
except ValueError as e:
134+
return RelaceEditObservation(
135+
status="error",
136+
error=str(e),
137+
filepath=filepath,
138+
)
139+
140+
# Apply the edit using Relace API
141+
try:
142+
merged_code = apply_relace_edit(api_key, original_content, edit_snippet)
143+
except Exception as e:
144+
return RelaceEditObservation(
145+
status="error",
146+
error=str(e),
147+
filepath=filepath,
148+
)
149+
150+
# Generate diff
151+
diff = generate_diff(original_content, merged_code)
152+
153+
# Apply the edit to the file
154+
file.edit(merged_code)
155+
codebase.commit()
156+
157+
return RelaceEditObservation(
158+
status="success",
159+
filepath=filepath,
160+
diff=diff,
161+
new_content=add_line_numbers(merged_code),
162+
line_count=len(merged_code.split("\n")),
163+
)
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
"""Prompts for the Relace edit tool."""
2+
3+
RELACE_EDIT_PROMPT = """Edit a file using the Relace Instant Apply API.
4+
5+
The Relace Instant Apply API is a high-speed code generation engine optimized for real-time performance at 2000 tokens/second. It splits code generation into two specialized steps:
6+
7+
1. Hard Reasoning: Uses SOTA models like Claude for complex code understanding
8+
2. Fast Integration: Rapidly merges edits into existing code
9+
10+
To use this tool, provide:
11+
1. The path to the file you want to edit
12+
2. An edit snippet that describes the changes you want to make
13+
14+
The edit snippet should:
15+
- Include complete code blocks that will appear in the final output
16+
- Clearly indicate which parts of the code remain unchanged with comments like "// ... rest of code ..."
17+
- Maintain correct indentation and code structure
18+
19+
Example edit snippet:
20+
```
21+
// ... keep existing imports ...
22+
23+
// Add new function
24+
function calculateDiscount(price, discountPercent) {
25+
return price * (discountPercent / 100);
26+
}
27+
28+
// ... keep existing code ...
29+
```
30+
31+
The API will merge your edit snippet with the existing code to produce the final result.
32+
"""
33+
34+
RELACE_EDIT_SYSTEM_PROMPT = """You are an expert at creating edit snippets for the Relace Instant Apply API.
35+
36+
Your job is to create an edit snippet that describes how to modify the provided existing code according to user specifications.
37+
38+
Follow these guidelines:
39+
1. Focus only on the MODIFICATION REQUEST, not other aspects of the code
40+
2. Abbreviate unchanged sections with "// ... rest of headers/sections/code ..." (be descriptive in the comment)
41+
3. Indicate the location and nature of modifications with comments and ellipses
42+
4. Preserve indentation and code structure exactly as it should appear in the final code
43+
5. Do not output lines that will not be in the final code after merging
44+
6. If removing a section, provide relevant context so it's clear what should be removed
45+
46+
Do NOT provide commentary or explanations - only the code with a focus on the modifications.
47+
"""
48+
49+
RELACE_EDIT_USER_PROMPT = """EXISTING CODE:
50+
{initial_code}
51+
52+
MODIFICATION REQUEST:
53+
{user_instructions}
54+
55+
Create an edit snippet that can be used with the Relace Instant Apply API to implement these changes.
56+
"""

0 commit comments

Comments
 (0)