Skip to content

Commit a677559

Browse files
committed
Merge commit 'e65bb85ca347d03eee82193cccf3cc9cce47c788' into refactor/v1
* commit 'e65bb85ca347d03eee82193cccf3cc9cce47c788': minor fix add deepseek, dashscope llm; merge _continue_gen
2 parents 60b872c + e65bb85 commit a677559

5 files changed

Lines changed: 127 additions & 93 deletions

File tree

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,28 @@
1-
import inspect
2-
from typing import Any, Optional
1+
from typing import List
32

4-
from modelscope_agent.utils.llm_utils import retry
5-
from modelscope_agent.llm.llm import LLM
3+
from modelscope_agent.llm.utils import Message, Tool
4+
from modelscope_agent.llm.openai_llm import OpenAI
5+
from omegaconf import DictConfig
66

7+
class DashScope(OpenAI):
78

8-
class DashScope(LLM):
9+
def __init__(self, config: DictConfig):
10+
super().__init__(config, base_url=config.llm.dashscope_base_url, api_key=config.llm.dashscope_api_key)
911

10-
def __init__(self, system):
11-
self.system = system
12-
self.client = OpenAI(
13-
api_key=self.token,
14-
base_url=self.base_url,
15-
)
12+
def _continue_generate(self, messages: List[Message], new_message, tools: List[Tool] = None, **kwargs):
13+
# ref: https://bailian.console.aliyun.com/?tab=doc#/doc/?type=model&url=https%3A%2F%2Fhelp.aliyun.com%2Fdocument_detail%2F2862210.html&renderType=iframe
14+
if messages and messages[-1].to_dict().get('partial', False):
1615

17-
@retry(max_attempts=5)
18-
def generate(self, messages, model: Optional[str] = None, tools=None, **kwargs) -> Any:
19-
_e = None
20-
parameters = inspect.signature(self.client.chat.completions.create).parameters
21-
kwargs = {key: value for key, value in kwargs.items() if key in parameters}
22-
completion = self.client.chat.completions.create(
23-
model=model,
24-
messages=messages,
25-
tools=tools,
26-
parallel_tool_calls=False,
27-
**kwargs
28-
)
29-
return completion
16+
messages[-1].reasoning_content += new_message.reasoning_content
17+
messages[-1].content += new_message.content
18+
if new_message.tool_calls:
19+
if messages[-1].tool_calls:
20+
messages[-1].tool_calls += new_message.tool_calls
21+
else:
22+
messages[-1].tool_calls = new_message.tool_calls
23+
else:
24+
messages.append(new_message)
25+
messages[-1].partial = True
3026

27+
messages = self.format_input_message(messages)
28+
return self._call_llm(messages=messages, tools=tools, **kwargs)
Lines changed: 81 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,82 @@
1-
import inspect
2-
from typing import Any
3-
4-
5-
class DeepSeek:
6-
7-
def __init__(self, system):
8-
self.system = system
9-
from openai import OpenAI
10-
self.client = OpenAI(
11-
api_key=self.token,
12-
base_url=self.base_url,
13-
)
14-
15-
def generate(self, messages, model, tools=None, **kwargs) -> Any:
16-
parameters = inspect.signature(self.client.chat.completions.create).parameters
17-
kwargs = {key: value for key, value in kwargs.items() if key in parameters}
18-
completion = self.client.chat.completions.create(
19-
model=model,
20-
messages=messages,
21-
tools=tools,
22-
**kwargs
23-
)
24-
return completion
1+
from typing import List
252

3+
from modelscope_agent.llm.utils import Message, Tool
4+
from modelscope_agent.llm.openai_llm import OpenAI
5+
from omegaconf import DictConfig
6+
7+
class DeepSeek(OpenAI):
8+
input_msg = {'role', 'content', 'tool_calls', 'prefix'}
9+
10+
def __init__(self, config: DictConfig):
11+
super().__init__(config, base_url=config.llm.deepseek_base_url, api_key=config.llm.deepseek_api_key)
12+
13+
def _continue_generate(self, messages: List[Message], new_message, tools: List[Tool] = None, **kwargs):
14+
# ref: https://api-docs.deepseek.com/zh-cn/guides/chat_prefix_completion
15+
if messages and messages[-1].to_dict().get('prefix', False):
16+
17+
messages[-1].reasoning_content += new_message.reasoning_content
18+
messages[-1].content += new_message.content
19+
if new_message.tool_calls:
20+
if messages[-1].tool_calls:
21+
messages[-1].tool_calls += new_message.tool_calls
22+
else:
23+
messages[-1].tool_calls = new_message.tool_calls
24+
else:
25+
messages.append(new_message)
26+
messages[-1].prefix = True
27+
28+
messages = self.format_input_message(messages)
29+
stop = kwargs.pop('stop', []).append('```')
30+
return self._call_llm(messages=messages, tools=tools, stop=stop, **kwargs)
31+
32+
33+
if __name__ == '__main__':
34+
import os
35+
from omegaconf import OmegaConf
36+
37+
# 创建一个嵌套的字典结构
38+
conf: DictConfig = OmegaConf.create({
39+
"llm": {
40+
"model": "deepseek-reasoner",
41+
"deepseek_base_url": "https://api.deepseek.com/v1",
42+
"deepseek_api_key": os.getenv("DEEPSEEK_API_KEY"),
43+
"openai_base_url": "https://api-inference.modelscope.cn/v1",
44+
"openai_api_key": os.getenv("MODELSCOPE_API_KEY"),
45+
"generation_config": {
46+
"stream": False,
47+
"max_tokens": 500,
48+
}
49+
}
50+
})
51+
52+
messages = [
53+
Message(role='assistant', content='You are a helpful assistant.'),
54+
# Message(role='user', content='经度:116.4074,纬度:39.9042是什么地方。用这个名字作为目录名'),
55+
# Message(role='user', content='请你简单介绍杭州'),
56+
Message(role='user', content='创建2个文件夹,一个叫a,一个叫b'),
57+
58+
]
59+
60+
tools = [
61+
# Tool(server_name='amap-maps', tool_name='maps_regeocode', description='将一个高德经纬度坐标转换为行政区划地址信息', parameters={'type': 'object', 'properties': {'location': {'type': 'string', 'description': '经纬度'}}, 'required': ['location']}),
62+
Tool(tool_name='mkdir', description='在文件系统创建目录', parameters={'type': 'object', 'properties': {'dir_name': {'type': 'string', 'description': '目录名'}}, 'required': ['dir_name']})
63+
]
64+
# tools = None
65+
66+
67+
# 打印配置
68+
print(OmegaConf.to_yaml(conf))
69+
70+
llm = DeepSeek(conf)
71+
72+
# res = llm.generate(messages=messages, tools=tools, extra_body={'enable_thinking': False})
73+
# for chunk in res:
74+
# print(chunk)
75+
76+
# kwargs覆盖conf
77+
message = llm.generate(messages=messages, tools=tools, stream=False, extra_body={'enable_thinking': False})
78+
print(message)
79+
messages.append(message)
80+
# messages.append(Message(role='tool', content='北京市朝阳区崔各庄阿里巴巴朝阳科技园'))
81+
# message = llm.generate(messages=messages, tools=tools, stream=False, extra_body={'enable_thinking': False})
82+
# print(message)

modelscope_agent/llm/modelscope_llm.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ class ModelScope(OpenAI):
88

99
def __init__(self, config: DictConfig):
1010
super().__init__(config, base_url=config.llm.modelscope_base_url, api_key=config.llm.modelscope_api_key)
11+

modelscope_agent/llm/openai_llm.py

Lines changed: 21 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@
55
from openai.types.chat.chat_completion_message_tool_call import ChatCompletionMessageToolCall, Function
66

77
from modelscope_agent.llm.llm import LLM
8+
from modelscope_agent.utils.llm_utils import retry
89
from modelscope_agent.llm.utils import Message, Tool, ToolCall
910
from modelscope_agent.utils.utils import assert_package_exist
1011

1112

12-
1313
class OpenAI(LLM):
14+
input_msg = {'role', 'content', 'tool_calls', 'partial', 'prefix'}
1415

1516
def __init__(self, config: DictConfig, base_url: Optional[str] = None, api_key: Optional[str] = None):
1617
super().__init__(config)
@@ -23,10 +24,10 @@ def __init__(self, config: DictConfig, base_url: Optional[str] = None, api_key:
2324
api_key=api_key,
2425
base_url=base_url,
2526
)
26-
exclude_fields = {"model", "base_url", "api_key"}
27-
self.args: Dict = {k: v for k, v in OmegaConf.to_container(getattr(config, 'generation_config', {}), resolve=True).items() if k not in exclude_fields}
27+
self.args: Dict = {k: v for k, v in getattr(config.llm, 'generation_config', {}).items()}
2828

