Skip to content

Commit 8ba586c

Browse files
committed
wip
1 parent 0301ecf commit 8ba586c

10 files changed

Lines changed: 100 additions & 79 deletions

File tree

modelscope_agent/callbacks/base.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,20 @@ class Callback:
1111
def __init__(self, config: DictConfig):
1212
self.config = config
1313

14-
def on_task_begin(self, run_status: RunStatus, messages: List[Message]):
14+
async def on_task_begin(self, run_status: RunStatus, messages: List[Message]):
1515
pass
1616

17-
def on_generate_response(self, run_status: RunStatus, messages: List[Message]):
17+
async def on_generate_response(self, run_status: RunStatus, messages: List[Message]):
1818
pass
1919

20-
def after_generate_response(self, run_status: RunStatus, messages: List[Message]):
20+
async def after_generate_response(self, run_status: RunStatus, messages: List[Message]):
2121
pass
2222

23-
def on_tool_call(self, run_status: RunStatus, messages: List[Message]):
23+
async def on_tool_call(self, run_status: RunStatus, messages: List[Message]):
2424
pass
2525

26-
def after_tool_call(self, run_status: RunStatus, messages: List[Message]):
26+
async def after_tool_call(self, run_status: RunStatus, messages: List[Message]):
2727
pass
2828

29-
def on_task_end(self, run_status: RunStatus, messages: List[Message]):
29+
async def on_task_end(self, run_status: RunStatus, messages: List[Message]):
3030
pass

modelscope_agent/cli/code/artifact_callback.py

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,35 +15,38 @@ def __init__(self, config: DictConfig):
1515
super().__init__(config)
1616
self.file_system = FileSystemTool(config)
1717

