Skip to content

Commit

Permalink
MCP Client去除appbuilder相关内容 (#759)
Browse files Browse the repository at this point in the history
  • Loading branch information
userpj authored Feb 28, 2025
1 parent a9cfb5e commit db4337f
Show file tree
Hide file tree
Showing 10 changed files with 131 additions and 75 deletions.
15 changes: 10 additions & 5 deletions python/core/console/appbuilder_client/appbuilder_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ def run(
query: str = "",
file_ids: list = [],
stream: bool = False,
tools: list[Union[data_class.Tool, Manifest]] = None,
tools: list[Union[data_class.Tool, Manifest, data_class.MCPTool]] = None,
tool_outputs: list[data_class.ToolOutput] = None,
tool_choice: data_class.ToolChoice = None,
end_user_id: str = None,
Expand All @@ -265,7 +265,7 @@ def run(
conversation_id (str): 唯一会话ID,如需开始新的会话,请使用self.create_conversation创建新的会话
file_ids(list[str]): 文件ID列表
stream (bool): 为True时,流式返回,需要将message.content.answer拼接起来才是完整的回答;为False时,对应非流式返回
tools(list[Union[data_class.Tool,Manifest]]): 一个Tool或Manifest组成的列表,其中每个Tool(Manifest)对应一个工具的配置, 默认为None
tools(list[Union[data_class.Tool,Manifest,data_class.MCPTool]]): 一个Tool或Manifest组成的列表,其中每个Tool(Manifest)对应一个工具的配置, 默认为None
tool_outputs(list[data_class.ToolOutput]): 工具输出列表,格式为list[ToolOutput], ToolOutputd内容为本地的工具执行结果,以自然语言/json dump str描述,默认为None
tool_choice(data_class.ToolChoice): 控制大模型使用组件的方式,默认为None
end_user_id (str): 用户ID,用于区分不同用户
Expand All @@ -286,6 +286,12 @@ def run(
"AppBuilderClient Run API: query and tool_outputs cannot both be empty"
)

if tools:
formatted_tools = [
data_class.ToAppBuilderTool(tool) for tool in tools
]
tools = formatted_tools

req = data_class.AppBuilderClientRequest(
app_id=self.app_id,
conversation_id=conversation_id,
Expand Down Expand Up @@ -368,7 +374,7 @@ def run_with_handler(
conversation_id: str,
query: str = "",
file_ids: list = [],
tools: list[Union[data_class.Tool, Manifest]] = None,
tools: list[Union[data_class.Tool, Manifest, data_class.MCPTool]] = None,
stream: bool = False,
event_handler=None,
action=None,
Expand All @@ -380,11 +386,10 @@ def run_with_handler(
conversation_id (str): 唯一会话ID,如需开始新的会话,请使用self.create_conversation创建新的会话
query (str): 查询字符串
file_ids (list): 文件ID列表
tools(list[Union[data_class.Tool,Manifest]], 可选): 一个Tool或Manifest组成的列表,其中每个Tool(Manifest)对应一个工具的配置, 默认为None
tools(list[Union[data_class.Tool,Manifest,data_class.MCPTool]], 可选): 一个Tool或Manifest组成的列表,其中每个Tool(Manifest)对应一个工具的配置, 默认为None
stream (bool): 是否流式响应
event_handler (EventHandler): 事件处理器
action(data_class.Action) 对话时要进行的特殊操作。如回复工作流agent中“信息收集节点“的消息。
kwargs: 其他参数
Returns:
Expand Down
15 changes: 10 additions & 5 deletions python/core/console/appbuilder_client/async_appbuilder_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ async def run(
query: str = "",
file_ids: list = [],
stream: bool = False,
tools: list[Union[data_class.Tool, Manifest]] = None,
tools: list[Union[data_class.Tool, Manifest, data_class.MCPTool]] = None,
tool_outputs: list[data_class.ToolOutput] = None,
tool_choice: data_class.ToolChoice = None,
end_user_id: str = None,
Expand All @@ -76,7 +76,7 @@ async def run(
conversation_id (str): 唯一会话ID,如需开始新的会话,请使用self.create_conversation创建新的会话
file_ids(list[str]): 文件ID列表
stream (bool): 为True时,流式返回,需要将message.content.answer拼接起来才是完整的回答;为False时,对应非流式返回
tools(list[Union[data_class.Tool,Manifest]]): 一个Tool或Manifest组成的列表,其中每个Tool(Manifest)对应一个工具的配置, 默认为None
tools(list[Union[data_class.Tool,Manifest,data_class.MCPTool]]): 一个Tool或Manifest组成的列表,其中每个Tool(Manifest)对应一个工具的配置, 默认为None
tool_outputs(list[data_class.ToolOutput]): 工具输出列表,格式为list[ToolOutput], ToolOutputd内容为本地的工具执行结果,以自然语言/json dump str描述,默认为None
tool_choice(data_class.ToolChoice): 控制大模型使用组件的方式,默认为None
end_user_id (str): 用户ID,用于区分不同用户
Expand All @@ -97,6 +97,12 @@ async def run(
"AppBuilderClient Run API: query and tool_outputs cannot both be empty"
)

if tools:
formatted_tools = [
data_class.ToAppBuilderTool(tool) for tool in tools
]
tools = formatted_tools

req = data_class.AppBuilderClientRequest(
app_id=self.app_id,
conversation_id=conversation_id,
Expand Down Expand Up @@ -174,7 +180,7 @@ async def run_with_handler(
conversation_id: str,
query: str = "",
file_ids: list = [],
tools: list[Union[data_class.Tool, Manifest]] = None,
tools: list[Union[data_class.Tool, Manifest, data_class.MCPTool]] = None,
stream: bool = False,
event_handler=None,
action=None,
Expand All @@ -186,11 +192,10 @@ async def run_with_handler(
conversation_id (str): 唯一会话ID,如需开始新的会话,请使用self.create_conversation创建新的会话
query (str): 查询字符串
file_ids (list): 文件ID列表
tools(list[Union[data_class.Tool,Manifest]], 可选): 一个Tool或Manifest组成的列表,其中每个Tool(Manifest)对应一个工具的配置, 默认为None
tools(list[Union[data_class.Tool,Manifest,data_class.MCPTool]], 可选): 一个Tool或Manifest组成的列表,其中每个Tool(Manifest)对应一个工具的配置, 默认为None
stream (bool): 是否流式响应
event_handler (EventHandler): 事件处理器
action(data_class.Action) 对话时要进行的特殊操作。如回复工作流agent中“信息收集节点“的消息。
kwargs: 其他参数
Returns:
Expand Down
20 changes: 19 additions & 1 deletion python/core/console/appbuilder_client/async_event_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from appbuilder.utils.logger_util import logger
from appbuilder.core.console.appbuilder_client import data_class


class AppBuilderClientRunContext(object):
def __init__(self) -> None:
"""
Expand Down Expand Up @@ -81,13 +80,32 @@ async def init(
self._need_tool_call = False
self._last_tool_output = None
self._action = action
await self.__format_tools__()

self._iterator = (
self.__run_process__()
if not self._stream
else self.__stream_run_process__()
)

async def __format_tools__(self):
"""
完善tools。
Args:
无参数。
Returns:
无参数
"""
if not self._tools:
return
tools = [
data_class.ToAppBuilderTool(tool) for tool in self._tools
]
self._tools = tools

async def __run_process__(self):
"""
运行进程,并在每次执行后生成结果。
Expand Down
14 changes: 13 additions & 1 deletion python/core/console/appbuilder_client/data_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# limitations under the License.

from pydantic import BaseModel
from pydantic import Field
from pydantic import Field, ValidationError
from typing import Union
from typing import Optional
from appbuilder.core.manifest.models import Manifest
Expand All @@ -29,6 +29,18 @@ class Tool(BaseModel):
type: str = "function"
function: Function = Field(..., description="工具信息")

class MCPTool(BaseModel):
name: str = Field(..., description="工具名称")
description: str = Field(..., description="工具描述")
inputSchema: dict = Field(..., description="工具参数, json_schema格式")

def ToAppBuilderTool(tool):
if "type" in tool and tool["type"]:
return Tool(**tool)
if hasattr(tool, 'inputSchema') and hasattr(tool, 'inputSchema'):
return Tool(type="function", function=Function(name=tool.name, description=tool.description, parameters=tool.inputSchema))
else:
return tool

class ToolOutput(BaseModel):
tool_call_id: str = Field(..., description="工具调用ID")
Expand Down
19 changes: 19 additions & 0 deletions python/core/console/appbuilder_client/event_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,29 @@ def init(
self._need_tool_call = False
self._last_tool_output = None
self._action = action
self.__format_tools__()

self._iterator = self.__run_process__(
) if not self._stream else self.__stream_run_process__()

def __format_tools__(self):
"""
完善tools。
Args:
无参数。
Returns:
无参数
"""
if not self._tools:
return
tools = [
data_class.ToAppBuilderTool(tool) for tool in self._tools
]
self._tools = tools

def __run_process__(self):
"""
运行进程,并在每次执行后生成结果。
Expand Down
17 changes: 0 additions & 17 deletions python/modelcontextprotocol/client.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
# Based on https://modelcontextprotocol.io/quickstart/client
# adapted to AppBuilderClient
# add multi servers support
import sys
from typing import Optional
from contextlib import AsyncExitStack
Expand All @@ -9,14 +7,12 @@
from mcp.client.stdio import stdio_client
from appbuilder.utils.logger_util import logger


class MCPClient:
def __init__(self):
# Initialize session and client objects
self.session: Optional[ClientSession] = None
self.exit_stack = AsyncExitStack()
self.tools = None
self.appbuilder_tools = []
self.sessions={}
self.tool_to_server = {}

Expand Down Expand Up @@ -63,19 +59,6 @@ async def connect_to_server(self, server_script_path: str):
raise ValueError(f"Duplicate tool name: {tool.name}")
self.tool_to_server[tool.name] = cmd_key

for tool in response.tools:
self.appbuilder_tools.append(
{
"type": "function",
"function": {
"name": f"{tool.name}",
"description": f"{tool.description}",
"parameters": tool.inputSchema,
},
}
)
logger.debug("AppBuilder tools: %s", self.appbuilder_tools)

async def call_tool(self, tool_name, tool_args):
server_cmd = self.tool_to_server.get(tool_name)
if server_cmd is None:
Expand Down
36 changes: 18 additions & 18 deletions python/tests/test_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,24 +159,24 @@ def test_chat_with_valid_message_and_streaming(self):
for it in answer.content:
self.assertIs(type(it), str)

def test_chainlit_agent_component_error(self):
""" 测试chainlit agent组件错误 """
component = Component()
agent = AgentRuntime(component=component)
subprocess.check_call(
[sys.executable, "-m", "pip", "uninstall", "-y", "chainlit"]
)
with self.assertRaises(ImportError):
agent.chainlit_agent()
subprocess.check_call(
[sys.executable, "-m", "pip", "install", "chainlit~=1.0.200"]
)
with self.assertRaises(ValueError):
agent.chainlit_agent()
os.environ["APPBUILDER_RUN_CHAINLIT"] = "1"
agent_builder = AppBuilderClient(self.app_id)
agent = AgentRuntime(component=agent_builder)
agent.chainlit_agent()
# def test_chainlit_agent_component_error(self):
# """ 测试chainlit agent组件错误 """
# component = Component()
# agent = AgentRuntime(component=component)
# subprocess.check_call(
# [sys.executable, "-m", "pip", "uninstall", "-y", "chainlit"]
# )
# with self.assertRaises(ImportError):
# agent.chainlit_agent()
# subprocess.check_call(
# [sys.executable, "-m", "pip", "install", "chainlit~=1.0.200"]
# )
# with self.assertRaises(ValueError):
# agent.chainlit_agent()
# os.environ["APPBUILDER_RUN_CHAINLIT"] = "1"
# agent_builder = AppBuilderClient(self.app_id)
# agent = AgentRuntime(component=agent_builder)
# agent.chainlit_agent()


if __name__ == '__main__':
Expand Down
66 changes: 40 additions & 26 deletions python/tests/test_appbuilder_client_mcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,39 +10,53 @@
)


@appbuilder.manifest(
description="获取指定中国城市的当前天气信息。仅支持中国城市的天气查询。参数 `location` 为中国城市名称,其他国家城市不支持天气查询。"
)
@appbuilder.manifest_parameter(
name="location", description="城市名,例如:北京。"
)
@appbuilder.manifest_parameter(
name="unit", description="温度单位,支持 'celsius' 或 'fahrenheit'"
)
def get_current_weather(location: str, unit: str) -> str:
return "北京今天25度"

functions = [get_current_weather]

class MyEventHandler(AsyncAppBuilderEventHandler):
def __init__(self, mcp_client):
super().__init__()
self.mcp_client = mcp_client

async def interrupt(self, run_context, run_response):
async def interrupt(self, run_context, run_response):
thought = run_context.current_thought
# 绿色打印
print("\033[1;31m", "-> Agent 中间思考: ", thought, "\033[0m")

tool_output = []
for tool_call in run_context.current_tool_calls:
tool_call_id = tool_call.id
print(
"\033[1;32m",
"MCP工具名称: {}, MCP参数:{}\n".format(
tool_call.function.name, tool_call.function.arguments
),
"\033[0m",
)
mcp_server_result = await self.mcp_client.call_tool(
tool_call.function.name, tool_call.function.arguments
)
print("\033[1;33m", "MCP结果: {}\n\033[0m".format(mcp_server_result))
index = 0
for i, content in enumerate(mcp_server_result.content):
if content.type == "text":
index = i
break
function_name = tool_call.function.name
function_arguments = tool_call.function.arguments
result = ""
function_map = {f.__name__: f for f in functions}
if function_name in function_map:
result = function_map[function_name](**tool_call.function.arguments)
print("\033[1;33m", "本地function结果: {}\n\033[0m".format(result))
else:
print("\033[1;32m","MCP工具名称: {}, MCP参数:{}\n".format(function_name, function_arguments),"\033[0m")
mcp_server_result = await self.mcp_client.call_tool(
function_name, function_arguments
)
print("\033[1;33m", "MCP结果: {}\n\033[0m".format(mcp_server_result))
index = 0
for i, content in enumerate(mcp_server_result.content):
if content.type == "text":
index = i
result = result + mcp_server_result.content[index].text
tool_output.append(
{
"tool_call_id": tool_call_id,
"output": mcp_server_result.content[index].text,
"tool_call_id": tool_call.id,
"output": result,
}
)
return tool_output
Expand Down Expand Up @@ -81,10 +95,12 @@ def test_appbuilder_client_mcp(self):

async def agent_run(client, mcp_client, query):
conversation_id = await client.create_conversation()
tools = [appbuilder.Manifest.from_function(f) for f in functions]
tools.extend(mcp_client.tools)
with await client.run_with_handler(
conversation_id=conversation_id,
query=query,
tools=mcp_client.appbuilder_tools,
tools=tools,
event_handler=MyEventHandler(mcp_client),
) as run:
await run.until_done()
Expand All @@ -96,12 +112,10 @@ async def handler():
await mcp_client.connect_to_server(
"./data/mcp_official_server_sample.py"
)
await mcp_client.connect_to_server(
"./data/mcp_component_server_sample.py"
)
await agent_run(appbuilder_client, mcp_client, "北京的天气怎么样")
await agent_run(appbuilder_client, mcp_client, "美国马萨诸塞州的天气")
await agent_run(appbuilder_client, mcp_client, "翻译“你好”为英文")
finally:
await appbuilder_client.http_client.session.close()
await mcp_client.cleanup()

subprocess.check_call(
Expand Down
Loading

0 comments on commit db4337f

Please sign in to comment.