refine a bit
parent
bfe2430534
commit
8aa98cf515
|
@ -78,12 +78,12 @@ if(Python3_FOUND)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# 添加简单版本的可执行文件
|
# 添加简单版本的可执行文件
|
||||||
add_executable(humanus_simple main_simple.cpp logger.cpp schema.cpp)
|
# add_executable(humanus_simple main_simple.cpp logger.cpp schema.cpp)
|
||||||
target_link_libraries(humanus_simple PRIVATE Threads::Threads ${OPENSSL_LIBRARIES})
|
# target_link_libraries(humanus_simple PRIVATE Threads::Threads ${OPENSSL_LIBRARIES})
|
||||||
if(Python3_FOUND)
|
# if(Python3_FOUND)
|
||||||
target_link_libraries(humanus_simple PRIVATE ${Python3_LIBRARIES})
|
# target_link_libraries(humanus_simple PRIVATE ${Python3_LIBRARIES})
|
||||||
endif()
|
# endif()
|
||||||
|
|
||||||
# 安装目标
|
# 安装目标
|
||||||
install(TARGETS humanus_cpp DESTINATION bin)
|
install(TARGETS humanus_cpp DESTINATION bin)
|
||||||
install(TARGETS humanus_simple DESTINATION bin)
|
# install(TARGETS humanus_simple DESTINATION bin)
|
18
agent/base.h
18
agent/base.h
|
@ -85,7 +85,7 @@ struct BaseAgent : std::enable_shared_from_this<BaseAgent> {
|
||||||
// Execute the agent's main loop asynchronously
|
// Execute the agent's main loop asynchronously
|
||||||
virtual std::string run(const std::string& request = "") {
|
virtual std::string run(const std::string& request = "") {
|
||||||
if (state != AgentState::IDLE) {
|
if (state != AgentState::IDLE) {
|
||||||
throw std::runtime_error("Cannot run agent from state" + agent_state_map[state]);
|
throw std::runtime_error("Cannot run agent from state " + agent_state_map[state]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!request.empty()) {
|
if (!request.empty()) {
|
||||||
|
@ -97,7 +97,15 @@ struct BaseAgent : std::enable_shared_from_this<BaseAgent> {
|
||||||
while (current_step < max_steps && state == AgentState::RUNNING) {
|
while (current_step < max_steps && state == AgentState::RUNNING) {
|
||||||
current_step++;
|
current_step++;
|
||||||
logger->info("Executing step " + std::to_string(current_step) + "/" + std::to_string(max_steps));
|
logger->info("Executing step " + std::to_string(current_step) + "/" + std::to_string(max_steps));
|
||||||
std::string step_result = step();
|
std::string step_result;
|
||||||
|
|
||||||
|
try {
|
||||||
|
step_result = step();
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
logger->error("Error executing step " + std::to_string(current_step) + ": " + std::string(e.what()));
|
||||||
|
state = AgentState::ERROR;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (is_stuck()) {
|
if (is_stuck()) {
|
||||||
this->handle_stuck_state();
|
this->handle_stuck_state();
|
||||||
|
@ -108,7 +116,11 @@ struct BaseAgent : std::enable_shared_from_this<BaseAgent> {
|
||||||
if (current_step >= max_steps) {
|
if (current_step >= max_steps) {
|
||||||
results.push_back("Terminated: Reached max steps (" + std::to_string(max_steps) + ")");
|
results.push_back("Terminated: Reached max steps (" + std::to_string(max_steps) + ")");
|
||||||
}
|
}
|
||||||
state = AgentState::IDLE; // RUNNING -> IDLE
|
if (state == AgentState::ERROR) {
|
||||||
|
results.push_back("Terminated: Agent state is " + agent_state_map[state]);
|
||||||
|
} else {
|
||||||
|
state = AgentState::IDLE; // FINISHED -> IDLE
|
||||||
|
}
|
||||||
|
|
||||||
std::string result_str = "";
|
std::string result_str = "";
|
||||||
|
|
||||||
|
|
|
@ -116,7 +116,7 @@ void PlanningAgent::update_plan_status(const std::string& tool_call_id) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Mark the step as completed
|
// Mark the step as completed
|
||||||
available_tools.execute(
|
ToolResult result = available_tools.execute(
|
||||||
"planning",
|
"planning",
|
||||||
{
|
{
|
||||||
{"command", "mark_step"},
|
{"command", "mark_step"},
|
||||||
|
@ -127,6 +127,7 @@ void PlanningAgent::update_plan_status(const std::string& tool_call_id) {
|
||||||
);
|
);
|
||||||
logger->info(
|
logger->info(
|
||||||
"Marked step " + std::to_string(step_index) + " as completed in plan " + active_plan_id
|
"Marked step " + std::to_string(step_index) + " as completed in plan " + active_plan_id
|
||||||
|
+ "\n\n" + result.to_string() + "\n\n"
|
||||||
);
|
);
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
logger->warn("Failed to update plan status: " + std::string(e.what()));
|
logger->warn("Failed to update plan status: " + std::string(e.what()));
|
||||||
|
@ -195,7 +196,7 @@ int PlanningAgent::_get_current_step_index() {
|
||||||
|
|
||||||
// Create an initial plan based on the request.
|
// Create an initial plan based on the request.
|
||||||
void PlanningAgent::create_initial_plan(const std::string& request) {
|
void PlanningAgent::create_initial_plan(const std::string& request) {
|
||||||
logger->info("Creating initial plan with ID: " + request);
|
logger->info("Creating initial plan with ID: " + active_plan_id);
|
||||||
|
|
||||||
std::vector<Message> messages = {
|
std::vector<Message> messages = {
|
||||||
Message::user_message(
|
Message::user_message(
|
||||||
|
|
|
@ -20,9 +20,9 @@ bool ToolCallAgent::think() {
|
||||||
tool_calls = ToolCall::from_json_list(response["tool_calls"]);
|
tool_calls = ToolCall::from_json_list(response["tool_calls"]);
|
||||||
|
|
||||||
// Log response info
|
// Log response info
|
||||||
logger->info("✨ " + name + "'s thoughts:" + response["content"].dump());
|
logger->info("✨ " + name + "'s thoughts: " + response["content"].dump());
|
||||||
logger->info(
|
logger->info(
|
||||||
"🛠️ " + name + " selected " + std::to_string(tool_calls.size()) + " tool(s) to use"
|
"🛠️ " + name + " selected " + std::to_string(tool_calls.size()) + " tool(s) to use"
|
||||||
);
|
);
|
||||||
if (tool_calls.size() > 0) {
|
if (tool_calls.size() > 0) {
|
||||||
std::string tools_str;
|
std::string tools_str;
|
||||||
|
@ -114,7 +114,11 @@ std::string ToolCallAgent::execute_tool(ToolCall tool_call) {
|
||||||
|
|
||||||
std::string name = tool_call.function.name;
|
std::string name = tool_call.function.name;
|
||||||
if (available_tools.tools_map.find(name) == available_tools.tools_map.end()) {
|
if (available_tools.tools_map.find(name) == available_tools.tools_map.end()) {
|
||||||
return "Error: Unknown tool '" + name + "'";
|
return "Error: Unknown tool '" + name + "'. Please use one of the following tools: " +
|
||||||
|
std::accumulate(available_tools.tools_map.begin(), available_tools.tools_map.end(), std::string(),
|
||||||
|
[](const std::string& a, const auto& b) {
|
||||||
|
return a + (a.empty() ? "" : ", ") + b.first;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[llm]
|
[llm]
|
||||||
model = "deepseek-chat"
|
model = "anthropic/claude-3.7-sonnet"
|
||||||
base_url = "https://api.deepseek.com"
|
base_url = "https://openrouter.ai"
|
||||||
end_point = "/v1/chat/completions"
|
end_point = "/api/v1/chat/completions"
|
||||||
api_key = "sk-93c5bfcb920c4a8aa345791d429b8536"
|
api_key = "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"
|
||||||
max_tokens = 8192
|
max_tokens = 8192
|
|
@ -3,4 +3,11 @@ model = "anthropic/claude-3.7-sonnet"
|
||||||
base_url = "https://openrouter.ai"
|
base_url = "https://openrouter.ai"
|
||||||
end_point = "/api/v1/chat/completions"
|
end_point = "/api/v1/chat/completions"
|
||||||
api_key = "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"
|
api_key = "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"
|
||||||
max_tokens = 8196
|
max_tokens = 8192
|
||||||
|
|
||||||
|
[llm]
|
||||||
|
model = "deepseek-chat"
|
||||||
|
base_url = "https://api.deepseek.com"
|
||||||
|
end_point = "/v1/chat/completions"
|
||||||
|
api_key = "sk-93c5bfcb920c4a8aa345791d429b8536"
|
||||||
|
max_tokens = 8192
|
|
@ -48,6 +48,8 @@ struct BaseFlow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual ~BaseFlow() = default;
|
||||||
|
|
||||||
// Get the primary agent for the flow
|
// Get the primary agent for the flow
|
||||||
std::shared_ptr<BaseAgent> primary_agent() const {
|
std::shared_ptr<BaseAgent> primary_agent() const {
|
||||||
return agents.at(primary_agent_key);
|
return agents.at(primary_agent_key);
|
||||||
|
|
|
@ -58,7 +58,7 @@ std::string PlanningFlow::execute(const std::string& input) {
|
||||||
result += step_result + "\n";
|
result += step_result + "\n";
|
||||||
|
|
||||||
// Check if agent wants to terminate
|
// Check if agent wants to terminate
|
||||||
if (executor->state == AgentState::FINISHED) {
|
if (executor->state == AgentState::FINISHED || executor->state == AgentState::ERROR) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -197,7 +197,6 @@ void PlanningFlow::_get_current_step_info(int& current_step_index, json& step_in
|
||||||
}
|
}
|
||||||
|
|
||||||
current_step_index = i;
|
current_step_index = i;
|
||||||
step_info = step;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
current_step_index = -1;
|
current_step_index = -1;
|
||||||
|
@ -245,7 +244,7 @@ void PlanningFlow::_mark_step_completed() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Mark the step as completed
|
// Mark the step as completed
|
||||||
planning_tool->execute({
|
ToolResult result = planning_tool->execute({
|
||||||
{"command", "mark_step"},
|
{"command", "mark_step"},
|
||||||
{"plan_id", active_plan_id},
|
{"plan_id", active_plan_id},
|
||||||
{"step_index", current_step_index},
|
{"step_index", current_step_index},
|
||||||
|
@ -253,6 +252,7 @@ void PlanningFlow::_mark_step_completed() {
|
||||||
});
|
});
|
||||||
logger->info(
|
logger->info(
|
||||||
"Marked step " + std::to_string(current_step_index) + " as completed in plan " + active_plan_id
|
"Marked step " + std::to_string(current_step_index) + " as completed in plan " + active_plan_id
|
||||||
|
+ "\n\n" + result.to_string() + "\n\n"
|
||||||
);
|
);
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
logger->warn("Failed to update plan status: " + std::string(e.what()));
|
logger->warn("Failed to update plan status: " + std::string(e.what()));
|
||||||
|
|
|
@ -33,11 +33,14 @@ struct PlanningFlow : public BaseFlow {
|
||||||
executor_keys(executor_keys),
|
executor_keys(executor_keys),
|
||||||
active_plan_id(active_plan_id) {
|
active_plan_id(active_plan_id) {
|
||||||
if (!llm) {
|
if (!llm) {
|
||||||
this->llm = LLM::get_instance();
|
this->llm = LLM::get_instance("default");
|
||||||
}
|
}
|
||||||
if (!planning_tool) {
|
if (!planning_tool) {
|
||||||
this->planning_tool = std::make_shared<PlanningTool>();
|
this->planning_tool = std::make_shared<PlanningTool>();
|
||||||
}
|
}
|
||||||
|
if (active_plan_id.empty()) {
|
||||||
|
this->active_plan_id = "plan_" + std::to_string(std::chrono::system_clock::now().time_since_epoch().count());
|
||||||
|
}
|
||||||
if (executor_keys.empty()) {
|
if (executor_keys.empty()) {
|
||||||
for (const auto& [key, agent] : agents) {
|
for (const auto& [key, agent] : agents) {
|
||||||
this->executor_keys.push_back(key);
|
this->executor_keys.push_back(key);
|
||||||
|
|
45
main.cpp
45
main.cpp
|
@ -1,6 +1,7 @@
|
||||||
#include "agent/manus.h"
|
#include "agent/manus.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "prompt.h"
|
#include "prompt.h"
|
||||||
|
#include "flow/flow_factory.h"
|
||||||
|
|
||||||
#if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__))
|
#if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__))
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
@ -42,16 +43,52 @@ int main() {
|
||||||
SetConsoleCtrlHandler(reinterpret_cast<PHANDLER_ROUTINE>(console_ctrl_handler), true);
|
SetConsoleCtrlHandler(reinterpret_cast<PHANDLER_ROUTINE>(console_ctrl_handler), true);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
Manus agent = Manus();
|
// Manus agent = Manus();
|
||||||
|
// while (true) {
|
||||||
|
// std::string prompt;
|
||||||
|
// std::cout << "Enter your prompt (or 'exit' to quit): ";
|
||||||
|
// std::getline(std::cin, prompt);
|
||||||
|
// if (prompt == "exit") {
|
||||||
|
// logger->info("Goodbye!");
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// logger->info("Processing your request...");
|
||||||
|
// agent.run(prompt);
|
||||||
|
// }
|
||||||
|
|
||||||
|
std::shared_ptr<BaseAgent> agent_ptr = std::make_shared<Manus>();
|
||||||
|
std::map<std::string, std::shared_ptr<BaseAgent>> agents;
|
||||||
|
agents["default"] = agent_ptr;
|
||||||
|
|
||||||
|
auto flow = FlowFactory::create_flow(
|
||||||
|
FlowType::PLANNING,
|
||||||
|
nullptr, // llm
|
||||||
|
nullptr, // planning_tool
|
||||||
|
std::vector<std::string>{}, // executor_keys
|
||||||
|
"", // active_plan_id
|
||||||
|
agents, // agents
|
||||||
|
std::vector<std::shared_ptr<BaseTool>>{}, // tools
|
||||||
|
"default" // primary_agent_key
|
||||||
|
);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
if (agent_ptr->current_step == agent_ptr->max_steps) {
|
||||||
|
std::cout << "Program automatically paused after " << agent_ptr->current_step << " steps." << std::endl;
|
||||||
|
std::cout << "Enter your prompt (like 'continue' to continue or 'exit' to quit): ";
|
||||||
|
agent_ptr->current_step = 0;
|
||||||
|
} else {
|
||||||
|
std::cout << "Enter your prompt (or 'exit' to quit): ";
|
||||||
|
}
|
||||||
|
|
||||||
std::string prompt;
|
std::string prompt;
|
||||||
std::cout << "Enter your prompt (or 'exit' to quit): ";
|
|
||||||
std::getline(std::cin, prompt);
|
std::getline(std::cin, prompt);
|
||||||
if (prompt == "exit") {
|
if (prompt == "exit") {
|
||||||
logger->info("Goodbye!");
|
logger->info("Goodbye!");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
logger->info("Processing your request...");
|
|
||||||
agent.run(prompt);
|
std::cout << "Processing your request..." << std::endl;
|
||||||
|
auto result = flow->execute(prompt);
|
||||||
|
std::cout << result << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -13,6 +13,7 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
// 检查是否找到Python
|
// 检查是否找到Python
|
||||||
#ifdef PYTHON_FOUND
|
#ifdef PYTHON_FOUND
|
||||||
|
@ -24,13 +25,31 @@
|
||||||
* @brief Python解释器类,用于执行Python代码
|
* @brief Python解释器类,用于执行Python代码
|
||||||
*/
|
*/
|
||||||
class python_interpreter {
|
class python_interpreter {
|
||||||
|
private:
|
||||||
|
// 互斥锁,确保Python解释器的线程安全
|
||||||
|
mutable std::mutex py_mutex;
|
||||||
|
bool is_initialized;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* @brief 构造函数,初始化Python解释器
|
* @brief 构造函数,初始化Python解释器
|
||||||
*/
|
*/
|
||||||
python_interpreter() {
|
python_interpreter() : is_initialized(false) {
|
||||||
#ifdef PYTHON_FOUND
|
#ifdef PYTHON_FOUND
|
||||||
Py_Initialize();
|
try {
|
||||||
|
Py_Initialize();
|
||||||
|
if (Py_IsInitialized()) {
|
||||||
|
is_initialized = true;
|
||||||
|
// 初始化线程支持
|
||||||
|
PyEval_InitThreads();
|
||||||
|
// 释放GIL,允许其他线程获取
|
||||||
|
PyThreadState *_save = PyEval_SaveThread();
|
||||||
|
} else {
|
||||||
|
std::cerr << "Python解释器初始化失败" << std::endl;
|
||||||
|
}
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "Python解释器初始化异常: " << e.what() << std::endl;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,7 +58,11 @@ public:
|
||||||
*/
|
*/
|
||||||
~python_interpreter() {
|
~python_interpreter() {
|
||||||
#ifdef PYTHON_FOUND
|
#ifdef PYTHON_FOUND
|
||||||
Py_Finalize();
|
if (is_initialized) {
|
||||||
|
std::lock_guard<std::mutex> lock(py_mutex);
|
||||||
|
Py_Finalize();
|
||||||
|
is_initialized = false;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,77 +73,176 @@ public:
|
||||||
*/
|
*/
|
||||||
mcp::json forward(const mcp::json& input) const {
|
mcp::json forward(const mcp::json& input) const {
|
||||||
#ifdef PYTHON_FOUND
|
#ifdef PYTHON_FOUND
|
||||||
if (input.contains("code") && input["code"].is_string()) {
|
if (!is_initialized) {
|
||||||
std::string code = input["code"].get<std::string>();
|
return mcp::json{{"error", "Python解释器未正确初始化"}};
|
||||||
|
|
||||||
// Create scope to manage Python objects automatically
|
|
||||||
PyObject *main_module = PyImport_AddModule("__main__");
|
|
||||||
PyObject *main_dict = PyModule_GetDict(main_module);
|
|
||||||
PyObject *sys_module = PyImport_ImportModule("sys");
|
|
||||||
PyObject *io_module = PyImport_ImportModule("io");
|
|
||||||
PyObject *string_io = PyObject_GetAttrString(io_module, "StringIO");
|
|
||||||
PyObject *sys_stdout = PyObject_CallObject(string_io, nullptr);
|
|
||||||
PyObject *sys_stderr = PyObject_CallObject(string_io, nullptr);
|
|
||||||
|
|
||||||
// Replace sys.stdout and sys.stderr with our StringIO objects
|
|
||||||
PySys_SetObject("stdout", sys_stdout);
|
|
||||||
PySys_SetObject("stderr", sys_stderr);
|
|
||||||
|
|
||||||
// Execute the Python code
|
|
||||||
PyObject *result = PyRun_String(code.c_str(), Py_file_input, main_dict, main_dict);
|
|
||||||
if (!result) {
|
|
||||||
PyErr_Print();
|
|
||||||
}
|
|
||||||
Py_XDECREF(result);
|
|
||||||
|
|
||||||
// Fetch the output and error from the StringIO object
|
|
||||||
PyObject *out_value = PyObject_CallMethod(sys_stdout, "getvalue", nullptr);
|
|
||||||
PyObject *err_value = PyObject_CallMethod(sys_stderr, "getvalue", nullptr);
|
|
||||||
|
|
||||||
// Convert Python string to C++ string
|
|
||||||
std::string output = PyUnicode_AsUTF8(out_value);
|
|
||||||
std::string error = PyUnicode_AsUTF8(err_value);
|
|
||||||
|
|
||||||
// Restore the original sys.stdout and sys.stderr
|
|
||||||
PySys_SetObject("stdout", PySys_GetObject("stdout"));
|
|
||||||
PySys_SetObject("stderr", PySys_GetObject("stderr"));
|
|
||||||
|
|
||||||
// Clean up
|
|
||||||
Py_DECREF(sys_stdout);
|
|
||||||
Py_DECREF(sys_stderr);
|
|
||||||
Py_DECREF(string_io);
|
|
||||||
Py_DECREF(io_module);
|
|
||||||
Py_DECREF(sys_module);
|
|
||||||
|
|
||||||
// Prepare JSON output
|
|
||||||
mcp::json result_json;
|
|
||||||
if (!output.empty()) {
|
|
||||||
result_json["output"] = output;
|
|
||||||
}
|
|
||||||
if (!error.empty()) {
|
|
||||||
result_json["error"] = error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result_json.empty()) {
|
|
||||||
std::string last_line;
|
|
||||||
std::istringstream code_stream(code);
|
|
||||||
while (std::getline(code_stream, last_line, '\n')) {}
|
|
||||||
size_t pos = last_line.find_last_of(';') + 1;
|
|
||||||
pos = last_line.find("=") + 1;
|
|
||||||
while (pos < last_line.size() && isblank(last_line[pos])) {
|
|
||||||
pos++;
|
|
||||||
}
|
|
||||||
if (pos != std::string::npos) {
|
|
||||||
last_line = last_line.substr(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
return mcp::json{{"warning", "No output. Maybe try with print(" + last_line + ")?"}};
|
|
||||||
}
|
|
||||||
|
|
||||||
return result_json;
|
|
||||||
} else {
|
|
||||||
return mcp::json{{"error", "Invalid parameters or code not provided"}};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取GIL锁
|
||||||
|
std::lock_guard<std::mutex> lock(py_mutex);
|
||||||
|
PyGILState_STATE gstate = PyGILState_Ensure();
|
||||||
|
|
||||||
|
mcp::json result_json;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (input.contains("code") && input["code"].is_string()) {
|
||||||
|
std::string code = input["code"].get<std::string>();
|
||||||
|
|
||||||
|
// 获取主模块和字典
|
||||||
|
PyObject *main_module = PyImport_AddModule("__main__");
|
||||||
|
if (!main_module) {
|
||||||
|
PyGILState_Release(gstate);
|
||||||
|
return mcp::json{{"error", "无法获取Python主模块"}};
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *main_dict = PyModule_GetDict(main_module);
|
||||||
|
if (!main_dict) {
|
||||||
|
PyGILState_Release(gstate);
|
||||||
|
return mcp::json{{"error", "无法获取Python主模块字典"}};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导入sys和io模块
|
||||||
|
PyObject *sys_module = PyImport_ImportModule("sys");
|
||||||
|
if (!sys_module) {
|
||||||
|
PyErr_Print();
|
||||||
|
PyGILState_Release(gstate);
|
||||||
|
return mcp::json{{"error", "无法导入sys模块"}};
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *io_module = PyImport_ImportModule("io");
|
||||||
|
if (!io_module) {
|
||||||
|
Py_DECREF(sys_module);
|
||||||
|
PyErr_Print();
|
||||||
|
PyGILState_Release(gstate);
|
||||||
|
return mcp::json{{"error", "无法导入io模块"}};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取StringIO类
|
||||||
|
PyObject *string_io = PyObject_GetAttrString(io_module, "StringIO");
|
||||||
|
if (!string_io) {
|
||||||
|
Py_DECREF(io_module);
|
||||||
|
Py_DECREF(sys_module);
|
||||||
|
PyErr_Print();
|
||||||
|
PyGILState_Release(gstate);
|
||||||
|
return mcp::json{{"error", "无法获取StringIO类"}};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建StringIO对象
|
||||||
|
PyObject *sys_stdout = PyObject_CallObject(string_io, nullptr);
|
||||||
|
if (!sys_stdout) {
|
||||||
|
Py_DECREF(string_io);
|
||||||
|
Py_DECREF(io_module);
|
||||||
|
Py_DECREF(sys_module);
|
||||||
|
PyErr_Print();
|
||||||
|
PyGILState_Release(gstate);
|
||||||
|
return mcp::json{{"error", "无法创建stdout StringIO对象"}};
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *sys_stderr = PyObject_CallObject(string_io, nullptr);
|
||||||
|
if (!sys_stderr) {
|
||||||
|
Py_DECREF(sys_stdout);
|
||||||
|
Py_DECREF(string_io);
|
||||||
|
Py_DECREF(io_module);
|
||||||
|
Py_DECREF(sys_module);
|
||||||
|
PyErr_Print();
|
||||||
|
PyGILState_Release(gstate);
|
||||||
|
return mcp::json{{"error", "无法创建stderr StringIO对象"}};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存原始的stdout和stderr
|
||||||
|
PyObject *old_stdout = PySys_GetObject("stdout");
|
||||||
|
PyObject *old_stderr = PySys_GetObject("stderr");
|
||||||
|
|
||||||
|
if (old_stdout) Py_INCREF(old_stdout);
|
||||||
|
if (old_stderr) Py_INCREF(old_stderr);
|
||||||
|
|
||||||
|
// 替换sys.stdout和sys.stderr
|
||||||
|
if (PySys_SetObject("stdout", sys_stdout) != 0 ||
|
||||||
|
PySys_SetObject("stderr", sys_stderr) != 0) {
|
||||||
|
Py_DECREF(sys_stderr);
|
||||||
|
Py_DECREF(sys_stdout);
|
||||||
|
Py_DECREF(string_io);
|
||||||
|
Py_DECREF(io_module);
|
||||||
|
Py_DECREF(sys_module);
|
||||||
|
PyErr_Print();
|
||||||
|
PyGILState_Release(gstate);
|
||||||
|
return mcp::json{{"error", "无法设置stdout/stderr重定向"}};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行Python代码
|
||||||
|
PyObject *result = PyRun_String(code.c_str(), Py_file_input, main_dict, main_dict);
|
||||||
|
if (!result) {
|
||||||
|
PyErr_Print();
|
||||||
|
}
|
||||||
|
Py_XDECREF(result);
|
||||||
|
|
||||||
|
// 获取输出和错误
|
||||||
|
PyObject *out_value = PyObject_CallMethod(sys_stdout, "getvalue", nullptr);
|
||||||
|
PyObject *err_value = PyObject_CallMethod(sys_stderr, "getvalue", nullptr);
|
||||||
|
|
||||||
|
std::string output, error;
|
||||||
|
|
||||||
|
// 安全地转换Python字符串到C++字符串
|
||||||
|
if (out_value && PyUnicode_Check(out_value)) {
|
||||||
|
output = PyUnicode_AsUTF8(out_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err_value && PyUnicode_Check(err_value)) {
|
||||||
|
error = PyUnicode_AsUTF8(err_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 恢复原始的stdout和stderr
|
||||||
|
if (old_stdout) {
|
||||||
|
PySys_SetObject("stdout", old_stdout);
|
||||||
|
Py_DECREF(old_stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (old_stderr) {
|
||||||
|
PySys_SetObject("stderr", old_stderr);
|
||||||
|
Py_DECREF(old_stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理
|
||||||
|
Py_XDECREF(out_value);
|
||||||
|
Py_XDECREF(err_value);
|
||||||
|
Py_DECREF(sys_stdout);
|
||||||
|
Py_DECREF(sys_stderr);
|
||||||
|
Py_DECREF(string_io);
|
||||||
|
Py_DECREF(io_module);
|
||||||
|
Py_DECREF(sys_module);
|
||||||
|
|
||||||
|
// 准备JSON输出
|
||||||
|
if (!output.empty()) {
|
||||||
|
result_json["output"] = output;
|
||||||
|
}
|
||||||
|
if (!error.empty()) {
|
||||||
|
result_json["error"] = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result_json.empty()) {
|
||||||
|
std::string last_line;
|
||||||
|
std::istringstream code_stream(code);
|
||||||
|
while (std::getline(code_stream, last_line, '\n')) {}
|
||||||
|
size_t pos = last_line.find_last_of(';') + 1;
|
||||||
|
pos = last_line.find("=") + 1;
|
||||||
|
while (pos < last_line.size() && isblank(last_line[pos])) {
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
if (pos != std::string::npos) {
|
||||||
|
last_line = last_line.substr(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
result_json["warning"] = "No output. Maybe try with print(" + last_line + ")?";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result_json["error"] = "Invalid parameters or code not provided";
|
||||||
|
}
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
result_json["error"] = std::string("Python执行异常: ") + e.what();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 释放GIL
|
||||||
|
PyGILState_Release(gstate);
|
||||||
|
return result_json;
|
||||||
#else
|
#else
|
||||||
return mcp::json{{"error", "Python interpreter not available"}};
|
return mcp::json{{"error", "Python interpreter not available"}};
|
||||||
#endif
|
#endif
|
||||||
|
@ -133,7 +255,6 @@ static python_interpreter interpreter;
|
||||||
// Python执行工具处理函数
|
// Python执行工具处理函数
|
||||||
mcp::json python_execute_handler(const mcp::json& args) {
|
mcp::json python_execute_handler(const mcp::json& args) {
|
||||||
if (!args.contains("code")) {
|
if (!args.contains("code")) {
|
||||||
std::cout << args.dump() << std::endl;
|
|
||||||
throw mcp::mcp_exception(mcp::error_code::invalid_params, "缺少'code'参数");
|
throw mcp::mcp_exception(mcp::error_code::invalid_params, "缺少'code'参数");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,25 +262,9 @@ mcp::json python_execute_handler(const mcp::json& args) {
|
||||||
// 使用Python解释器执行代码
|
// 使用Python解释器执行代码
|
||||||
mcp::json result = interpreter.forward(args);
|
mcp::json result = interpreter.forward(args);
|
||||||
|
|
||||||
// 格式化输出结果
|
|
||||||
std::string output;
|
|
||||||
if (result.contains("output")) {
|
|
||||||
output += result["output"].get<std::string>();
|
|
||||||
}
|
|
||||||
if (result.contains("error")) {
|
|
||||||
output += result["error"].get<std::string>();
|
|
||||||
}
|
|
||||||
if (result.contains("warning")) {
|
|
||||||
output += result["warning"].get<std::string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (output.empty()) {
|
|
||||||
output = "Executed successfully but w/o output. Maybe you should print more information.";
|
|
||||||
}
|
|
||||||
|
|
||||||
return {{
|
return {{
|
||||||
{"type", "text"},
|
{"type", "text"},
|
||||||
{"text", output}
|
{"text", result.dump(2)}
|
||||||
}};
|
}};
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
throw mcp::mcp_exception(mcp::error_code::internal_error,
|
throw mcp::mcp_exception(mcp::error_code::internal_error,
|
||||||
|
@ -167,12 +272,11 @@ mcp::json python_execute_handler(const mcp::json& args) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 注册Python执行工具的函数
|
// Register the PythonExecute tool
|
||||||
void register_python_execute_tool(mcp::server& server) {
|
void register_python_execute_tool(mcp::server& server) {
|
||||||
// 注册PythonExecute工具
|
mcp::tool python_tool = mcp::tool_builder("python_execute")
|
||||||
mcp::tool python_tool = mcp::tool_builder("PythonExecute")
|
.with_description("Execute Python code and return the result")
|
||||||
.with_description("执行Python代码并返回结果")
|
.with_string_param("code", "The Python code to execute", true)
|
||||||
.with_string_param("code", "要执行的Python代码", true)
|
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
server.register_tool(python_tool, python_execute_handler);
|
server.register_tool(python_tool, python_execute_handler);
|
||||||
|
|
12
tool/base.h
12
tool/base.h
|
@ -149,8 +149,16 @@ struct ToolResult {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::string parse_json_content(const json& content) {
|
||||||
|
if (content.is_string()) {
|
||||||
|
return content.get<std::string>();
|
||||||
|
} else {
|
||||||
|
return content.dump(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::string to_string() const {
|
std::string to_string() const {
|
||||||
return !error.empty() ? "Error: " + error.dump() : output.dump();
|
return !error.empty() ? "Error: " + parse_json_content(error) : parse_json_content(output);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -161,7 +169,7 @@ struct ToolError : ToolResult {
|
||||||
|
|
||||||
// Execute the tool with given parameters.
|
// Execute the tool with given parameters.
|
||||||
struct BaseTool {
|
struct BaseTool {
|
||||||
inline static std::set<std::string> special_tool_name = {"terminate"};
|
inline static std::set<std::string> special_tool_name = {"terminate", "planning"};
|
||||||
|
|
||||||
std::string name;
|
std::string name;
|
||||||
std::string description;
|
std::string description;
|
||||||
|
|
|
@ -62,6 +62,19 @@ struct FileSystem : BaseTool {
|
||||||
"required": ["tool"]
|
"required": ["tool"]
|
||||||
})json");
|
})json");
|
||||||
|
|
||||||
|
inline static std::set<std::string> allowed_tools = {
|
||||||
|
"read_file",
|
||||||
|
"read_multiple_files",
|
||||||
|
"write_file",
|
||||||
|
"edit_file",
|
||||||
|
"create_directory",
|
||||||
|
"list_directory",
|
||||||
|
"move_file",
|
||||||
|
"search_files",
|
||||||
|
"get_file_info",
|
||||||
|
"list_allowed_directories"
|
||||||
|
};
|
||||||
|
|
||||||
FileSystem() : BaseTool(name_, description_, parameters_) {}
|
FileSystem() : BaseTool(name_, description_, parameters_) {}
|
||||||
|
|
||||||
ToolResult execute(const json& args) override {
|
ToolResult execute(const json& args) override {
|
||||||
|
@ -82,6 +95,14 @@ struct FileSystem : BaseTool {
|
||||||
return ToolError("Tool is required");
|
return ToolError("Tool is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (allowed_tools.find(tool) == allowed_tools.end()) {
|
||||||
|
return ToolError("Unknown tool '" + tool + "'. Please use one of the following tools: " +
|
||||||
|
std::accumulate(allowed_tools.begin(), allowed_tools.end(), std::string(),
|
||||||
|
[](const std::string& a, const std::string& b) {
|
||||||
|
return a + (a.empty() ? "" : ", ") + b;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
json result = _client->call_tool(tool, args);
|
json result = _client->call_tool(tool, args);
|
||||||
|
|
||||||
bool is_error = result.value("isError", false);
|
bool is_error = result.value("isError", false);
|
||||||
|
|
|
@ -254,7 +254,7 @@ ToolResult PlanningTool::_delete_plan(const std::string& plan_id) {
|
||||||
// Format a plan for display.
|
// Format a plan for display.
|
||||||
std::string PlanningTool::_format_plan(const json& plan) {
|
std::string PlanningTool::_format_plan(const json& plan) {
|
||||||
std::stringstream output_ss;
|
std::stringstream output_ss;
|
||||||
output_ss << "Plan ID: " << plan["plan_id"].get<std::string>() << "\n";
|
output_ss << "Plan: " << plan.value("title", "Unknown Plan") << " (ID: " << plan["plan_id"].get<std::string>() << ")\n";
|
||||||
int current_length = output_ss.str().length();
|
int current_length = output_ss.str().length();
|
||||||
|
|
||||||
for (int i = 0; i < current_length; i++) {
|
for (int i = 0; i < current_length; i++) {
|
||||||
|
|
|
@ -58,6 +58,16 @@ struct Puppeteer : BaseTool {
|
||||||
"required": ["tool"]
|
"required": ["tool"]
|
||||||
})json");
|
})json");
|
||||||
|
|
||||||
|
inline static std::set<std::string> allowed_tools = {
|
||||||
|
"navigate",
|
||||||
|
"screenshot",
|
||||||
|
"click",
|
||||||
|
"hover",
|
||||||
|
"fill",
|
||||||
|
"select",
|
||||||
|
"evaluate"
|
||||||
|
};
|
||||||
|
|
||||||
Puppeteer() : BaseTool(name_, description_, parameters_) {}
|
Puppeteer() : BaseTool(name_, description_, parameters_) {}
|
||||||
|
|
||||||
ToolResult execute(const json& args) override {
|
ToolResult execute(const json& args) override {
|
||||||
|
@ -78,6 +88,14 @@ struct Puppeteer : BaseTool {
|
||||||
return ToolError("Tool is required");
|
return ToolError("Tool is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (allowed_tools.find(tool) == allowed_tools.end()) {
|
||||||
|
return ToolError("Unknown tool '" + tool + "'. Please use one of the following tools: " +
|
||||||
|
std::accumulate(allowed_tools.begin(), allowed_tools.end(), std::string(),
|
||||||
|
[](const std::string& a, const std::string& b) {
|
||||||
|
return a + (a.empty() ? "" : ", ") + b;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
json result = _client->call_tool("puppeteer_" + tool, args);
|
json result = _client->call_tool("puppeteer_" + tool, args);
|
||||||
|
|
||||||
bool is_error = result.value("isError", false);
|
bool is_error = result.value("isError", false);
|
||||||
|
|
Loading…
Reference in New Issue