29-
def generate(self, messages: List[Message], model: Optional[str] = None, tools: List[Tool] = None, **kwargs) -> Message | Generator[Message, None, None]:
29+
@retry(max_attempts=3)
30+
def generate(self, messages: List[Message], tools: List[Tool] = None, **kwargs) -> Message | Generator[Message, None, None]:
3031
parameters = inspect.signature(self.client.chat.completions.create).parameters
3132
args = self.args.copy()
3233
args.update(kwargs)
@@ -45,44 +46,23 @@ def generate(self, messages: List[Message], model: Optional[str] = None, tools:
4546
}
4647
} for tool in tools
4748
]
48-
completion = self._call_llm(model or self.model, messages, tools, **args)
49+
completion = self._call_llm(messages, tools, **args)
4950

5051
# 考虑到复杂任务可能存在 单次调用llm生成不完整的情况。需要调用continue_gen判断是否应多次调用以获得完整输出
5152
if stream:
5253
return self.stream_continue_generate(messages, completion, tools, **args)
5354
else:
5455
return self.continue_generate(messages, completion, tools, **args)
5556

56-
def _call_llm(self, model, messages, tools, **kwargs):
57+
def _call_llm(self, messages, tools, **kwargs):
5758
messages = self.format_input_message(messages)
5859
return self.client.chat.completions.create(
59-
model=model,
60+
model=self.model,
6061
messages=messages,
6162
tools=tools,
6263
**kwargs
6364
)
6465

