add humanus_server (mcp)
parent
7c797864dd
commit
26c45d966e
11
agent/base.h
11
agent/base.h
|
@ -157,7 +157,7 @@ struct BaseAgent : std::enable_shared_from_this<BaseAgent> {
|
|||
memory->add_message(Message::user_message(stuck_prompt));
|
||||
}
|
||||
|
||||
// O(nm) LCS algorithm, could basically handle current LLM context
|
||||
// O(n * m) LCS algorithm, could basically handle current LLM context
|
||||
size_t get_lcs_length(const std::string& s1, const std::string& s2) {
|
||||
std::vector<std::vector<size_t>> dp(s1.size() + 1, std::vector<size_t>(s2.size() + 1));
|
||||
for (size_t i = 1; i <= s1.size(); i++) {
|
||||
|
@ -209,10 +209,19 @@ struct BaseAgent : std::enable_shared_from_this<BaseAgent> {
|
|||
void reset(bool reset_memory = true) {
|
||||
current_step = 0;
|
||||
state = AgentState::IDLE;
|
||||
llm->reset_tokens();
|
||||
if (reset_memory) {
|
||||
memory->clear();
|
||||
}
|
||||
}
|
||||
|
||||
size_t get_prompt_tokens() {
|
||||
return llm->get_prompt_tokens();
|
||||
}
|
||||
|
||||
size_t get_completion_tokens() {
|
||||
return llm->get_completion_tokens();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace humanus
|
||||
|
|
|
@ -35,11 +35,13 @@ struct ReActAgent : BaseAgent {
|
|||
// Execute a single step: think and act.
|
||||
virtual std::string step() {
|
||||
bool should_act = think();
|
||||
logger->info("Prompt tokens: " + std::to_string(llm->total_prompt_tokens()) + ", Completion tokens: " + std::to_string(llm->total_completion_tokens()));
|
||||
if (!should_act) {
|
||||
return "Thinking complete - no action needed";
|
||||
}
|
||||
return act();
|
||||
if (state == AgentState::RUNNING) {
|
||||
return act();
|
||||
}
|
||||
return "Agent is not running";
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -30,6 +30,10 @@ bool ToolCallAgent::think() {
|
|||
);
|
||||
}
|
||||
|
||||
if (state != AgentState::RUNNING) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// Handle different tool_choices modes
|
||||
if (tool_choice == "none") {
|
||||
|
@ -76,6 +80,10 @@ std::string ToolCallAgent::act() {
|
|||
|
||||
std::vector<std::string> results;
|
||||
for (const auto& tool_call : tool_calls) {
|
||||
if (state != AgentState::RUNNING) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto result = execute_tool(tool_call);
|
||||
logger->info(
|
||||
"🎯 Tool `" + tool_call.function.name + "` completed its mission! Result: " + result.substr(0, 500) + (result.size() > 500 ? "..." : "")
|
||||
|
@ -94,6 +102,10 @@ std::string ToolCallAgent::act() {
|
|||
result_str += result + "\n\n";
|
||||
}
|
||||
|
||||
if (state != AgentState::RUNNING) {
|
||||
result_str += "Agent is not running, so no more tool calls will be executed.\n\n";
|
||||
}
|
||||
|
||||
return result_str;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,16 +1,8 @@
|
|||
# examples/CMakeLists.txt
|
||||
# 构建所有examples目录
|
||||
|
||||
# 获取examples目录下的所有子目录
|
||||
file(GLOB EXAMPLE_DIRS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/*)
|
||||
|
||||
# 遍历所有子目录
|
||||
foreach(EXAMPLE_DIR ${EXAMPLE_DIRS})
|
||||
# 检查是否是目录
|
||||
if(IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${EXAMPLE_DIR})
|
||||
# 检查子目录中是否有CMakeLists.txt文件
|
||||
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${EXAMPLE_DIR}/CMakeLists.txt")
|
||||
# 添加子目录
|
||||
add_subdirectory(${EXAMPLE_DIR})
|
||||
message(STATUS "Added example: ${EXAMPLE_DIR}")
|
||||
endif()
|
||||
|
|
|
@ -2,5 +2,4 @@ set(target humanus_chat)
|
|||
|
||||
add_executable(${target} humanus_chat.cpp)
|
||||
|
||||
# 链接到核心库
|
||||
target_link_libraries(${target} PRIVATE humanus)
|
|
@ -2,11 +2,4 @@ set(target humanus_cli)
|
|||
|
||||
add_executable(${target} main.cpp)
|
||||
|
||||
# 链接到核心库
|
||||
target_link_libraries(${target} PRIVATE humanus)
|
||||
|
||||
# 设置输出目录
|
||||
set_target_properties(${target}
|
||||
PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
|
||||
)
|
||||
target_link_libraries(${target} PRIVATE humanus)
|
|
@ -2,11 +2,4 @@ set(target humanus_cli_mcp)
|
|||
|
||||
add_executable(${target} humanus_mcp.cpp)
|
||||
|
||||
# 链接到核心库
|
||||
target_link_libraries(${target} PRIVATE humanus)
|
||||
|
||||
# 设置输出目录
|
||||
set_target_properties(${target}
|
||||
PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
|
||||
)
|
||||
target_link_libraries(${target} PRIVATE humanus)
|
|
@ -2,11 +2,4 @@ set(target humanus_cli_plan)
|
|||
|
||||
add_executable(${target} humanus_plan.cpp)
|
||||
|
||||
# 链接到核心库
|
||||
target_link_libraries(${target} PRIVATE humanus)
|
||||
|
||||
# 设置输出目录
|
||||
set_target_properties(${target}
|
||||
PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
|
||||
)
|
||||
target_link_libraries(${target} PRIVATE humanus)
|
|
@ -0,0 +1,5 @@
|
|||
set(target humanus_server)
|
||||
|
||||
add_executable(${target} server.cpp)
|
||||
|
||||
target_link_libraries(${target} PRIVATE humanus)
|
|
@ -0,0 +1,222 @@
|
|||
#include "httplib.h"
|
||||
#include "agent/humanus.h"
|
||||
#include "logger.h"
|
||||
#include "mcp_server.h"
|
||||
#include "mcp_tool.h"
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <random>
|
||||
#include <unordered_map>
|
||||
|
||||
using namespace humanus;
|
||||
|
||||
static auto session_sink = SessionSink::get_instance();
|
||||
|
||||
class SessionManager {
|
||||
public:
|
||||
std::shared_ptr<Humanus> get_agent(const std::string& session_id) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
auto it = agents_.find(session_id);
|
||||
if (it != agents_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
auto agent = std::make_shared<Humanus>();
|
||||
agents_[session_id] = agent;
|
||||
|
||||
return agent;
|
||||
}
|
||||
|
||||
static std::vector<std::string> get_logs_buffer(const std::string& session_id) {
|
||||
return session_sink->get_buffer(session_id);
|
||||
}
|
||||
|
||||
static std::vector<std::string> get_logs_history(const std::string& session_id) {
|
||||
return session_sink->get_history(session_id);
|
||||
}
|
||||
|
||||
void set_result(const std::string& session_id, const std::string& result) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
results_[session_id] = result;
|
||||
}
|
||||
|
||||
std::string get_result(const std::string& session_id) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
auto it = results_.find(session_id);
|
||||
if (it != results_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
void clear_result(const std::string& session_id) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
results_.erase(session_id);
|
||||
}
|
||||
|
||||
bool has_session(const std::string& session_id) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return agents_.find(session_id) != agents_.end();
|
||||
}
|
||||
|
||||
void close_session(const std::string& session_id) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
agents_.erase(session_id);
|
||||
results_.erase(session_id);
|
||||
session_sink->cleanup_session(session_id);
|
||||
}
|
||||
|
||||
std::vector<std::string> get_all_sessions() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
std::vector<std::string> sessions;
|
||||
for (const auto& pair : agents_) {
|
||||
sessions.push_back(pair.first);
|
||||
}
|
||||
return sessions;
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex mutex_;
|
||||
std::unordered_map<std::string, std::shared_ptr<Humanus>> agents_;
|
||||
std::unordered_map<std::string, std::string> results_;
|
||||
std::unordered_map<std::string, std::shared_ptr<SessionSink>> session_sinks_;
|
||||
};
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
mcp::set_log_level(mcp::log_level::warning);
|
||||
|
||||
int port = 8896;
|
||||
|
||||
if (argc == 2) {
|
||||
try {
|
||||
port = std::stoi(argv[1]);
|
||||
} catch (...) {
|
||||
std::cerr << "Invalid port number: " << argv[1] << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Create and configure server
|
||||
mcp::server server("localhost", port, "HumanusServer", "0.0.1");
|
||||
|
||||
// Set server capabilities
|
||||
mcp::json capabilities = {
|
||||
{"tools", mcp::json::object()}
|
||||
};
|
||||
server.set_capabilities(capabilities);
|
||||
|
||||
auto session_manager = std::make_shared<SessionManager>();
|
||||
|
||||
auto run_tool = mcp::tool_builder("humanus_run")
|
||||
.with_description("Request to start a new task. Best to give clear and concise prompts.")
|
||||
.with_string_param("prompt", "The prompt text to process", true)
|
||||
.build();
|
||||
|
||||
server.register_tool(run_tool, [session_manager](const json& args, const std::string& session_id) -> json {
|
||||
if (!args.contains("prompt")) {
|
||||
throw mcp::mcp_exception(mcp::error_code::invalid_params, "Missing `prompt` parameter");
|
||||
}
|
||||
|
||||
std::string prompt = args["prompt"].get<std::string>();
|
||||
|
||||
auto agent = session_manager->get_agent(session_id);
|
||||
|
||||
if (agent->state != AgentState::IDLE) {
|
||||
throw mcp::mcp_exception(mcp::error_code::invalid_request, "The agent is busy, please wait for the current task to complete or terminate the current task.");
|
||||
}
|
||||
|
||||
agent->reset();
|
||||
|
||||
std::thread([agent, session_manager, prompt, session_id]() {
|
||||
try {
|
||||
session_sink->set_session_id(session_id);
|
||||
logger->info("Processing your request: " + prompt);
|
||||
auto result = agent->run(prompt);
|
||||
logger->info("Task completed.");
|
||||
session_manager->set_result(session_id, result);
|
||||
} catch (const std::exception& e) {
|
||||
logger->error("Session {} error: {}", session_id, e.what());
|
||||
}
|
||||
}).detach();
|
||||
|
||||
return {{
|
||||
{"type", "text"},
|
||||
{"text", "Task started, call `humanus_status` to check the status."}
|
||||
}};
|
||||
});
|
||||
|
||||
auto terminate_tool = mcp::tool_builder("humanus_terminate")
|
||||
.with_description("Terminate the current task")
|
||||
.build();
|
||||
|
||||
server.register_tool(terminate_tool, [session_manager](const json& args, const std::string& session_id) -> json {
|
||||
if (!session_manager->has_session(session_id)) {
|
||||
throw mcp::mcp_exception(mcp::error_code::invalid_request, "Session not found");
|
||||
}
|
||||
|
||||
auto agent = session_manager->get_agent(session_id);
|
||||
|
||||
if (agent->state == AgentState::IDLE) {
|
||||
return {{
|
||||
{"type", "text"},
|
||||
{"text", "The agent is idle, no task to terminate."}
|
||||
}};
|
||||
}
|
||||
|
||||
agent->update_memory("user", "User interrupted the interaction. Consider rescheduling the previous task or switching to a different task according to the user's request.");
|
||||
agent->state = AgentState::IDLE;
|
||||
|
||||
logger->info("Task terminated by user.");
|
||||
|
||||
return {{
|
||||
{"type", "text"},
|
||||
{"text", "Task terminated."}
|
||||
}};
|
||||
});
|
||||
|
||||
auto status_tool = mcp::tool_builder("humanus_status")
|
||||
.with_description("Get the status of the current task.")
|
||||
.build();
|
||||
|
||||
server.register_tool(status_tool, [session_manager](const json& args, const std::string& session_id) -> json {
|
||||
if (!session_manager->has_session(session_id)) {
|
||||
throw mcp::mcp_exception(mcp::error_code::invalid_request, "Session not found");
|
||||
}
|
||||
|
||||
auto agent = session_manager->get_agent(session_id);
|
||||
auto result = session_manager->get_result(session_id);
|
||||
|
||||
json status = {
|
||||
{"state", agent_state_map[agent->state]},
|
||||
{"current_step", agent->current_step},
|
||||
{"max_steps", agent->max_steps},
|
||||
{"prompt_tokens", agent->get_prompt_tokens()},
|
||||
{"completion_tokens", agent->get_completion_tokens()},
|
||||
{"logs_buffer", session_sink->get_buffer(session_id)},
|
||||
{"result", result}
|
||||
};
|
||||
|
||||
return {{
|
||||
{"type", "text"},
|
||||
{"text", status.dump(2)}
|
||||
}};
|
||||
});
|
||||
|
||||
// Start server
|
||||
std::cout << "Starting Humanus server at http://localhost:" << port << "..." << std::endl;
|
||||
std::cout << "Press Ctrl+C to stop server" << std::endl;
|
||||
server.start(true); // Blocking mode
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -120,13 +120,18 @@ public:
|
|||
int max_retries = 3
|
||||
);
|
||||
|
||||
size_t total_prompt_tokens() const {
|
||||
size_t get_prompt_tokens() const {
|
||||
return total_prompt_tokens_;
|
||||
}
|
||||
|
||||
size_t total_completion_tokens() const {
|
||||
size_t get_completion_tokens() const {
|
||||
return total_completion_tokens_;
|
||||
}
|
||||
|
||||
void reset_tokens() {
|
||||
total_prompt_tokens_ = 0;
|
||||
total_completion_tokens_ = 0;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace humanus
|
||||
|
|
173
include/logger.h
173
include/logger.h
|
@ -1,33 +1,180 @@
|
|||
#ifndef HUMANUSlogger_H
|
||||
#define HUMANUSlogger_H
|
||||
|
||||
#include "config.h"
|
||||
#include "spdlog/spdlog.h"
|
||||
#include "spdlog/sinks/stdout_color_sinks.h"
|
||||
#include "spdlog/sinks/basic_file_sink.h"
|
||||
#include "spdlog/sinks/rotating_file_sink.h"
|
||||
#include "spdlog/sinks/daily_file_sink.h"
|
||||
#include "spdlog/sinks/dist_sink.h"
|
||||
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
#include "config.h"
|
||||
#include <mutex>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace humanus {
|
||||
|
||||
static spdlog::level::level_enum _print_level = spdlog::level::info;
|
||||
static spdlog::level::level_enum _logfile_level = spdlog::level::debug;
|
||||
|
||||
/**
|
||||
* @brief Adjust the log level
|
||||
* @param print_level The console output log level
|
||||
* @param logfile_level The file record log level
|
||||
* @param name The log file name prefix
|
||||
* @return The log record instance
|
||||
*/
|
||||
extern std::shared_ptr<spdlog::logger> define_log_level(spdlog::level::level_enum print_level = spdlog::level::info,
|
||||
spdlog::level::level_enum logfile_level = spdlog::level::debug,
|
||||
std::string name = "");
|
||||
extern std::shared_ptr<spdlog::logger> set_log_level(spdlog::level::level_enum print_level, spdlog::level::level_enum logfile_level);
|
||||
|
||||
static std::shared_ptr<spdlog::logger> logger = define_log_level();
|
||||
class SessionSink : public spdlog::sinks::base_sink<std::mutex> {
|
||||
private:
|
||||
inline static std::unordered_map<std::string, std::vector<std::string>> buffers_; // session_id -> buffer
|
||||
inline static std::unordered_map<std::string, std::vector<std::string>> histories_; // session_id -> history
|
||||
inline static std::unordered_map<std::string, std::string> sessions_; // thread_id -> session_id
|
||||
inline static std::mutex mutex_;
|
||||
|
||||
SessionSink() = default;
|
||||
SessionSink(const SessionSink&) = delete;
|
||||
SessionSink& operator=(const SessionSink&) = delete;
|
||||
|
||||
public:
|
||||
~SessionSink() = default;
|
||||
|
||||
static std::shared_ptr<SessionSink> get_instance() {
|
||||
static SessionSink instance;
|
||||
static std::shared_ptr<SessionSink> shared_instance(&instance, [](SessionSink*){});
|
||||
return shared_instance;
|
||||
}
|
||||
|
||||
void sink_it_(const spdlog::details::log_msg& msg) override {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
if (sessions_.find(get_thread_id()) == sessions_.end()) { // Ignore messages if session_id is not set
|
||||
return;
|
||||
}
|
||||
|
||||
auto session_id = sessions_[get_thread_id()];
|
||||
|
||||
auto time_t = std::chrono::system_clock::to_time_t(msg.time);
|
||||
auto tm = fmt::localtime(time_t);
|
||||
std::string log_message = fmt::format("[{:%Y-%m-%d %H:%M:%S}] {}", tm, msg.payload);
|
||||
|
||||
buffers_[session_id].push_back(log_message);
|
||||
}
|
||||
|
||||
void flush_() override {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
if (sessions_.find(get_thread_id()) == sessions_.end()) { // Ignore messages if session_id is not set
|
||||
return;
|
||||
}
|
||||
|
||||
auto session_id = sessions_[get_thread_id()];
|
||||
|
||||
if (!buffers_[session_id].empty()) {
|
||||
histories_[session_id].insert(histories_[session_id].end(),
|
||||
buffers_[session_id].begin(),
|
||||
buffers_[session_id].end());
|
||||
buffers_[session_id].clear();
|
||||
}
|
||||
}
|
||||
|
||||
inline std::string get_thread_id() {
|
||||
std::stringstream ss;
|
||||
ss << std::this_thread::get_id();
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
void set_session_id(std::string session_id) {
|
||||
if (session_id.empty()) {
|
||||
throw std::invalid_argument("session_id is empty");
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
sessions_[get_thread_id()] = session_id;
|
||||
}
|
||||
|
||||
// Messages in buffer are flushed to the history and cleared from the buffer.
|
||||
std::vector<std::string> get_buffer(const std::string& session_id) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
if (buffers_.find(session_id) == buffers_.end()) {
|
||||
throw std::invalid_argument("Invalid session_id: " + session_id);
|
||||
}
|
||||
|
||||
std::vector<std::string> buffer = buffers_[session_id];
|
||||
if (!buffers_[session_id].empty()) {
|
||||
histories_[session_id].insert(histories_[session_id].end(),
|
||||
buffers_[session_id].begin(),
|
||||
buffers_[session_id].end());
|
||||
buffers_[session_id].clear();
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
std::vector<std::string> get_history(const std::string& session_id) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
if (histories_.find(session_id) == histories_.end()) {
|
||||
throw std::invalid_argument("Invalid session_id: " + session_id);
|
||||
}
|
||||
|
||||
return histories_[session_id];
|
||||
}
|
||||
|
||||
void clear_buffer() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
if (sessions_.find(get_thread_id()) == sessions_.end()) { // Ignore messages if session_id is not set
|
||||
return;
|
||||
}
|
||||
|
||||
auto session_id = sessions_[get_thread_id()];
|
||||
|
||||
if (!buffers_[session_id].empty()) {
|
||||
histories_[session_id].insert(histories_[session_id].end(),
|
||||
buffers_[session_id].begin(),
|
||||
buffers_[session_id].end());
|
||||
buffers_[session_id].clear();
|
||||
}
|
||||
}
|
||||
|
||||
void clear_history() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
if (sessions_.find(get_thread_id()) == sessions_.end()) { // Ignore messages if session_id is not set
|
||||
return;
|
||||
}
|
||||
|
||||
auto session_id = sessions_[get_thread_id()];
|
||||
|
||||
histories_[session_id].clear();
|
||||
}
|
||||
|
||||
void cleanup_session(const std::string& session_id) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
for (auto it = sessions_.begin(); it != sessions_.end();) {
|
||||
if (it->second == session_id) {
|
||||
it = sessions_.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
buffers_.erase(session_id);
|
||||
histories_.erase(session_id);
|
||||
}
|
||||
|
||||
std::vector<std::string> get_active_sessions() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
std::vector<std::string> result;
|
||||
std::unordered_set<std::string> unique_sessions;
|
||||
|
||||
for (const auto& [thread_id, session_id] : sessions_) {
|
||||
if (unique_sessions.insert(session_id).second) {
|
||||
result.push_back(session_id);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
static std::shared_ptr<spdlog::logger> logger = set_log_level(_print_level, _logfile_level);
|
||||
|
||||
} // namespace humanus
|
||||
|
||||
|
|
2
mcp
2
mcp
|
@ -1 +1 @@
|
|||
Subproject commit 21dc5cb1448923abc2566568403d837f0a9da64a
|
||||
Subproject commit 0dbcd1e6d5bef3443c9d4b3cff6c6e3adef72264
|
|
@ -14,6 +14,7 @@
|
|||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
// Check if Python is found
|
||||
#ifdef PYTHON_FOUND
|
||||
|
@ -22,13 +23,16 @@
|
|||
|
||||
/**
|
||||
* @class python_interpreter
|
||||
* @brief Python interpreter class for executing Python code
|
||||
* @brief Python interpreter class for executing Python code with session support
|
||||
*/
|
||||
class python_interpreter {
|
||||
private:
|
||||
// Mutex to ensure thread safety of Python interpreter
|
||||
mutable std::mutex py_mutex;
|
||||
bool is_initialized;
|
||||
|
||||
// Map to store Python thread states for each session
|
||||
mutable std::unordered_map<std::string, PyThreadState*> session_states;
|
||||
|
||||
public:
|
||||
/**
|
||||
|
@ -40,7 +44,9 @@ public:
|
|||
Py_Initialize();
|
||||
if (Py_IsInitialized()) {
|
||||
is_initialized = true;
|
||||
PyThreadState *_save = PyEval_SaveThread();
|
||||
// Create main thread state
|
||||
PyThreadState* main_thread_state = PyThreadState_Get();
|
||||
PyThreadState_Swap(NULL);
|
||||
} else {
|
||||
std::cerr << "Failed to initialize Python interpreter" << std::endl;
|
||||
}
|
||||
|
@ -57,27 +63,69 @@ public:
|
|||
#ifdef PYTHON_FOUND
|
||||
if (is_initialized) {
|
||||
std::lock_guard<std::mutex> lock(py_mutex);
|
||||
// Clean up all session states
|
||||
for (auto& pair : session_states) {
|
||||
PyThreadState_Swap(pair.second);
|
||||
PyThreadState_Clear(pair.second);
|
||||
PyThreadState_Delete(pair.second);
|
||||
}
|
||||
session_states.clear();
|
||||
|
||||
Py_Finalize();
|
||||
is_initialized = false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get or create a thread state for a session
|
||||
* @param session_id The session identifier
|
||||
* @return PyThreadState for the session
|
||||
*/
|
||||
#ifdef PYTHON_FOUND
|
||||
PyThreadState* get_session_state(const std::string& session_id) const {
|
||||
std::lock_guard<std::mutex> lock(py_mutex);
|
||||
|
||||
// Check if session already exists
|
||||
auto it = session_states.find(session_id);
|
||||
if (it != session_states.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
// Create new thread state for this session
|
||||
PyThreadState* new_state = Py_NewInterpreter();
|
||||
if (!new_state) {
|
||||
throw std::runtime_error("Failed to create new Python interpreter for session" + session_id);
|
||||
}
|
||||
|
||||
// Store and return the new state
|
||||
session_states[session_id] = new_state;
|
||||
return new_state;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Execute Python code
|
||||
* @brief Execute Python code in the context of a specific session
|
||||
* @param input JSON object containing Python code
|
||||
* @param session_id The session identifier
|
||||
* @return JSON object with execution results
|
||||
*/
|
||||
mcp::json forward(const mcp::json& input) const {
|
||||
mcp::json forward(const mcp::json& input, const std::string& session_id) const {
|
||||
#ifdef PYTHON_FOUND
|
||||
if (!is_initialized) {
|
||||
return mcp::json{{"error", "Python interpreter not properly initialized"}};
|
||||
}
|
||||
|
||||
// Acquire GIL lock
|
||||
std::lock_guard<std::mutex> lock(py_mutex);
|
||||
PyGILState_STATE gstate = PyGILState_Ensure();
|
||||
// Get or create session thread state
|
||||
PyThreadState* tstate = nullptr;
|
||||
try {
|
||||
tstate = get_session_state(session_id);
|
||||
} catch (const std::exception& e) {
|
||||
return mcp::json{{"error", e.what()}};
|
||||
}
|
||||
|
||||
PyThreadState* old_state = PyThreadState_Swap(tstate);
|
||||
|
||||
mcp::json result_json;
|
||||
|
||||
try {
|
||||
|
@ -87,13 +135,13 @@ public:
|
|||
// Get main module and dictionary
|
||||
PyObject *main_module = PyImport_AddModule("__main__");
|
||||
if (!main_module) {
|
||||
PyGILState_Release(gstate);
|
||||
PyThreadState_Swap(old_state);
|
||||
return mcp::json{{"error", "Failed to get Python main module"}};
|
||||
}
|
||||
|
||||
PyObject *main_dict = PyModule_GetDict(main_module);
|
||||
if (!main_dict) {
|
||||
PyGILState_Release(gstate);
|
||||
PyThreadState_Swap(old_state);
|
||||
return mcp::json{{"error", "Failed to get Python main module dictionary"}};
|
||||
}
|
||||
|
||||
|
@ -101,7 +149,7 @@ public:
|
|||
PyObject *sys_module = PyImport_ImportModule("sys");
|
||||
if (!sys_module) {
|
||||
PyErr_Print();
|
||||
PyGILState_Release(gstate);
|
||||
PyThreadState_Swap(old_state);
|
||||
return mcp::json{{"error", "Failed to import sys module"}};
|
||||
}
|
||||
|
||||
|
@ -109,7 +157,7 @@ public:
|
|||
if (!io_module) {
|
||||
Py_DECREF(sys_module);
|
||||
PyErr_Print();
|
||||
PyGILState_Release(gstate);
|
||||
PyThreadState_Swap(old_state);
|
||||
return mcp::json{{"error", "Failed to import io module"}};
|
||||
}
|
||||
|
||||
|
@ -119,7 +167,7 @@ public:
|
|||
Py_DECREF(io_module);
|
||||
Py_DECREF(sys_module);
|
||||
PyErr_Print();
|
||||
PyGILState_Release(gstate);
|
||||
PyThreadState_Swap(old_state);
|
||||
return mcp::json{{"error", "Failed to get StringIO class"}};
|
||||
}
|
||||
|
||||
|
@ -130,7 +178,7 @@ public:
|
|||
Py_DECREF(io_module);
|
||||
Py_DECREF(sys_module);
|
||||
PyErr_Print();
|
||||
PyGILState_Release(gstate);
|
||||
PyThreadState_Swap(old_state);
|
||||
return mcp::json{{"error", "Failed to create stdout StringIO object"}};
|
||||
}
|
||||
|
||||
|
@ -141,7 +189,7 @@ public:
|
|||
Py_DECREF(io_module);
|
||||
Py_DECREF(sys_module);
|
||||
PyErr_Print();
|
||||
PyGILState_Release(gstate);
|
||||
PyThreadState_Swap(old_state);
|
||||
return mcp::json{{"error", "Failed to create stderr StringIO object"}};
|
||||
}
|
||||
|
||||
|
@ -161,7 +209,7 @@ public:
|
|||
Py_DECREF(io_module);
|
||||
Py_DECREF(sys_module);
|
||||
PyErr_Print();
|
||||
PyGILState_Release(gstate);
|
||||
PyThreadState_Swap(old_state);
|
||||
return mcp::json{{"error", "Failed to set stdout/stderr redirection"}};
|
||||
}
|
||||
|
||||
|
@ -237,11 +285,32 @@ public:
|
|||
result_json["error"] = std::string("Python execution exception: ") + e.what();
|
||||
}
|
||||
|
||||
// Release GIL
|
||||
PyGILState_Release(gstate);
|
||||
// Restore previous thread state
|
||||
PyThreadState_Swap(old_state);
|
||||
|
||||
return result_json;
|
||||
#else
|
||||
return mcp::json{{"error", "Python interpreter not available"}};
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clean up a session and remove its thread state
|
||||
* @param session_id The session identifier to clean up
|
||||
*/
|
||||
void cleanup_session(const std::string& session_id) {
|
||||
#ifdef PYTHON_FOUND
|
||||
std::lock_guard<std::mutex> lock(py_mutex);
|
||||
|
||||
auto it = session_states.find(session_id);
|
||||
if (it != session_states.end()) {
|
||||
PyThreadState* old_state = PyThreadState_Swap(it->second);
|
||||
PyThreadState_Clear(it->second);
|
||||
PyThreadState_Delete(it->second);
|
||||
PyThreadState_Swap(old_state);
|
||||
|
||||
session_states.erase(it);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
@ -250,14 +319,14 @@ public:
|
|||
static python_interpreter interpreter;
|
||||
|
||||
// Python execution tool handler function
|
||||
mcp::json python_execute_handler(const mcp::json& args) {
|
||||
mcp::json python_execute_handler(const mcp::json& args, const std::string& session_id) {
|
||||
if (!args.contains("code")) {
|
||||
throw mcp::mcp_exception(mcp::error_code::invalid_params, "Missing 'code' parameter");
|
||||
}
|
||||
|
||||
try {
|
||||
// Use Python interpreter to execute code
|
||||
mcp::json result = interpreter.forward(args);
|
||||
// Use Python interpreter to execute code with session context
|
||||
mcp::json result = interpreter.forward(args, session_id);
|
||||
|
||||
return {{
|
||||
{"type", "text"},
|
||||
|
@ -277,4 +346,9 @@ void register_python_execute_tool(mcp::server& server) {
|
|||
.build();
|
||||
|
||||
server.register_tool(python_tool, python_execute_handler);
|
||||
|
||||
// Register session cleanup handler
|
||||
server.register_session_cleanup("python_execute", [](const std::string& session_id) {
|
||||
interpreter.cleanup_session(session_id);
|
||||
});
|
||||
}
|
|
@ -6,20 +6,16 @@
|
|||
|
||||
namespace humanus {
|
||||
|
||||
std::shared_ptr<spdlog::logger> define_log_level(spdlog::level::level_enum print_level,
|
||||
spdlog::level::level_enum logfile_level,
|
||||
std::string name) {
|
||||
_print_level = print_level;
|
||||
|
||||
std::shared_ptr<spdlog::logger> set_log_level(spdlog::level::level_enum print_level, spdlog::level::level_enum logfile_level) {
|
||||
auto current_date = std::chrono::system_clock::now();
|
||||
auto in_time_t = std::chrono::system_clock::to_time_t(current_date);
|
||||
|
||||
std::stringstream ss;
|
||||
std::tm tm_info = *std::localtime(&in_time_t);
|
||||
ss << std::put_time(&tm_info, "%Y%m%d");
|
||||
std::string formatted_date = ss.str(); // YYYYMMDD
|
||||
ss << std::put_time(&tm_info, "%Y-%m-%d");
|
||||
std::string formatted_date = ss.str(); // YYYY-MM-DD
|
||||
|
||||
std::string log_name = name.empty() ? formatted_date : name + "_" + formatted_date;
|
||||
std::string log_name = formatted_date;
|
||||
std::string log_file_path = (PROJECT_ROOT / "logs" / (log_name + ".log")).string();
|
||||
|
||||
// Ensure the log directory exists
|
||||
|
@ -29,13 +25,18 @@ std::shared_ptr<spdlog::logger> define_log_level(spdlog::level::level_enum print
|
|||
std::shared_ptr<spdlog::logger> _logger = std::make_shared<spdlog::logger>(log_name);
|
||||
|
||||
auto stderr_sink = std::make_shared<spdlog::sinks::stderr_color_sink_mt>();
|
||||
stderr_sink->set_level(print_level);
|
||||
stderr_sink->set_level(_print_level);
|
||||
_logger->sinks().push_back(stderr_sink);
|
||||
|
||||
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(log_file_path, false);
|
||||
file_sink->set_level(logfile_level);
|
||||
file_sink->set_level(_logfile_level);
|
||||
_logger->sinks().push_back(file_sink);
|
||||
|
||||
|
||||
auto session_sink = SessionSink::get_instance();
|
||||
session_sink->set_level(_print_level);
|
||||
_logger->sinks().push_back(session_sink);
|
||||
|
||||
// Reset the log output
|
||||
return _logger;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue