1- # Copyright (c) ModelScope Contributors. All rights reserved .
1+ # Copyright (c) Alibaba, Inc. and its affiliates .
22import fnmatch
33import os
44import re
88from typing import Optional
99
1010import json
11- from ms_agent .config import Config
1211from ms_agent .llm import LLM
1312from ms_agent .llm .utils import Message , Tool
1413from ms_agent .tools .base import ToolBase
15- from ms_agent .utils import get_logger
14+ from ms_agent .utils import MAX_CONTINUE_RUNS , get_logger , retry
1615from ms_agent .utils .constants import DEFAULT_INDEX_DIR , DEFAULT_OUTPUT_DIR
16+ from openai import OpenAI
1717
1818logger = get_logger ()
1919
@@ -46,6 +46,13 @@ def __init__(self, config, **kwargs):
4646 super ().__init__ (config )
4747 self .exclude_func (getattr (config .tools , 'file_system' , None ))
4848 self .output_dir = getattr (config , 'output_dir' , DEFAULT_OUTPUT_DIR )
49+ if self .exclude_functions and 'edit_file' not in self .exclude_functions \
50+ or self .include_functions and 'edit_file' in self .include_functions :
51+ self .edit_file_config = getattr (config .tools .file_system ,
52+ 'edit_file_config' , None )
53+ self .edit_client = OpenAI (
54+ api_key = self .edit_file_config .api_key ,
55+ base_url = self .edit_file_config .base_url )
4956 self .trust_remote_code = kwargs .get ('trust_remote_code' , False )
5057 self .allow_read_all_files = getattr (
5158 getattr (config .tools , 'file_system' , {}), 'allow_read_all_files' ,
@@ -57,10 +64,8 @@ def __init__(self, config, **kwargs):
5764 index_dir = getattr (config , 'index_cache_dir' , DEFAULT_INDEX_DIR )
5865 self .index_dir = os .path .join (self .output_dir , index_dir )
5966 self .system = self .SYSTEM_FOR_ABBREVIATIONS
60- system = Config .safe_get_config (
61- self .config , 'tools.file_system.system_for_abbreviations' )
62- if system :
63- self .system = system
67+ if hasattr (self .config .tools .file_system , 'system_for_abbreviations' ):
68+ self .system = self .config .tools .file_system .system_for_abbreviations
6469
6570 async def connect (self ):
6671 logger .warning_once (
@@ -196,6 +201,65 @@ async def _get_tools_inner(self):
196201 'required' : ['path' ],
197202 'additionalProperties' : False
198203 }),
204+ Tool (
205+ tool_name = 'edit_file' ,
206+ server_name = 'file_system' ,
207+ description =
208+ ('Use this tool to make an edit to an existing file.\n \n '
209+ 'This will be read by a less intelligent model, which will quickly apply the edit. '
210+ 'You should make it clear what the edit is, while also minimizing the unchanged code you write.\n '
211+ 'When writing the edit, you should specify each edit in sequence, with the special comment '
212+ '// ... existing code ... to represent unchanged code in between edited lines.\n \n '
213+ 'For example:\n \n // ... existing code ...\n FIRST_EDIT\n // ... existing code ...\n '
214+ 'SECOND_EDIT\n // ... existing code ...\n THIRD_EDIT\n // ... existing code ...\n \n '
215+ 'You should still bias towards repeating as few lines of the original file '
216+ 'as possible to convey the change.\n '
217+ 'But, each edit should contain minimally sufficient context of unchanged lines '
218+ "around the code you're editing to resolve ambiguity.\n "
219+ 'DO NOT omit spans of pre-existing code (or comments) without using the '
220+ '// ... existing code ... comment to indicate its absence. '
221+ 'If you omit the existing code comment, the model may inadvertently delete these lines.\n '
222+ 'If you plan on deleting a section, you must provide context before and after to delete it. '
223+ 'If the initial code is ```code \\ n Block 1 \\ n Block 2 \\ n Block 3 \\ n code```, '
224+ 'and you want to remove Block 2, you would output '
225+ '```// ... existing code ... \\ n Block 1 \\ n Block 3 \\ n // ... existing code ...```.\n '
226+ 'Make sure it is clear what the edit should be, and where it should be applied.\n '
227+ 'Make edits to a file in a single edit_file call '
228+ 'instead of multiple edit_file calls to the same file. '
229+ 'The apply model can handle many distinct edits at once.'
230+ ),
231+ parameters = {
232+ 'type' : 'object' ,
233+ 'properties' : {
234+ 'path' : {
235+ 'type' : 'string' ,
236+ 'description' :
237+ 'Path of the target file to modify.'
238+ },
239+ 'instructions' : {
240+ 'type' :
241+ 'string' ,
242+ 'description' :
243+ ('A single sentence instruction describing '
244+ 'what you are going to do for the sketched edit. '
245+ 'This is used to assist the less intelligent model in applying the edit. '
246+ 'Use the first person to describe what you are going to do. '
247+ 'Use it to disambiguate uncertainty in the edit.'
248+ )
249+ },
250+ 'code_edit' : {
251+ 'type' :
252+ 'string' ,
253+ 'description' :
254+ ('Specify ONLY the precise lines of code that you wish to edit. '
255+ 'NEVER specify or write out unchanged code. '
256+ 'Instead, represent all unchanged code using the comment of the language '
257+ "you're editing in - example: // ... existing code ..."
258+ )
259+ }
260+ },
261+ 'required' : ['path' , 'instructions' , 'code_edit' ]
262+ }),
199263 Tool (
200264 tool_name = 'search_file_content' ,
201265 server_name = 'file_system' ,
@@ -936,3 +1000,29 @@ async def list_files(self, path: str = None):
9361000 except Exception as e :
9371001 return f'List files of <{ path or "root path" } > failed, error: ' + str (
9381002 e )
1003+
1004+ @retry (max_attempts = MAX_CONTINUE_RUNS , delay = 1.0 )
1005+ async def edit_file (self ,
1006+ path : str = None ,
1007+ instructions : str = None ,
1008+ code_edit : str = None ):
1009+ try :
1010+ with open (os .path .join (self .output_dir , path ), 'r' ) as f :
1011+ initial_code = f .read ()
1012+ response = self .edit_client .chat .completions .create (
1013+ model = self .edit_file_config .diff_model ,
1014+ messages = [{
1015+ 'role' :
1016+ 'user' ,
1017+ 'content' :
1018+ (f'<instruction>{ instructions } </instruction>\n '
1019+ f'<code>{ initial_code } </code>\n '
1020+ f'<update>{ code_edit } </update>' )
1021+ }])
1022+ merged_code = response .choices [0 ].message .content
1023+
1024+ with open (os .path .join (self .output_dir , path ), 'w' ) as f :
1025+ f .write (merged_code )
1026+ return f'Edit file <{ path } > successfully.'
1027+ except Exception as e :
1028+ return f'Edit file <{ path } > failed, error: ' + str (e )
0 commit comments