18+
async def on_task_begin(self, run_status: RunStatus, messages: List[Message]):
19+
await self.file_system.connect()
20+
1821
@staticmethod
1922
def extract_metadata(config: DictConfig, llm: LLM, messages: List[Message]):
2023
assert messages[0].role == 'system' and messages[1].role == 'user'
21-
_system = """Here gives a LLM system field, and a user query field, you need to extract the code file information of it, and wraps the final result in <result></result>.
24+
_system = """You are a file name parser, I will give a user query field to you, you need to extract the code file name from it, and wraps the final result in <result></result>.
25+
Always remember your task is not generating the code, but parse the file name from the query.
2226
Here shows an example:
23-
system is: You are a code engineer, you should help me to write a code file, which is a part of a complex job. The rules you need to follow are: ...
2427
query is: You should write the index.js file, the file you need to use is main.css and nav.js, the interface in the code is ...
2528
2629
Your answer should be: <result>index.js</result>
2730
"""
28-
_query = (f'The input system is: {messages[0].content}\n\n'
29-
f'The input query is: {messages[1].content}\n\n'
30-
'Now give the code file name:\n')
31+
_query = (f'The input query is: {messages[1].content}\n\n'
32+
'Now give the code file name and wraps with <result></result>:\n')
3133
_messages = [
3234
Message(role='system', content=_system),
3335
Message(role='user', content=_query)
3436
]
3537
if getattr(config.generation_config, 'stream', False):
3638
message = None
37-
for msg in llm.generate(messages):
39+
for msg in llm.generate(_messages):
3840
message = llm.merge_stream_message(message, msg)
3941

4042
_response_message = message
4143
else:
42-
_response_message = llm.generate(messages)
43-
assert '<result>' in _response_message[-1].content and '</result>' in _response_message[-1].content
44-
return re.findall(r'<result>(.*?)</result>', _response_message[-1].content)[0]
44+
_response_message = llm.generate(_messages)
45+
if not ('<result>' in _response_message.content and '</result>' in _response_message.content):
46+
raise AssertionError(f'Could not extract information from {_response_message.content}')
47+
return re.findall(r'<result>(.*?)</result>', _response_message.content)[0]
4548

46-
def after_generate_response(self, run_status: RunStatus, messages: List[Message]):
49+
async def after_generate_response(self, run_status: RunStatus, messages: List[Message]):
4750
last_message_content = messages[-1].content
4851
if '</code>' in last_message_content:
4952
code = ''
@@ -63,8 +66,11 @@ def after_generate_response(self, run_status: RunStatus, messages: List[Message]
6366
code += message.content
6467
if code:
6568
code_file = self.extract_metadata(self.config, run_status.llm, messages)
66-
self.file_system.create_directory('./output')
67-
self.file_system.write_file(code_file, code)
69+
try:
70+
# await self.file_system.create_directory('output')
71+
await self.file_system.write_file(code_file, code)
72+
except Exception as e:
73+
print(e)
6874
messages.append(Message(role='assistant', content=f'Original query: {messages[1].content}'
6975
f'Task sunning successfully, '
7076
f'the code has been saved in the {code_file} file.'))

modelscope_agent/cli/code/coding.yaml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
llm:
22
service: openai
3-
model: claude-3-7-sonnet-20250219
3+
model: qwen-plus-latest
44
openai_api_key:
55
openai_base_url: https://dashscope.aliyuncs.com/compatible-mode/v1
66

@@ -10,13 +10,12 @@ generation_config:
1010
top_k: 50
1111
stream: false
1212
extra_body:
13-
enable_thinking: false
1413
dashscope_extend_params:
1514
provider: idealab
1615

1716
prompt:
1817
system: |
19-
You are a senior software architect. Your responsibility is to break down original requirements into implementable modules and assign tasks for each module into subtasks. The initiation of subtasks requires calling the `split_complex_task` tool, which can start all sub tasks as you need at one time. In this process, you need to answer the following questions:
18+
You are a senior software architect. Your responsibility is to break down original requirements into implementable modules and assign tasks for each module into subtasks. The initiation of subtasks requires calling the `split_to_sub_task` tool, which can start all sub tasks as you need at one time. In this process, you need to answer the following questions:
2019
2120
1. What is the original requirement? Does it involve frontend or backend code? What programming language is needed?
2221
2. How many modules should it be split into? What are the interfaces for each module?
@@ -33,6 +32,7 @@ prompt:
3332
3. The site will display specific categories, each category can showcase different product showcases
3433
4. Products can be clicked to purchase or favorite, so I need to design a complete favorites functionality
3534
5. Due to the complexity of the code engineering, I cannot complete this complex goal in a single file, so I need to split the tasks, which task should in charge of one single code file.
35+
6. I should give a very detail design of the page functions and file locations, in case the sub tasks work abnormally
3636
...
3737
3838
The subtasks I need to split are:
@@ -51,11 +51,11 @@ prompt:
5151
You should **DO A TOOL CALLING** with multi sub tasks information, here shows example of tool args:
5252
[
5353
{
54-
"system": "You are a software engineer which helps me to finish a part of my job. You need to follow these instructions: 1. ... 2. ...",
54+
"system": "You are a software engineer which helps me to finish a part of my job. You need to follow these instructions: 1. You should generate code by yourself, do not call `split_to_sub_task` anymore.\n 2. Do not give fake image addresses 3. You should keep the page as beautiful as you can 4. DO not add ``` around the code, wrap the code with <code></code> 5. Detail function and interface requirements here... 6. ...",
5555
"query": "The original query is to write an e-commerce website with Christmas atmosphere your part of job is the index.html page, the interface design should be: 1. ... 2. ..."
5656
},
5757
{
58-
"system": "You are a software engineer which helps me to finish a part of my job. You need to follow these instructions: 1. ... 2. ...",
58+
"system": "You are a software engineer which helps me to finish a part of my job. You need to follow these instructions: 1. You should generate code by yourself, do not call `split_to_sub_task` anymore.\n 2. Do not give fake image addresses 4. DO not add ``` around the code, wrap the code with <code></code> 5. Detail function and interface requirements here... 6. ...",
5959
"query": "The original query is to write an e-commerce website with Christmas atmosphere your part of job is the index.js code file, the interface design should be: 1. ... 2. ..."
6060
},
6161
...

modelscope_agent/engine/simple_engine.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import importlib
22
import inspect
3+
import json
34
import sys
45
from typing import List, Optional, Dict
56

@@ -94,9 +95,9 @@ def _register_callback_from_config(self):
9495
else:
9596
self.callbacks.append(callbacks_mapping[_callback]())
9697

97-
def _loop_callback(self, point, messages: List[Message]):
98+
async def _loop_callback(self, point, messages: List[Message]):
9899
for callback in self.callbacks:
99-
getattr(callback, point)(self.run_status, messages)
100+
await getattr(callback, point)(self.run_status, messages)
100101

101102
async def _parallel_tool_call(self, messages: List[Message]):
102103
tool_call_result = await self.tool_manager.parallel_call_tool(messages[-1].tool_calls)
@@ -106,7 +107,6 @@ async def _parallel_tool_call(self, messages: List[Message]):
106107
role='tool',
107108
content=tool_call_result,
108109
tool_call_id=tool_call_query['id'],
109-
name=tool_call_query['tool_name']
110110
)
111111
messages.append(_new_message)
112112
logger.info(_new_message.content)
@@ -172,7 +172,8 @@ def handle_stream_message(self):
172172

173173
def log_output(self, content: str, tag='Default workflow'):
174174
for line in content.split('\n'):
175-
logger.info(f'[{tag}] {line}')
175+
for _line in line.split('\\n'):
176+
logger.info(f'[{tag}] {_line}')
176177

177178
async def run(self, prompt, **kwargs):
178179
try:
@@ -182,11 +183,11 @@ async def run(self, prompt, **kwargs):
182183
self._prepare_rag()
183184
tag = kwargs.get('tag', 'Default workflow')
184185
messages = self._prepare_messages(prompt)
185-
self._loop_callback('on_task_begin', messages)
186+
await self._loop_callback('on_task_begin', messages)
186187
if self.planer:
187188
self.planer.generate_plan(messages, self.run_status)
188189
while not self.run_status.should_stop:
189-
self._loop_callback('on_generate_response', messages)
190+
await self._loop_callback('on_generate_response', messages)
190191
messages = self._refine_memory(messages)
191192
messages = self._update_plan(messages)
192193
tools = await self.tool_manager.get_tools()
@@ -199,15 +200,15 @@ async def run(self, prompt, **kwargs):
199200
self.log_output(_response_message.content, tag=tag)
200201
if _response_message.tool_calls:
201202
for tool_call in _response_message.tool_calls:
202-
self.log_output(str(tool_call), tag=tag)
203-
self._loop_callback('after_generate_response', messages)
204-
self._loop_callback('on_tool_call', messages)
203+
self.log_output(json.dumps(tool_call), tag=tag)
204+
await self._loop_callback('after_generate_response', messages)
205+
await self._loop_callback('on_tool_call', messages)
205206
if messages[-1].tool_calls:
206207
await self._parallel_tool_call(messages)
207208
else:
208209
self.run_status.should_stop = True
209-
self._loop_callback('after_tool_call', messages)
210-
self._loop_callback('on_task_end', messages)
210+
await self._loop_callback('after_tool_call', messages)
211+
await self._loop_callback('on_task_end', messages)
211212
await self._cleanup_tools()
212213
return messages
213214
except Exception as e:

modelscope_agent/llm/claude.py

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,28 @@
11
import inspect
2-
from typing import Any
2+
from typing import Any, List
33

4-
from modelscope_agent.utils.llm_utils import retry
4+
from omegaconf import DictConfig
55

6+
from modelscope_agent.llm.openai_llm import OpenAI
7+
from modelscope_agent.llm.utils import Tool
8+
from modelscope_agent.utils.llm_utils import retry
69

7-
class Claude:
810

9-
def __init__(self, system):
10-
self.system = system
11-
self.client = OpenAI(
12-
api_key=self.token,
13-
base_url=self.base_url,
14-
)
11+
class Claude(OpenAI):
1512

16-
@retry(max_attempts=5)
17-
def generate(self, messages, model, tools=None, **kwargs) -> Any:
18-
_e = None
19-
parameters = inspect.signature(self.client.chat.completions.create).parameters
20-
kwargs = {key: value for key, value in kwargs.items() if key in parameters}
21-
completion = self.client.chat.completions.create(
22-
model=model,
23-
messages=messages,
24-
tools=tools,
25-
parallel_tool_calls=False,
26-
**kwargs
27-
)
28-
return completion
13+
def __init__(self, config: DictConfig):
14+
super().__init__(config, base_url=config.llm.claude_base_url, api_key=config.llm.claude_api_key)
2915

16+
def format_tools(self, tools: List[Tool]):
17+
if tools:
18+
tools = [
19+
{
20+
'name': f'{tool.get("server_name")}---{tool["tool_name"]}' if tool.get('server_name') else tool[
21+
'tool_name'],
22+
'description': tool['description'],
23+
'input_schema': tool['parameters']
24+
} for tool in tools
25+
]
26+
else:
27+
tools = None
28+
return tools

modelscope_agent/llm/openai_llm.py

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,7 @@ def __init__(self, config: DictConfig, base_url: Optional[str] = None, api_key:
2727
)
2828
self.args: Dict = OmegaConf.to_container(getattr(config, 'generation_config', {}))
2929

30-
@retry(max_attempts=3)
31-
def generate(self, messages: List[Message], tools: List[Tool] = None, **kwargs) -> Message | Generator[Message, None, None]:
32-
parameters = inspect.signature(self.client.chat.completions.create).parameters
33-
args = self.args.copy()
34-
args.update(kwargs)
35-
stream = args.get('stream', False)
36-
37-
args = {key: value for key, value in args.items() if key in parameters}
38-
30+
def format_tools(self, tools: List[Tool]):
3931
if tools:
4032
tools = [
4133
{
@@ -49,7 +41,17 @@ def generate(self, messages: List[Message], tools: List[Tool] = None, **kwargs)
4941
]
5042
else:
5143
tools = None
52-
completion = self._call_llm(messages, tools, **args)
44+
return tools
45+
46+
@retry(max_attempts=3)
47+
def generate(self, messages: List[Message], tools: List[Tool] = None, **kwargs) -> Message | Generator[Message, None, None]:
48+
parameters = inspect.signature(self.client.chat.completions.create).parameters
49+
args = self.args.copy()
50+
args.update(kwargs)
51+
stream = args.get('stream', False)
52+
53+
args = {key: value for key, value in args.items() if key in parameters}
54+
completion = self._call_llm(messages, self.format_tools(tools), **args)
5355

5456
# 考虑到复杂任务可能存在 单次调用llm生成不完整的情况。需要调用continue_gen判断是否应多次调用以获得完整输出
5557
if stream:
@@ -125,16 +127,19 @@ def stream_format_output_message(self, completion_chunk) -> Message:
125127

126128
def format_output_message(self, completion) -> Message:
127129
content = completion.choices[0].message.content or ''
128-
reasoning_content = completion.choices[0].message.reasoning_content or ''
130+
if hasattr(completion.choices[0].message, 'reasoning_content'):
131+
reasoning_content = completion.choices[0].message.reasoning_content or ''
132+
else:
133+
reasoning_content = ''
129134
tool_calls = None
130135
if completion.choices[0].message.tool_calls:
131136
tool_calls = [ToolCall(
132137
id=tool_call.id,
133-
index=tool_call.index,
138+
index=getattr(tool_call, 'index', idx),
134139
type=tool_call.type,
135140
arguments=tool_call.function.arguments,
136141
tool_name=tool_call.function.name
137-
) for tool_call in completion.choices[0].message.tool_calls
142+
) for idx, tool_call in enumerate(completion.choices[0].message.tool_calls)
138143
]
139144
return Message(role='assistant', content=content, reasoning_content=reasoning_content, tool_calls=tool_calls, id=completion.id)
140145

modelscope_agent/tools/filesystem.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ tools:
44
args:
55
- "-y"
66
- "@modelcontextprotocol/server-filesystem"
7-
- "./output"
7+
- "."

modelscope_agent/tools/filesystem_tool.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ async def call_tool(self, server_name: str, *, tool_name: str, tool_args: dict):
2525
return await self.mcp_client.call_tool(server_name, tool_name, tool_args)
2626

2727
async def create_directory(self, path: str):
28-
return await self.call_tool(tool_name='create_directory', tool_args={'path': path})
28+
return await self.call_tool('filesystem', tool_name='create_directory', tool_args={'path': path})
2929

3030
async def write_file(self, path: str, content: str):
31-
return await self.call_tool(tool_name='write_file', tool_args={'path': path, 'content': content})
31+
return await self.call_tool('filesystem', tool_name='write_file', tool_args={'path': path, 'content': content})
3232

3333
async def read_file(self, path: str):
34-
return await self.call_tool(tool_name='read_file', tool_args={'path': path})
34+
return await self.call_tool('filesystem', tool_name='read_file', tool_args={'path': path})

modelscope_agent/tools/mcp_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ async def get_tools(self) -> Dict:
6666
@staticmethod
6767
def print_tools(server_name: str, tools: ListToolsResult):
6868
tools = tools.tools
69-
sep = "\n"
69+
sep = ","
7070
if len(tools) > 10:
7171
tools = [tool.name for tool in tools][:10]
7272
logger.info(f'\nConnected to server "{server_name}" '

modelscope_agent/tools/split_task.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,19 @@ async def get_tools(self):
2626
'you plan the framework, include code files and classes and functions, and give the detail '
2727
'information to the system and query field of the subtask, then '
2828
'let each subtask to write a single file',
29-
parameters={
30-
'tasks': 'List type, each element is a dict, which contains two fields: system and query to start the sub task.',
31-
}
29+
parameters= {
30+
"type": "object",
31+
"properties": {
32+
"tasks": {
33+
"type": "array",
34+
"description": "Each element is a dict, which contains two fields: system and query to start the sub task."
35+
}
36+
},
37+
"required": [
38+
"tasks"
39+
],
40+
"additionalProperties": False
41+
}
3242
)]
3343
}
3444

@@ -43,12 +53,12 @@ async def call_tool(self, server_name: str, *, tool_name: str, tool_args: dict):
4353
config = DictConfig(self.config)
4454
config.prompt.system = system
4555
config.prompt.query = query
46-
# config.tools = DictConfig({})
56+
delattr(config.tools, 'split_task')
4757
trust_remote_code = getattr(config, 'trust_remote_code', False)
4858
engine = SimpleEngine(config=config, trust_remote_code=trust_remote_code)
4959
sub_tasks.append(engine.run(query, tag=f'workflow {i}'))
5060
result = await asyncio.gather(*sub_tasks)
5161
res = []
5262
for messages in result:
5363
res.append(messages[-1].content)
54-
return result
64+
return res

0 commit comments

Comments
 (0)