Skip to content

Commit 1623ebe

Browse files
committed
wip
1 parent 0762edd commit 1623ebe

10 files changed

Lines changed: 114 additions & 75 deletions

File tree

modelscope_agent/cli/arg_parser.py

Lines changed: 0 additions & 15 deletions
This file was deleted.

modelscope_agent/cli/search.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,13 @@
22
import asyncio
33
import os
44

5-
from modelscope_agent.cli.arg_parser import parse_args
65
from modelscope_agent.config import Config
76
from modelscope_agent.engine.simple_engine import SimpleEngine
87

98
if __name__ == '__main__':
109
cur_file = __file__
1110
cur_dir = os.path.dirname(cur_file)
1211
config = Config.from_task(os.path.join(cur_dir, 'search.yaml'))
13-
parse_args(config)
1412
engine = SimpleEngine(config=config)
1513
query = input('>>>Please input the query')
1614
asyncio.run(engine.run(query))

modelscope_agent/cli/search.yaml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,27 @@ servers:
1818
crawl4ai:
1919
command: "uv"
2020
args:
21+
- run
22+
- <crawl4ai_script>
2123

2224
web-search:
2325
command: "npx"
2426
args:
2527
- "-y"
2628
- "tavily-mcp@0.1.4"
2729
env:
28-
- TAVILY_API_KEY:
30+
TAVILY_API_KEY:
2931
disabled: false
30-
autoApprove:
32+
autoApprove: []
3133
exclude:
3234
- tavily-extract
3335

3436
edgeone-pages-mcp-server:
3537
command: "npx"
3638
args:
3739
- "edgeone-pages-mcp"
40+
exclude:
41+
- deploy_folder_or_zip
3842

3943
help: |
4044
This search agent uses the following mcp servers:
@@ -48,9 +52,9 @@ help: |
4852
https://app.tavily.com/home
4953
3. Add `modelscope_api_key` to your env variable, you can create one from:
5054
https://www.modelscope.cn/my/myaccesstoken
51-
4. Run `modelscope_api_key=xxx TAVILY_API_KEY=xxx modelscope-agent search --servers.crawl4ai.args /your/path/to/mcp-central/mcp_central/crawl4ai/server.py`
55+
4. Run `modelscope_api_key=xxx TAVILY_API_KEY=xxx modelscope-agent search --crawl4ai_script /your/path/to/mcp-central/mcp_central/crawl4ai/server.py`
5256
5. If you want to change models, you can add more arguments:
53-
- --llm.model xxx
57+
- --model xxx
5458
5559
5660

modelscope_agent/config/config.py

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import argparse
12
import os.path
3+
from copy import deepcopy
24
from typing import Dict, Union, Any
35