65-
def _stream_continue_generate(self, messages: List[Message], new_message, tools: List[Tool] = None, **kwargs):
66-
# 如果上一条消息也和new_message一样不完整,则进行拼接
67-
if messages and messages[-1].to_dict().get('partial', False):
68-
# 更新最后一条消息的内容
69-
messages[-1].reasoning_content += new_message.reasoning_content
70-
messages[-1].content += new_message.content
71-
if new_message.tool_calls:
72-
if messages[-1].tool_calls:
73-
messages[-1].tool_calls += new_message.tool_calls
74-
else:
75-
messages[-1].tool_calls = new_message.tool_calls
76-
else:
77-
# 否则添加为新的 partial 消息
78-
new_message.partial = True
79-
messages.append(new_message)
80-
81-
messages = self.format_input_message(messages)
82-
83-
# 继续调用 LLM 并流式返回后续结果
84-
return self._call_llm(messages, tools, **kwargs)
85-
8666
def stream_continue_generate(self, messages: List[Message], completion, tools: List[Tool] = None, **kwargs) -> Generator[Message, None, None]:
8767
message = None
8868
for chunk in completion:
@@ -114,13 +94,13 @@ def stream_continue_generate(self, messages: List[Message], completion, tools: L
11494
yield message_chunk
11595
if chunk.choices[0].finish_reason in ['length', 'null']:
11696
print(f'finish_reason: {chunk.choices[0].finish_reason}, continue generate.')
117-
completion = self._stream_continue_generate(messages, message, tools, **kwargs)
97+
completion = self._continue_generate(messages, message, tools, **kwargs)
11898
for chunk in self.stream_continue_generate(messages, completion, tools, **kwargs):
11999
yield chunk
120100

121101
def stream_format_output_message(self, completion_chunk) -> Message:
122-
content = completion_chunk.choices[0].delta.content
123-
reasoning_content = completion_chunk.choices[0].delta.reasoning_content
102+
content = completion_chunk.choices[0].delta.content or ''
103+
reasoning_content = completion_chunk.choices[0].delta.reasoning_content or ''
124104
tool_calls = None
125105
if completion_chunk.choices[0].delta.tool_calls:
126106
func = completion_chunk.choices[0].delta.tool_calls
@@ -135,8 +115,8 @@ def stream_format_output_message(self, completion_chunk) -> Message:
135115
return Message(role='assistant', content=content, reasoning_content=reasoning_content, tool_calls=tool_calls, id=completion_chunk.id)
136116

137117
def format_output_message(self, completion) -> Message:
138-
content = completion.choices[0].message.content
139-
reasoning_content = completion.choices[0].message.reasoning_content
118+
content = completion.choices[0].message.content or ''
119+
reasoning_content = completion.choices[0].message.reasoning_content or ''
140120
tool_calls = None
141121
if completion.choices[0].message.tool_calls:
142122
tool_calls = [ToolCall(
@@ -149,11 +129,10 @@ def format_output_message(self, completion) -> Message:
149129
]
150130
return Message(role='assistant', content=content, reasoning_content=reasoning_content, tool_calls=tool_calls, id=completion.id)
151131

152-
def _continue_generate(self, messages: List[Message], completion, tools: List[Tool] = None, **kwargs):
132+
def _continue_generate(self, messages: List[Message], new_message, tools: List[Tool] = None, **kwargs):
153133
# ref: https://bailian.console.aliyun.com/?tab=doc#/doc/?type=model&url=https%3A%2F%2Fhelp.aliyun.com%2Fdocument_detail%2F2862210.html&renderType=iframe
154134
# TODO: 移到dashscope_llm并找到真正openai的续写方式
155135
if messages[-1].to_dict().get('partial', False):
156-
new_message = self.format_output_message(completion)
157136
messages[-1].reasoning_content += new_message.reasoning_content
158137
messages[-1].content += new_message.content
159138
if new_message.tool_calls:
@@ -162,22 +141,21 @@ def _continue_generate(self, messages: List[Message], completion, tools: List[To
162141
else:
163142
messages[-1].tool_calls = new_message.tool_calls
164143
else:
165-
messages.append(self.format_output_message(completion))
144+
messages.append(new_message)
166145
messages[-1].partial = True
167146

168147
messages = self.format_input_message(messages)
169148
return self._call_llm(messages, tools, **kwargs)
170149

171150
def continue_generate(self, messages: List[Message], completion, tools: List[Tool] = None, **kwargs) -> Message:
172-
# finish_reason: Literal["stop", "length", "tool_calls", "content_filter", "function_call"]
173-
174-
151+
new_message = self.format_output_message(completion)
175152
if completion.choices[0].finish_reason in ['length', 'null']:
153+
print(f'new_message: {new_message}')
176154
print(f'finish_reason: {completion.choices[0].finish_reason}, continue generate.')
177-
completion = self._continue_generate(messages, completion, tools, **kwargs)
155+
completion = self._continue_generate(messages, new_message, tools, **kwargs)
178156
return self.continue_generate(messages, completion, tools, **kwargs)
179157
else:
180-
return self.format_output_message(completion)
158+
return new_message
181159

182160
def format_input_message(self, messages: List[Message]) -> List[Dict[str, Any]]:
183161
openai_messages = []
@@ -200,8 +178,7 @@ def format_input_message(self, messages: List[Message]) -> List[Dict[str, Any]]:
200178
tool_calls.append(tool_call)
201179
message['tool_calls'] = tool_calls
202180

203-
input_msg = {'role', 'content', 'tool_calls', 'partial'}
204-
message = {key: value for key, value in message.items() if key in input_msg and value}
181+
message = {key: value for key, value in message.items() if key in self.input_msg and value}
205182

206183
openai_messages.append(message)
207184

@@ -233,7 +210,7 @@ def format_input_message(self, messages: List[Message]) -> List[Dict[str, Any]]:
233210

234211
# tools = [
235212
# Tool(server_name='amap-maps', tool_name='maps_regeocode', description='将一个高德经纬度坐标转换为行政区划地址信息', parameters={'type': 'object', 'properties': {'location': {'type': 'string', 'description': '经纬度'}}, 'required': ['location']}),
236-
# Tool(tool_name='mkdir', description='在文件系统创建目录', parameters={'type': 'object', 'properties': {'dir_name': {'type': 'string', 'description': '目录名'}}, 'required': ['location']})
213+
# Tool(tool_name='mkdir', description='在文件系统创建目录', parameters={'type': 'object', 'properties': {'dir_name': {'type': 'string', 'description': '目录名'}}, 'required': ['dir_name']})
237214
# ]
238215
tools = None
239216

modelscope_agent/llm/utils.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class Tool(TypedDict, total=False):
3030
class Message:
3131
role: Required[Literal['system', 'user', 'assistant', 'tool']]
3232

33-
content: Required[Union[str, List[Dict[str, 'Message']]]]
33+
content: str = ''
3434

3535
tool_calls: List[ToolCall] = field(default_factory=list)
3636

@@ -46,6 +46,7 @@ class Message:
4646

4747
# 续写模式
4848
partial: bool = False
49+
prefix: bool = False
4950

5051
def to_dict(self):
5152
return asdict(self)

0 commit comments

Comments
 (0)