mirror of
https://github.com/zenlm/zen-agent.git
synced 2026-10-30 14:58:52 +00:00
194 lines
8.3 KiB
Python
194 lines
8.3 KiB
Python
# Copyright 2023 The Qwen team, Alibaba Group. All rights reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import json
|
|
import os
|
|
from http import HTTPStatus
|
|
from pprint import pformat
|
|
from typing import Dict, Iterator, List, Optional
|
|
|
|
import dashscope
|
|
|
|
from qwen_agent.llm.base import ModelServiceError, register_llm
|
|
from qwen_agent.llm.function_calling import BaseFnCallModel
|
|
from qwen_agent.llm.schema import ASSISTANT, FunctionCall, Message
|
|
from qwen_agent.log import logger
|
|
|
|
|
|
@register_llm('qwen_dashscope')
|
|
class QwenChatAtDS(BaseFnCallModel):
|
|
|
|
def __init__(self, cfg: Optional[Dict] = None):
|
|
super().__init__(cfg)
|
|
self.model = self.model or 'qwen-max'
|
|
initialize_dashscope(cfg)
|
|
|
|
def _chat_stream(
|
|
self,
|
|
messages: List[Message],
|
|
delta_stream: bool,
|
|
generate_cfg: dict,
|
|
) -> Iterator[List[Message]]:
|
|
messages = [msg.model_dump() for msg in messages]
|
|
if messages[-1]['role'] == ASSISTANT:
|
|
messages[-1]['partial'] = True
|
|
messages = self._conv_qwen_agent_messages_to_oai(messages)
|
|
logger.debug(f'LLM Input: \n{pformat(messages, indent=2)}')
|
|
logger.debug(f'LLM Input generate_cfg: \n{generate_cfg}')
|
|
response = dashscope.Generation.call(
|
|
self.model,
|
|
messages=messages, # noqa
|
|
result_format='message',
|
|
stream=True,
|
|
**generate_cfg)
|
|
if delta_stream:
|
|
return self._delta_stream_output(response)
|
|
else:
|
|
return self._full_stream_output(response)
|
|
|
|
def _chat_no_stream(
|
|
self,
|
|
messages: List[Message],
|
|
generate_cfg: dict,
|
|
) -> List[Message]:
|
|
messages = [msg.model_dump() for msg in messages]
|
|
if messages[-1]['role'] == ASSISTANT:
|
|
messages[-1]['partial'] = True
|
|
messages = self._conv_qwen_agent_messages_to_oai(messages)
|
|
logger.debug(f'LLM Input: \n{pformat(messages, indent=2)}')
|
|
response = dashscope.Generation.call(
|
|
self.model,
|
|
messages=messages, # noqa
|
|
result_format='message',
|
|
stream=False,
|
|
**generate_cfg)
|
|
if response.status_code == HTTPStatus.OK:
|
|
return [
|
|
Message(role=ASSISTANT,
|
|
content=response.output.choices[0].message.content,
|
|
reasoning_content=response.output.choices[0].message.get('reasoning_content', ''),
|
|
extra={'model_service_info': response})
|
|
]
|
|
else:
|
|
raise ModelServiceError(code=response.code,
|
|
message=response.message,
|
|
extra={'model_service_info': response})
|
|
|
|
def _continue_assistant_response(
|
|
self,
|
|
messages: List[Message],
|
|
generate_cfg: dict,
|
|
stream: bool,
|
|
) -> Iterator[List[Message]]:
|
|
return self._chat(messages, stream=stream, delta_stream=False, generate_cfg=generate_cfg)
|
|
|
|
@staticmethod
|
|
def _delta_stream_output(response) -> Iterator[List[Message]]:
|
|
for chunk in response:
|
|
if chunk.status_code == HTTPStatus.OK:
|
|
yield [
|
|
Message(role=ASSISTANT,
|
|
content=chunk.output.choices[0].message.content,
|
|
reasoning_content=chunk.output.choices[0].message.reasoning_content,
|
|
extra={'model_service_info': chunk})
|
|
]
|
|
else:
|
|
raise ModelServiceError(code=chunk.code, message=chunk.message, extra={'model_service_info': chunk})
|
|
|
|
@staticmethod
|
|
def _full_stream_output(response) -> Iterator[List[Message]]:
|
|
full_content = ''
|
|
full_reasoning_content = ''
|
|
full_tool_calls = []
|
|
for chunk in response:
|
|
if chunk.status_code == HTTPStatus.OK:
|
|
if chunk.output.choices[0].message.get('reasoning_content', ''):
|
|
full_reasoning_content += chunk.output.choices[0].message.reasoning_content
|
|
if chunk.output.choices[0].message.content:
|
|
full_content += chunk.output.choices[0].message.content
|
|
tool_calls = chunk.output.choices[0].message.get('tool_calls', None)
|
|
if tool_calls:
|
|
for tc in tool_calls:
|
|
if full_tool_calls and (not tc['id'] or
|
|
tc['id'] == full_tool_calls[-1]['extra']['function_id']):
|
|
if tc['function'].get('name', ''):
|
|
full_tool_calls[-1].function_call['name'] += tc['function']['name']
|
|
if tc['function'].get('arguments', ''):
|
|
full_tool_calls[-1].function_call['arguments'] += tc['function']['arguments']
|
|
else:
|
|
full_tool_calls.append(
|
|
Message(role=ASSISTANT,
|
|
content='',
|
|
function_call=FunctionCall(name=tc['function'].get('name', ''),
|
|
arguments=tc['function'].get('arguments', '')),
|
|
extra={
|
|
'model_service_info': json.loads(str(chunk)),
|
|
'function_id': tc['id']
|
|
}))
|
|
res = []
|
|
if full_reasoning_content:
|
|
res.append(
|
|
Message(role=ASSISTANT,
|
|
content='',
|
|
reasoning_content=full_reasoning_content,
|
|
extra={
|
|
'model_service_info': json.loads(str(chunk)),
|
|
}))
|
|
if full_content:
|
|
res.append(
|
|
Message(role=ASSISTANT,
|
|
content=full_content,
|
|
extra={
|
|
'model_service_info': json.loads(str(chunk)),
|
|
}))
|
|
if full_tool_calls:
|
|
res += full_tool_calls
|
|
yield res
|
|
else:
|
|
raise ModelServiceError(code=chunk.code, message=chunk.message, extra={'model_service_info': chunk})
|
|
|
|
|
|
def initialize_dashscope(cfg: Optional[Dict] = None) -> None:
|
|
cfg = cfg or {}
|
|
|
|
api_key = cfg.get('api_key', '')
|
|
base_http_api_url = cfg.get('base_http_api_url', None)
|
|
base_websocket_api_url = cfg.get('base_websocket_api_url', None)
|
|
|
|
if not api_key:
|
|
api_key = os.getenv('DASHSCOPE_API_KEY', 'EMPTY')
|
|
if not base_http_api_url:
|
|
base_http_api_url = os.getenv('DASHSCOPE_HTTP_URL', None)
|
|
if not base_websocket_api_url:
|
|
base_websocket_api_url = os.getenv('DASHSCOPE_WEBSOCKET_URL', None)
|
|
|
|
api_key = api_key.strip()
|
|
if api_key in ('', 'EMPTY'):
|
|
if dashscope.api_key is None or dashscope.api_key in ('', 'EMPTY'):
|
|
logger.warning(
|
|
'No valid dashscope api_key found in cfg, environment variable `DASHSCOPE_API_KEY` or dashscope.api_key, the model call may raise errors.'
|
|
)
|
|
else:
|
|
logger.info('No dashscope api_key found in cfg, using the dashscope.api_key that has already been set.')
|
|
else: # valid api_key
|
|
if api_key != dashscope.api_key:
|
|
logger.info('Setting the dashscope api_key.')
|
|
dashscope.api_key = api_key
|
|
# or do nothing since both keys are the same
|
|
|
|
if base_http_api_url is not None:
|
|
dashscope.base_http_api_url = base_http_api_url.strip()
|
|
if base_websocket_api_url is not None:
|
|
dashscope.base_websocket_api_url = base_websocket_api_url.strip()
|