46
from modelscope import snapshot_download
@@ -42,13 +44,28 @@ def from_task(cls, task_dir_or_id: str, env: Dict[str, str] = None) -> Union[Dic
4244
assert config is not None, (f'Cannot find any config file in {task_dir_or_id} named `config.json`, '
4345
f'`config.yml` or `config.yaml`')
4446
envs = Env.load_env(env)
45-
cls._update_envs(config, envs)
47+
cls._update_config(config, envs)
48+
_dict_config = cls.parse_args()
49+
cls._update_config(config, _dict_config)
4650
config.local_dir = task_dir_or_id
4751
return config
4852

4953
@staticmethod
50-
def _update_envs(config: Union[DictConfig, ListConfig], envs: Dict[str, str]=None):
51-
if not envs:
54+
def parse_args():
55+
arg_parser = argparse.ArgumentParser()
56+
args, unknown = arg_parser.parse_known_args()
57+
_dict_config = {}
58+
if unknown:
59+
for idx in range(0, len(unknown), 2):
60+
key = unknown[idx]
61+
value = unknown[idx + 1]
62+
assert key.startswith('--'), f'Parameter not correct: {unknown}'
63+
_dict_config[key[2:]] = value
64+
return _dict_config
65+
66+
@staticmethod
67+
def _update_config(config: Union[DictConfig, ListConfig], extra: Dict[str, str]=None):
68+
if not extra:
5269
return config
5370

5471
def traverse_config(_config: Union[DictConfig, ListConfig, Any]):
@@ -57,13 +74,25 @@ def traverse_config(_config: Union[DictConfig, ListConfig, Any]):
5774
if isinstance(value, BaseContainer):
5875
traverse_config(value)
5976
else:
60-
if name in envs:
61-
logger.info(f'Replacing {name} with the value in your environment variables.')
62-
setattr(_config, name, envs[name])
77+
if name in extra:
78+
logger.info(f'Replacing {name} with extra value.')
79+
setattr(_config, name, extra[name])
80+
if (isinstance(value, str) and value.startswith('<') and
81+
value.endswith('>') and value[1:-1] in extra):
82+
logger.info(f'Replacing {value} with extra value.')
83+
setattr(_config, name, extra[name])
84+
6385
elif isinstance(_config, ListConfig):
64-
for value in _config:
86+
for idx in range(len(_config)):
87+
value = _config[idx]
6588
if isinstance(value, BaseContainer):
6689
traverse_config(value)
90+
else:
91+
if (isinstance(value, str) and value.startswith('<') and
92+
value.endswith('>') and value[1:-1] in extra):
93+
logger.info(f'Replacing {value} with extra value.')
94+
_config[idx] = extra[value[1:-1]]
95+
6796
traverse_config(config)
6897
return None
6998

@@ -77,5 +106,5 @@ def convert_mcp_servers_to_json(config: Union[DictConfig, ListConfig]):
77106
}
78107
if config.servers:
79108
for server, server_config in config.servers.items():
80-
servers['mcpServers'][server] = server_config.to_json()
109+
servers['mcpServers'][server] = deepcopy(server_config)
81110
return servers

modelscope_agent/engine/simple_engine.py

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -74,25 +74,26 @@ def register_callback(self, callback: Callback):
7474
self.callbacks.append(callback)
7575

7676
def _register_callback_from_config(self):
77-
local_dir = self.config.local_dir
78-
for _callback in self.config.callbacks:
79-
if _callback.endswith('.py'):
80-
if not self.trust_remote_code:
81-
raise AssertionError(f'Your config file contains external code, '
82-
f'instantiate the code may be UNSAFE, if you trust the code, '
83-
f'please pass `trust_remote_code=True` or `--trust_remote_code true`')
84-
if sys.path[0] != local_dir:
85-
assert local_dir is not None, 'Using external py files, but local_dir cannot be found.'
86-
sys.path.insert(0, local_dir)
87-
callback_file = importlib.import_module(_callback[:-3])
88-
module_classes = {name: cls for name, cls in inspect.getmembers(callback_file, inspect.isclass)}
89-
for name, cls in module_classes.items():
90-
# Find cls which base class is `Callback`
91-
if cls.__base__[0] is Callback:
92-
self.callbacks.append(cls())
93-
else:
94-
assert _callback in callbacks_mapping
95-
self.callbacks.append(callbacks_mapping[_callback]())
77+
local_dir = self.config.local_dir if hasattr(self.config, 'local_dir') else None
78+
if hasattr(self.config, 'callbacks'):
79+
for _callback in self.config.callbacks:
80+
if _callback.endswith('.py'):
81+
if not self.trust_remote_code:
82+
raise AssertionError(f'Your config file contains external code, '
83+
f'instantiate the code may be UNSAFE, if you trust the code, '
84+
f'please pass `trust_remote_code=True` or `--trust_remote_code true`')
85+
if sys.path[0] != local_dir:
86+
assert local_dir is not None, 'Using external py files, but local_dir cannot be found.'
87+
sys.path.insert(0, local_dir)
88+
callback_file = importlib.import_module(_callback[:-3])
89+
module_classes = {name: cls for name, cls in inspect.getmembers(callback_file, inspect.isclass)}
90+
for name, cls in module_classes.items():
91+
# Find cls which base class is `Callback`
92+
if cls.__base__[0] is Callback:
93+
self.callbacks.append(cls())
94+
else:
95+
assert _callback in callbacks_mapping
96+
self.callbacks.append(callbacks_mapping[_callback]())
9697

9798
def _loop_callback(self, point, messages: List[Message]):
9899
for callback in self.callbacks:
@@ -120,18 +121,18 @@ def _prepare_messages(self, prompt):
120121
{'role': 'system', 'content': self.config.prompt.system or self.DEFAULT_SYSTEM_EN},
121122
{'role': 'user', 'content': prompt or self.config.prompt.query},
122123
]
123-
messages['query'] = self._query_documents(messages[1]['content'])
124+
messages[1]['content'] = self._query_documents(messages[1]['content'])
124125
return messages
125126

126127
def _prepare_memory(self):
127-
if self.config.memory:
128+
if hasattr(self.config, 'memory') and self.config.memory:
128129
for _memory in self.config.memory:
129130
assert _memory in memory_mapping, (f'{_memory} not in memory_mapping, '
130131
f'which supports: {list(memory_mapping.keys())}')
131132
self.memory_tools.append(memory_mapping[_memory]())
132133

133134
def _prepare_rag(self):
134-
if self.config.rag:
135+
if hasattr(self.config, 'rag') and self.config.rag:
135136
assert self.config.rag in rag_mapping
136137
self.rag: Rag = rag_mapping(self.config.rag)()
137138

modelscope_agent/llm/openai_llm.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
import inspect
2-
32
from typing import Any, List, Dict, Optional, Generator
43

5-
from grpc.framework.interfaces.base.utilities import completion
6-
from omegaconf import DictConfig
4+
from omegaconf import DictConfig, OmegaConf
5+
from openai.types.chat.chat_completion_message_tool_call import ChatCompletionMessageToolCall, Function
76

8-
from modelscope_agent.utils.utils import assert_package_exist
97
from modelscope_agent.llm.llm import LLM
108
from modelscope_agent.llm.utils import Message, Tool, ToolCall
9+
from modelscope_agent.utils.utils import assert_package_exist
1110

12-
from openai.types.chat.chat_completion_message_tool_call import ChatCompletionMessageToolCall, Function
1311

1412
def _stream_generator() -> Generator[Message, None, None]:
1513
pass

modelscope_agent/tools/loop_tool.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from omegaconf import DictConfig
22

33
from modelscope_agent.tools.base import ToolBase
4+
from modelscope_agent.tools.utils import ToolField
45

56

67
class LoopTool(ToolBase):
@@ -16,13 +17,15 @@ async def cleanup(self):
1617

1718
async def get_tools(self):
1819
return {
19-
'split_complex_task': {
20-
'tool_name': 'split_to_sub_task',
21-
'tool_args': {
22-
'system': 'The system prompt of this sub task',
23-
'query': 'The query to solve of this sub task',
24-
}
25-
}
20+
'split_complex_task': [ToolField(
21+
name='split_to_sub_task',
22+
description='Split complex task into sub tasks, for example, split a website generation task into sub tasks, '
23+
'you plan the framework, include code files and classes and functions, and give the detail '
24+
'information to the system and query field of the subtask, then '
25+
'let each subtask to write a single file',
26+
input_schema='system: str, The system prompt to use in this sub task. \n'
27+
'query: str, The specific query in this sub task. \n',
28+
)]
2629
}
2730

2831

modelscope_agent/tools/mcp_client.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
from contextlib import AsyncExitStack
2-
from typing import Any, Dict, Literal, Optional
2+
from typing import Any, Dict, Literal, Optional, List
33

4-
from mcp import ClientSession, StdioServerParameters
4+
from mcp import ClientSession, StdioServerParameters, ListToolsResult
55
from mcp.client.sse import sse_client
66
from mcp.client.stdio import stdio_client
77

8+
from modelscope_agent.config import Config
89
from modelscope_agent.config.env import Env
910
from modelscope_agent.tools.base import ToolBase
1011
from modelscope_agent.utils import get_logger
@@ -26,7 +27,10 @@ def __init__(self, config, mcp_config: Optional[Dict[str, Any]] = None):
2627
super().__init__(config)
2728
self.sessions: Dict[str, ClientSession] = {}
2829
self.exit_stack = AsyncExitStack()
29-
self.mcp_config = mcp_config
30+
self.mcp_config = Config.convert_mcp_servers_to_json(config)
31+
self._exclude_functions = {}
32+
if mcp_config is not None:
33+
self.mcp_config.update(mcp_config)
3034

3135
async def call_tool(self, server_name: str, tool_name: str,
3236
tool_args: dict):
@@ -45,11 +49,17 @@ async def get_tools(self) -> Dict:
4549
for key, session in self.sessions.items():
4650
tools[key] = []
4751
response = await session.list_tools()
48-
tools[key].extend(response.tools)
52+
_session_tools = response.tools
53+
exclude = []
54+
if key in self._exclude_functions:
55+
exclude = self._exclude_functions[key]
56+
_session_tools = [t for t in _session_tools if t.name not in exclude]
57+
tools[key].extend(_session_tools)
4958
return tools
5059

5160
@staticmethod
52-
def print_tools(server_name: str, tools: Dict):
61+
def print_tools(server_name: str, tools: ListToolsResult):
62+
tools = tools.tools
5363
if len(tools) > 10:
5464
tools = [tool.name for tool in tools][:10]
5565
logger.info(f'\nConnected to server "{server_name}" '
@@ -116,14 +126,16 @@ async def connect_to_server(self, server_name: str, **kwargs):
116126
async def connect(self):
117127
assert self.mcp_config, 'MCP config is required'
118128
envs = Env.load_env()
119-
for name, server in self.mcp_config.items():
120-
cmd = server['cmd']
121-
env_dict = cmd.pop('env', {})
129+
mcp_config = self.mcp_config['mcpServers']
130+
for name, server in mcp_config.items():
131+
env_dict = server.pop('env', {})
122132
env_dict = {
123133
key: value if value else envs.get(key, '')
124134
for key, value in env_dict.items()
125135
}
126-
await self.connect_to_server(server_name=name, env=env_dict, **cmd)
136+
if 'exclude' in server:
137+
self._exclude_functions[name] = server.pop('exclude')
138+
await self.connect_to_server(server_name=name, env=env_dict, **server)
127139

128140
async def cleanup(self):
129141
"""Clean up resources"""

modelscope_agent/tools/tool_manager.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@ def __init__(self, config):
1414
self.loop_tool = LoopTool(config)
1515
self.extra_tools: List[ToolBase] = []
1616
self._tool_index = {}
17-
asyncio.run(self.reindex_tool())
1817

1918
async def connect(self):
2019
await self.mcp_client.connect()
2120
await self.loop_tool.connect()
2221
for tool in self.extra_tools:
2322
await tool.connect()
23+
asyncio.run(self.reindex_tool())
2424

2525
async def cleanup(self):
2626
await self.mcp_client.cleanup()
@@ -32,8 +32,8 @@ async def reindex_tool(self):
3232

3333
def extend_tool(tool_ins: ToolBase, server_name: str, tool_list: List):
3434
for tool in tool_list:
35-
assert tool['name'] not in self._tool_index, f'Tool name duplicated {tool["name"]}'
36-
self._tool_index[tool['name']] = (tool_ins, server_name, tool)
35+
assert tool.name not in self._tool_index, f'Tool name duplicated {tool.name}'
36+
self._tool_index[tool.name] = (tool_ins, server_name, tool)
3737

3838
mcps = await self.mcp_client.get_tools()
3939
for server_name, tool_list in mcps.items():

modelscope_agent/tools/utils.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from dataclasses import dataclass
2+
3+
4+
@dataclass
5+
class ToolField:
6+
7+
name: str = None
8+
description: str = None
9+
input_schema: str = None

0 commit comments

Comments
 (0)