From 10c2ef0bbc1143d171804441d65b6b801d85d456 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E6=BD=AE?= Date: Tue, 7 Oct 2025 12:25:41 +0800 Subject: [PATCH] openai api --- __pycache__/agent_pool.cpython-312.pyc | Bin 0 -> 8267 bytes __pycache__/fastapi_app.cpython-312.pyc | Bin 0 -> 12537 bytes __pycache__/gbase_agent.cpython-312.pyc | Bin 9777 -> 8150 bytes __pycache__/project_config.cpython-312.pyc | Bin 0 -> 7774 bytes __pycache__/session_manager.cpython-312.pyc | Bin 0 -> 15316 bytes agent_pool.py | 178 ++++++++++ agent_prompt.txt | 10 +- data/all_hp_product_spec_book2506/.DS_Store | Bin 6148 -> 0 bytes fastapi_app.py | 306 ++++++++++++++--- gbase_agent.py | 302 ++++++----------- llm_config.json | 65 ++++ .../json_reader_server.cpython-312.pyc | Bin 0 -> 12667 bytes mcp/__pycache__/mcp_wrapper.cpython-312.pyc | Bin 0 -> 9034 bytes mcp/directory_tree_wrapper_server.py | 61 ++++ mcp/json_reader_server.py | 44 ++- mcp/mcp_settings.json | 7 - mcp/mcp_wrapper.py | 308 ++++++++++++++++++ mcp/ripgrep_wrapper_server.py | 61 ++++ project_config.py | 154 +++++++++ .../all_hp_product_spec_book2506/document.txt | 0 .../all_hp_product_spec_book2506/schema.json | 0 .../serialization.txt | 0 projects/project_registry.json | 18 + 23 files changed, 1259 insertions(+), 255 deletions(-) create mode 100644 __pycache__/agent_pool.cpython-312.pyc create mode 100644 __pycache__/fastapi_app.cpython-312.pyc create mode 100644 __pycache__/project_config.cpython-312.pyc create mode 100644 __pycache__/session_manager.cpython-312.pyc create mode 100644 agent_pool.py delete mode 100644 data/all_hp_product_spec_book2506/.DS_Store create mode 100644 llm_config.json create mode 100644 mcp/__pycache__/json_reader_server.cpython-312.pyc create mode 100644 mcp/__pycache__/mcp_wrapper.cpython-312.pyc create mode 100644 mcp/directory_tree_wrapper_server.py create mode 100644 mcp/mcp_wrapper.py create mode 100644 mcp/ripgrep_wrapper_server.py create mode 100644 project_config.py rename {data => projects/demo-project}/all_hp_product_spec_book2506/document.txt (100%) rename {data => projects/demo-project}/all_hp_product_spec_book2506/schema.json (100%) rename {data => projects/demo-project}/all_hp_product_spec_book2506/serialization.txt (100%) create mode 100644 projects/project_registry.json diff --git a/__pycache__/agent_pool.cpython-312.pyc b/__pycache__/agent_pool.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b674af240bd548e7dc050188cf4fb82f3d1e792c GIT binary patch literal 8267 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 KGqalBvHuI_QB~If literal 0 HcmV?d00001 diff --git a/__pycache__/fastapi_app.cpython-312.pyc b/__pycache__/fastapi_app.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2a1a2521c18b7defb5cf86c281f7b167638938b3 GIT binary patch literal 12537 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 literal 0 HcmV?d00001 diff --git a/__pycache__/gbase_agent.cpython-312.pyc b/__pycache__/gbase_agent.cpython-312.pyc index 42beec491c6331640a45002e683d56117d8ef60e..3b844b93b4d91be123c70422b493da76979e2516 100644 GIT binary patch delta 4671 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 literal 9777 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 diff --git a/__pycache__/project_config.cpython-312.pyc b/__pycache__/project_config.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c24f269ed120c73823fb74cf8f9114e596c1a34e GIT binary patch literal 7774 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 YJ3vuQpA(G^GNhz-#=HLeZtaKt56cZM`Tzg` literal 0 HcmV?d00001 diff --git a/__pycache__/session_manager.cpython-312.pyc b/__pycache__/session_manager.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9a9599d4da9baddd5a53e0c73399cda1f0cda358 GIT binary patch literal 15316 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 literal 0 HcmV?d00001 diff --git a/agent_pool.py b/agent_pool.py new file mode 100644 index 0000000..82b9684 --- /dev/null +++ b/agent_pool.py @@ -0,0 +1,178 @@ +import asyncio +from typing import List, Optional +import logging + +logger = logging.getLogger(__name__) + + +class AgentPool: + """助手实例池管理器""" + + def __init__(self, pool_size: int = 5): + """ + 初始化助手实例池 + + Args: + pool_size: 池中实例的数量,默认5个 + """ + self.pool_size = pool_size + self.pool: asyncio.Queue = asyncio.Queue(maxsize=pool_size) + self.semaphore = asyncio.Semaphore(pool_size) + self.agents = [] # 保存所有创建的实例引用 + + async def initialize(self, agent_factory): + """ + 初始化实例池,使用工厂函数创建助手实例 + + Args: + agent_factory: 创建助手实例的工厂函数 + """ + logger.info(f"正在初始化助手实例池,大小: {self.pool_size}") + + for i in range(self.pool_size): + try: + agent = agent_factory() + await self.pool.put(agent) + self.agents.append(agent) + logger.info(f"助手实例 {i+1}/{self.pool_size} 创建成功") + except Exception as e: + logger.error(f"创建助手实例 {i+1} 失败: {e}") + raise + + logger.info("助手实例池初始化完成") + + async def get_agent(self, timeout: Optional[float] = 30.0): + """ + 获取空闲的助手实例 + + Args: + timeout: 获取超时时间,默认30秒 + + Returns: + 助手实例 + + Raises: + asyncio.TimeoutError: 获取超时 + """ + try: + # 使用信号量控制并发 + await asyncio.wait_for(self.semaphore.acquire(), timeout=timeout) + # 从池中获取实例 + agent = await asyncio.wait_for(self.pool.get(), timeout=timeout) + logger.debug(f"成功获取助手实例,剩余池大小: {self.pool.qsize()}") + return agent + except asyncio.TimeoutError: + logger.error(f"获取助手实例超时 ({timeout}秒)") + raise + + async def release_agent(self, agent): + """ + 释放助手实例回池 + + Args: + agent: 要释放的助手实例 + """ + try: + await self.pool.put(agent) + self.semaphore.release() + logger.debug(f"释放助手实例,当前池大小: {self.pool.qsize()}") + except Exception as e: + logger.error(f"释放助手实例失败: {e}") + # 即使释放失败也要释放信号量 + self.semaphore.release() + + def get_pool_stats(self) -> dict: + """ + 获取池状态统计信息 + + Returns: + 包含池状态信息的字典 + """ + return { + "pool_size": self.pool_size, + "available_agents": self.pool.qsize(), + "total_agents": len(self.agents), + "in_use_agents": len(self.agents) - self.pool.qsize() + } + + async def shutdown(self): + """关闭实例池,清理资源""" + logger.info("正在关闭助手实例池...") + + # 清空队列 + while not self.pool.empty(): + try: + agent = self.pool.get_nowait() + # 如果有清理方法,调用清理 + if hasattr(agent, 'cleanup'): + await agent.cleanup() + except asyncio.QueueEmpty: + break + + logger.info("助手实例池已关闭") + + +# 全局实例池单例 +_global_agent_pool: Optional[AgentPool] = None + + +def get_agent_pool() -> Optional[AgentPool]: + """获取全局助手实例池""" + return _global_agent_pool + + +def set_agent_pool(pool: AgentPool): + """设置全局助手实例池""" + global _global_agent_pool + _global_agent_pool = pool + + +async def init_global_agent_pool(pool_size: int = 5, agent_factory=None): + """ + 初始化全局助手实例池 + + Args: + pool_size: 池大小 + agent_factory: 实例工厂函数 + """ + global _global_agent_pool + + if _global_agent_pool is not None: + logger.warning("全局助手实例池已存在,跳过初始化") + return + + if agent_factory is None: + raise ValueError("必须提供 agent_factory 参数") + + _global_agent_pool = AgentPool(pool_size=pool_size) + await _global_agent_pool.initialize(agent_factory) + logger.info("全局助手实例池初始化完成") + + +async def get_agent_from_pool(timeout: Optional[float] = 30.0): + """ + 从全局池获取助手实例 + + Args: + timeout: 获取超时时间 + + Returns: + 助手实例 + """ + if _global_agent_pool is None: + raise RuntimeError("全局助手实例池未初始化") + + return await _global_agent_pool.get_agent(timeout) + + +async def release_agent_to_pool(agent): + """ + 释放助手实例到全局池 + + Args: + agent: 要释放的助手实例 + """ + if _global_agent_pool is None: + raise RuntimeError("全局助手实例池未初始化") + + await _global_agent_pool.release_agent(agent) \ No newline at end of file diff --git a/agent_prompt.txt b/agent_prompt.txt index 815463b..e7826b3 100644 --- a/agent_prompt.txt +++ b/agent_prompt.txt @@ -17,7 +17,7 @@ ### 数据存储层次 ``` -./data/ +[当前数据目录]/ ├── [数据集文件夹]/ │ ├── schema.json # 倒排索引层 │ ├── serialization.txt # 序列化数据层 @@ -28,7 +28,7 @@ #### 1. 索引层 (schema.json) - **功能**:字段枚举值倒排索引,查询入口点 -- **访问方式**:`json-reader-get_all_keys({"file_path": "./data/[数据集文件夹]/schema.json", "key_path": "schema"})` +- **访问方式**:`json-reader-get_all_keys({"file_path": "[当前数据目录]/[数据集文件夹]/schema.json", "key_path": "schema"})` - **数据结构**: ```json { @@ -66,14 +66,14 @@ **执行步骤**: 1. **加载索引**:读取schema.json获取字段元数据 2. **字段分析**:识别数值字段、文本字段、枚举字段 -3. **字段详情分析**:对于相关字段调用`json-reader-get_value({"file_path": "./data/[数据集文件夹]/schema.json", "key_path": "schema.[字段名]"})`查看具体的枚举值和取值范围 +3. **字段详情分析**:对于相关字段调用`json-reader-get_value({"file_path": "[当前数据目录]/[数据集文件夹]/schema.json", "key_path": "schema.[字段名]"})`查看具体的枚举值和取值范围 4. **策略制定**:基于查询条件选择最优检索路径 5. **范围预估**:评估各条件的数据分布和选择度 ### 阶段2:精准数据匹配 **目标**:从序列化数据中提取符合条件的记录 **执行步骤**: -1. **预检查**:`ripgrep-count-matches({"path": "./data/[数据集文件夹]/serialization.txt", "pattern": "匹配模式"})` +1. **预检查**:`ripgrep-count-matches({"path": "[当前数据目录]/[数据集文件夹]/serialization.txt", "pattern": "匹配模式"})` 2. **智能限流**: - 匹配数 > 1000:增加过滤条件,重新预检查 - 匹配数 100-1000:`ripgrep-search({"maxResults": 30})` @@ -193,3 +193,5 @@ query_pattern = simple_field_match(conditions[0]) # 先匹配主要条件 --- **执行提醒**:始终使用完整的文件路径参数调用工具,确保数据访问的准确性和安全性。在查询执行过程中,动态调整策略以适应不同的数据特征和查询需求。 + +**重要说明**:所有文件路径中的 `[当前数据目录]` 将通过系统消息动态提供,请根据实际的数据目录路径进行操作。 diff --git a/data/all_hp_product_spec_book2506/.DS_Store b/data/all_hp_product_spec_book2506/.DS_Store deleted file mode 100644 index 5008ddfcf53c02e82d7eee2e57c38e5672ef89f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 str: + full_text = '' + content = [] + TOOL_CALL_S = '[TOOL_CALL]' + TOOL_RESULT_S = '[TOOL_RESPONSE]' + THOUGHT_S = '[THINK]' + ANSWER_S = '[ANSWER]' + + for msg in messages: + if msg['role'] == ASSISTANT: + if msg.get('reasoning_content'): + assert isinstance(msg['reasoning_content'], str), 'Now only supports text messages' + content.append(f'{THOUGHT_S}\n{msg["reasoning_content"]}') + if msg.get('content'): + assert isinstance(msg['content'], str), 'Now only supports text messages' + content.append(f'{ANSWER_S}\n{msg["content"]}') + if msg.get('function_call'): + content.append(f'{TOOL_CALL_S} {msg["function_call"]["name"]}\n{msg["function_call"]["arguments"]}') + elif msg['role'] == FUNCTION: + content.append(f'{TOOL_RESULT_S} {msg["name"]}\n{msg["content"]}') + else: + raise TypeError + if content: + full_text = '\n'.join(content) + + return full_text + +from agent_pool import (get_agent_from_pool, init_global_agent_pool, + release_agent_to_pool) +from gbase_agent import init_agent_service_universal, update_agent_llm +from project_config import project_manager app = FastAPI(title="Database Assistant API", version="1.0.0") -# Initialize agent globally at startup -bot = init_agent_service() +# 全局助手实例池,在应用启动时初始化 +agent_pool_size = int(os.getenv("AGENT_POOL_SIZE", "1")) -class QueryRequest(BaseModel): - question: str +class Message(BaseModel): + role: str + content: str + + +class ChatRequest(BaseModel): + messages: List[Message] + model: str = "qwen3-next" + api_key: Optional[str] = None + extra: Optional[Dict] = None + stream: Optional[bool] = False file_url: Optional[str] = None -class QueryResponse(BaseModel): - answer: str +class ChatResponse(BaseModel): + choices: List[Dict] + usage: Optional[Dict] = None -@app.post("/query", response_model=QueryResponse) -async def query_database(request: QueryRequest): - """ - Process a database query using the assistant agent. +class ChatStreamResponse(BaseModel): + choices: List[Dict] + usage: Optional[Dict] = None - Args: - request: QueryRequest containing the query and optional file URL - Returns: - QueryResponse containing the assistant's response - """ +async def generate_stream_response(agent, messages, request) -> AsyncGenerator[str, None]: + """生成流式响应""" + accumulated_content = "" + accumulated_args = "" + chunk_id = 0 try: - messages = [] - - if request.file_url: - messages.append( - { - "role": "user", - "content": [{"text":"使用sqlite数据库,用日语回答下面问题:"+request.question}, {"file": request.file_url}], + for response in agent.run(messages=messages): + previous_content = accumulated_content + accumulated_content = get_content_from_messages(response) + + # 计算新增的内容 + if accumulated_content.startswith(previous_content): + new_content = accumulated_content[len(previous_content):] + else: + new_content = accumulated_content + previous_content = "" + + # 只有当有新内容时才发送chunk + if new_content: + chunk_id += 1 + # 构造OpenAI格式的流式响应 + chunk_data = { + "id": f"chatcmpl-{chunk_id}", + "object": "chat.completion.chunk", + "created": int(__import__('time').time()), + "model": request.model, + "choices": [{ + "index": 0, + "delta": { + "content": new_content + }, + "finish_reason": None + }] } + + yield f"data: {json.dumps(chunk_data, ensure_ascii=False)}\n\n" + + # 发送最终完成标记 + final_chunk = { + "id": f"chatcmpl-{chunk_id + 1}", + "object": "chat.completion.chunk", + "created": int(__import__('time').time()), + "model": request.model, + "choices": [{ + "index": 0, + "delta": {}, + "finish_reason": "stop" + }] + } + yield f"data: {json.dumps(final_chunk, ensure_ascii=False)}\n\n" + + # 发送结束标记 + yield "data: [DONE]\n\n" + + except Exception as e: + import traceback + error_details = traceback.format_exc() + print(f"Error in generate_stream_response: {str(e)}") + print(f"Full traceback: {error_details}") + + error_data = { + "error": { + "message": f"Stream error: {str(e)}", + "type": "internal_error" + } + } + yield f"data: {json.dumps(error_data, ensure_ascii=False)}\n\n" + + +@app.post("/chat/completions") +async def chat_completions(request: ChatRequest): + """ + Chat completions API similar to OpenAI, supports both streaming and non-streaming + + Args: + request: ChatRequest containing messages, model, project_id in extra field, etc. + + Returns: + Union[ChatResponse, StreamingResponse]: Chat completion response or stream + """ + agent = None + try: + # 从extra字段中获取project_id + if not request.extra or 'project_id' not in request.extra: + raise HTTPException(status_code=400, detail="project_id is required in extra field") + + project_id = request.extra['project_id'] + + # 验证项目访问权限 + if not project_manager.validate_project_access(project_id): + raise HTTPException(status_code=404, detail=f"Project {project_id} not found or inactive") + + # 获取项目数据目录 + project_dir = project_manager.get_project_dir(project_id) + + # 从实例池获取助手实例 + agent = await get_agent_from_pool(timeout=30.0) + + # 准备LLM配置,从extra字段中移除project_id + llm_extra = request.extra.copy() if request.extra else {} + llm_extra.pop('project_id', None) # 移除project_id,不传递给LLM + + # 动态设置请求的模型,支持从接口传入api_key和extra参数 + update_agent_llm(agent, request.model, request.api_key, llm_extra) + + # 构建包含项目信息的消息上下文 + messages = [ + # 项目信息系统消息 + { + "role": "user", + "content": f"当前项目ID: {project_id},数据目录: {project_dir}。所有文件路径中的 '[当前数据目录]' 请替换为: {project_dir}" + }, + # 用户消息批量转换 + *[{"role": msg.role, "content": msg.content} for msg in request.messages] + ] + + # 根据stream参数决定返回流式还是非流式响应 + if request.stream: + return StreamingResponse( + generate_stream_response(agent, messages, request), + media_type="text/event-stream", + headers={"Cache-Control": "no-cache", "Connection": "keep-alive"} ) else: - messages.append({"role": "user", "content": request.question}) - - responses = [] - for response in bot.run(messages): - responses.append(response) - - if responses: - final_response = responses[-1][-1] - return QueryResponse(answer=final_response["content"]) - else: - raise HTTPException(status_code=500, detail="No response from agent") + # 非流式响应 + final_responses = agent.run_nonstream(messages) + + if final_responses and len(final_responses) > 0: + # 取最后一个响应 + final_response = final_responses[-1] + + # 如果返回的是Message对象,需要转换为字典 + if hasattr(final_response, 'model_dump'): + final_response = final_response.model_dump() + elif hasattr(final_response, 'dict'): + final_response = final_response.dict() + + content = final_response.get("content", "") + + # 构造OpenAI格式的响应 + return ChatResponse( + choices=[{ + "index": 0, + "message": { + "role": "assistant", + "content": content + }, + "finish_reason": "stop" + }], + usage={ + "prompt_tokens": sum(len(msg.content) for msg in request.messages), + "completion_tokens": len(content), + "total_tokens": sum(len(msg.content) for msg in request.messages) + len(content) + } + ) + else: + raise HTTPException(status_code=500, detail="No response from agent") except Exception as e: + import traceback + error_details = traceback.format_exc() + print(f"Error in chat_completions: {str(e)}") + print(f"Full traceback: {error_details}") raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}") + finally: + # 确保释放助手实例回池 + if agent is not None: + await release_agent_to_pool(agent) + + @app.get("/") @@ -65,6 +250,53 @@ async def root(): return {"message": "Database Assistant API is running"} +@app.get("/system/status") +async def system_status(): + """获取系统状态信息""" + from agent_pool import get_agent_pool + + pool = get_agent_pool() + pool_stats = pool.get_pool_stats() if pool else {"pool_size": 0, "available_agents": 0, "total_agents": 0, "in_use_agents": 0} + + return { + "status": "running", + "storage_type": "Agent Pool API", + "agent_pool": { + "pool_size": pool_stats["pool_size"], + "available_agents": pool_stats["available_agents"], + "total_agents": pool_stats["total_agents"], + "in_use_agents": pool_stats["in_use_agents"] + } + } + + +@app.on_event("startup") +async def startup_event(): + """应用启动时初始化助手实例池""" + print(f"正在启动FastAPI应用,初始化助手实例池(大小: {agent_pool_size})...") + + try: + def agent_factory(): + return init_agent_service_universal() + + await init_global_agent_pool(pool_size=agent_pool_size, agent_factory=agent_factory) + print("助手实例池初始化完成!") + except Exception as e: + print(f"助手实例池初始化失败: {e}") + raise + + +@app.on_event("shutdown") +async def shutdown_event(): + """应用关闭时清理实例池""" + print("正在关闭应用,清理助手实例池...") + + from agent_pool import get_agent_pool + pool = get_agent_pool() + if pool: + await pool.shutdown() + print("助手实例池清理完成!") + + if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=8000) - diff --git a/gbase_agent.py b/gbase_agent.py index a3d7cdd..f682094 100644 --- a/gbase_agent.py +++ b/gbase_agent.py @@ -16,7 +16,6 @@ import argparse import asyncio -import copy import json import os from typing import Dict, List, Optional, Union @@ -30,118 +29,6 @@ from qwen_agent.utils.output_beautify import typewriter_print ROOT_RESOURCE = os.path.join(os.path.dirname(__file__), "resource") -class GPT4OChat(TextChatAtOAI): - """自定义 GPT-4o 聊天类,修复 tool_call_id 问题""" - - def convert_messages_to_dicts(self, messages: List[Message]) -> List[dict]: - # 使用父类方法进行基础转换 - messages = super().convert_messages_to_dicts(messages) - - # 应用修复后的消息转换 - messages = self._fixed_conv_qwen_agent_messages_to_oai(messages) - - return messages - - @staticmethod - def _fixed_conv_qwen_agent_messages_to_oai(messages: List[Union[Message, Dict]]): - """修复后的消息转换方法,确保 tool 消息包含 tool_call_id 字段""" - new_messages = [] - i = 0 - - while i < len(messages): - msg = messages[i] - - if msg['role'] == ASSISTANT: - # 处理 assistant 消息 - assistant_msg = {'role': 'assistant'} - - # 设置 content - content = msg.get('content', '') - if isinstance(content, (list, dict)): - assistant_msg['content'] = json.dumps(content, ensure_ascii=False) - elif content is None: - assistant_msg['content'] = '' - else: - assistant_msg['content'] = content - - # 设置 reasoning_content - if msg.get('reasoning_content'): - assistant_msg['reasoning_content'] = msg['reasoning_content'] - - # 检查是否需要构造 tool_calls - has_tool_call = False - tool_calls = [] - - # 情况1:当前消息有 function_call - if msg.get('function_call'): - has_tool_call = True - tool_calls.append({ - 'id': msg.get('extra', {}).get('function_id', '1'), - 'type': 'function', - 'function': { - 'name': msg['function_call']['name'], - 'arguments': msg['function_call']['arguments'] - } - }) - - # 注意:不再为孤立的 tool 消息构造虚假的 tool_call - - if has_tool_call: - assistant_msg['tool_calls'] = tool_calls - new_messages.append(assistant_msg) - - # 检查后续是否有对应的 tool 消息 - if i + 1 < len(messages) and messages[i + 1]['role'] == 'tool': - tool_msg = copy.deepcopy(messages[i + 1]) - # 确保 tool_call_id 匹配 - tool_msg['tool_call_id'] = tool_calls[0]['id'] - # 移除多余字段 - for field in ['id', 'extra', 'function_call']: - if field in tool_msg: - del tool_msg[field] - # 确保 content 有效且为字符串 - content = tool_msg.get('content', '') - if isinstance(content, (list, dict)): - tool_msg['content'] = json.dumps(content, ensure_ascii=False) - elif content is None: - tool_msg['content'] = '' - new_messages.append(tool_msg) - i += 2 - else: - i += 1 - else: - new_messages.append(assistant_msg) - i += 1 - - elif msg['role'] == 'tool': - # 孤立的 tool 消息,转换为 assistant + user 消息序列 - # 首先添加一个包含工具结果的 assistant 消息 - assistant_result = {'role': 'assistant'} - content = msg.get('content', '') - if isinstance(content, (list, dict)): - content = json.dumps(content, ensure_ascii=False) - assistant_result['content'] = f"工具查询结果: {content}" - new_messages.append(assistant_result) - - # 然后添加一个 user 消息来继续对话 - new_messages.append({'role': 'user', 'content': '请继续分析以上结果'}) - i += 1 - - else: - # 处理其他角色消息 - new_msg = copy.deepcopy(msg) - - # 确保 content 有效且为字符串 - content = new_msg.get('content', '') - if isinstance(content, (list, dict)): - new_msg['content'] = json.dumps(content, ensure_ascii=False) - elif content is None: - new_msg['content'] = '' - - new_messages.append(new_msg) - i += 1 - - return new_messages def read_mcp_settings(): @@ -150,107 +37,70 @@ def read_mcp_settings(): return mcp_settings_json +def read_mcp_settings_with_project_restriction(project_data_dir: str): + """读取MCP配置并添加项目目录限制""" + with open("./mcp/mcp_settings.json", "r") as f: + mcp_settings_json = json.load(f) + + # 为json-reader添加项目目录限制 + for server_config in mcp_settings_json: + if "mcpServers" in server_config: + for server_name, server_info in server_config["mcpServers"].items(): + if server_name == "json-reader": + # 添加环境变量来传递项目目录限制 + if "env" not in server_info: + server_info["env"] = {} + server_info["env"]["PROJECT_DATA_DIR"] = project_data_dir + server_info["env"]["PROJECT_ID"] = project_data_dir.split("/")[-2] if "/" in project_data_dir else "default" + break + + return mcp_settings_json + + def read_system_prompt(): with open("./agent_prompt.txt", "r", encoding="utf-8") as f: return f.read().strip() +def read_system_prompt(): + """读取通用的无状态系统prompt""" + with open("./agent_prompt.txt", "r", encoding="utf-8") as f: + return f.read().strip() + + def init_agent_service(): - llm_cfg = { - "llama-33": { - "model": "gbase-llama-33", - "model_server": "http://llmapi:9009/v1", - "api_key": "any", - }, - "gpt-oss-120b": { - "model": "openai/gpt-oss-120b", - "model_server": "https://openrouter.ai/api/v1", # base_url, also known as api_base - "api_key": "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", - - "generate_cfg": { - "use_raw_api": True, # GPT-OSS true ,Qwen false - "fncall_prompt_type": "nous", # 使用 nous 风格的函数调用提示 - } - }, + """默认初始化函数,保持向后兼容""" + return init_agent_service_universal("qwen3-next") - "claude-3.7": { - "model": "claude-3-7-sonnet-20250219", - "model_server": "https://one.felo.me/v1", - "api_key": "sk-9gtHriq7C3jAvepq5dA0092a5cC24a54Aa83FbC99cB88b21-2", - "generate_cfg": { - "use_raw_api": True, # GPT-OSS true ,Qwen false - }, - }, - "gpt-4o": { - "model": "gpt-4o", - "model_server": "https://one-dev.felo.me/v1", - "api_key": "sk-hsKClH0Z695EkK5fDdB2Ec2fE13f4fC1B627BdBb8e554b5b-4", - "generate_cfg": { - "use_raw_api": True, # 启用 raw_api 但使用自定义类修复 tool_call_id 问题 - "fncall_prompt_type": "nous", # 使用 nous 风格的函数调用提示 - }, - }, - "Gpt-4o-back": { - "model_type": "oai", # 使用 oai 类型以便使用自定义类 - "model": "gpt-4o", - "model_server": "https://one-dev.felo.me/v1", - "api_key": "sk-hsKClH0Z695EkK5fDdB2Ec2fE13f4fC1B627BdBb8e554b5b-4", - "generate_cfg": { - "use_raw_api": True, # 启用 raw_api 但使用自定义类修复 tool_call_id 问题 - "fncall_prompt_type": "nous", # 使用 nous 风格的函数调用提示 - }, - # 使用自定义的 GPT4OChat 类 - "llm_class": GPT4OChat, - }, - "glm-45": { - "model_server": "https://open.bigmodel.cn/api/paas/v4", - "api_key": "0c9cbaca9d2bbf864990f1e1decdf340.dXRMsZCHTUbPQ0rm", - "model": "glm-4.5", - "generate_cfg": { - "use_raw_api": True, # GPT-OSS true ,Qwen false - }, - }, - "qwen3-next": { - "model": "qwen/qwen3-next-80b-a3b-instruct", - "model_server": "https://openrouter.ai/api/v1", # base_url, also known as api_base - "api_key": "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", +def read_llm_config(): + """读取LLM配置文件""" + with open("./llm_config.json", "r") as f: + return json.load(f) - }, - "deepresearch": { - "model": "alibaba/tongyi-deepresearch-30b-a3b", - "model_server": "https://openrouter.ai/api/v1", # base_url, also known as api_base - "api_key": "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", - }, - "qwen3-coder":{ - "model": "Qwen/Qwen3-Coder-30B-A3B-Instruct", - "model_server": "https://api-inference.modelscope.cn/v1", # base_url, also known as api_base - "api_key": "ms-92027446-2787-4fd6-af01-f002459ec556", - }, - "openrouter-gpt4o":{ - "model": "openai/gpt-4o", - "model_server": "https://openrouter.ai/api/v1", # base_url, also known as api_base - "api_key": "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", - "generate_cfg": { - "use_raw_api": True, # GPT-OSS true ,Qwen false - "fncall_prompt_type": "nous", # 使用 nous 风格的函数调用提示 - }, - } - } +def init_agent_service_with_project(project_id: str, project_data_dir: str, model_name: str = "qwen3-next"): + """支持项目目录的agent初始化函数 - 保持向后兼容""" + llm_cfg = read_llm_config() + + # 读取通用的系统prompt(无状态) system = read_system_prompt() - # 暂时禁用 MCP 工具以测试 GPT-4o - tools = read_mcp_settings() - # 使用自定义的 GPT-4o 配置 - llm_instance = llm_cfg["qwen3-next"] + # 读取MCP工具配置 + tools = read_mcp_settings_with_project_restriction(project_data_dir) + + # 使用指定的模型配置 + if model_name not in llm_cfg: + raise ValueError(f"Model '{model_name}' not found in llm_config.json. Available models: {list(llm_cfg.keys())}") + + llm_instance = llm_cfg[model_name] if "llm_class" in llm_instance: llm_instance = llm_instance.get("llm_class", TextChatAtOAI)(llm_instance) bot = Assistant( - llm=llm_instance, # 使用自定义的 GPT-4o 实例 - name="数据库助手", - description="数据库查询", + llm=llm_instance, # 使用指定的模型实例 + name=f"数据库助手-{project_id}", + description=f"项目 {project_id} 数据库查询", system_message=system, function_list=tools, ) @@ -258,6 +108,62 @@ def init_agent_service(): return bot +def init_agent_service_universal(): + """创建无状态的通用助手实例(使用默认LLM,可动态切换)""" + llm_cfg = read_llm_config() + + # 读取通用的系统prompt(无状态) + system = read_system_prompt() + + # 读取基础的MCP工具配置(不包含项目限制) + tools = read_mcp_settings() + + # 使用默认模型创建助手实例 + default_model = "qwen3-next" # 默认模型 + if default_model not in llm_cfg: + # 如果默认模型不存在,使用第一个可用模型 + default_model = list(llm_cfg.keys())[0] + + llm_instance = llm_cfg[default_model] + if "llm_class" in llm_instance: + llm_instance = llm_instance.get("llm_class", TextChatAtOAI)(llm_instance) + + bot = Assistant( + llm=llm_instance, # 使用默认LLM初始化 + name="通用数据检索助手", + description="无状态通用数据检索助手", + system_message=system, + function_list=tools, + ) + + return bot + + +def update_agent_llm(agent, model_name: str, api_key: str = None, extra: Dict = None): + """动态更新助手实例的LLM,支持从接口传入参数""" + + # 获取基础配置 + llm_config = { + "model": model_name, + "api_key": api_key, + } + # 如果接口传入了extra参数,则合并到配置中 + if extra is not None: + llm_config.update(extra) + + # 创建LLM实例,确保不是字典 + if "llm_class" in llm_config: + llm_instance = llm_config.get("llm_class", TextChatAtOAI)(llm_config) + else: + # 使用默认的 TextChatAtOAI 类 + llm_instance = TextChatAtOAI(llm_config) + + # 动态设置LLM + agent.llm = llm_instance + + return agent + + def test(query="数据库里有几张表"): # Define the agent bot = init_agent_service() diff --git a/llm_config.json b/llm_config.json new file mode 100644 index 0000000..c5df2e2 --- /dev/null +++ b/llm_config.json @@ -0,0 +1,65 @@ +{ + "llama-33": { + "model": "gbase-llama-33", + "model_server": "http://llmapi:9009/v1", + "api_key": "any" + }, + "gpt-oss-120b": { + "model": "openai/gpt-oss-120b", + "model_server": "https://openrouter.ai/api/v1", + "api_key": "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", + "generate_cfg": { + "use_raw_api": true, + "fncall_prompt_type": "nous" + } + }, + "claude-3.7": { + "model": "claude-3-7-sonnet-20250219", + "model_server": "https://one.felo.me/v1", + "api_key": "sk-9gtHriq7C3jAvepq5dA0092a5cC24a54Aa83FbC99cB88b21-2", + "generate_cfg": { + "use_raw_api": true + } + }, + "gpt-4o": { + "model": "gpt-4o", + "model_server": "https://one-dev.felo.me/v1", + "api_key": "sk-hsKClH0Z695EkK5fDdB2Ec2fE13f4fC1B627BdBb8e554b5b-4", + "generate_cfg": { + "use_raw_api": true, + "fncall_prompt_type": "nous" + } + }, + "glm-45": { + "model_server": "https://open.bigmodel.cn/api/paas/v4", + "api_key": "0c9cbaca9d2bbf864990f1e1decdf340.dXRMsZCHTUbPQ0rm", + "model": "glm-4.5", + "generate_cfg": { + "use_raw_api": true + } + }, + "qwen3-next": { + "model": "qwen/qwen3-next-80b-a3b-instruct", + "model_server": "https://openrouter.ai/api/v1", + "api_key": "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" + }, + "deepresearch": { + "model": "alibaba/tongyi-deepresearch-30b-a3b", + "model_server": "https://openrouter.ai/api/v1", + "api_key": "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" + }, + "qwen3-coder": { + "model": "Qwen/Qwen3-Coder-30B-A3B-Instruct", + "model_server": "https://api-inference.modelscope.cn/v1", + "api_key": "ms-92027446-2787-4fd6-af01-f002459ec556" + }, + "openrouter-gpt4o": { + "model": "openai/gpt-4o", + "model_server": "https://openrouter.ai/api/v1", + "api_key": "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", + "generate_cfg": { + "use_raw_api": true, + "fncall_prompt_type": "nous" + } + } +} \ No newline at end of file diff --git a/mcp/__pycache__/json_reader_server.cpython-312.pyc b/mcp/__pycache__/json_reader_server.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c13b4ff821cbfa257d9d2a1aa5d7fac9c9701ebc GIT binary patch literal 12667 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 literal 0 HcmV?d00001 diff --git a/mcp/__pycache__/mcp_wrapper.cpython-312.pyc b/mcp/__pycache__/mcp_wrapper.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..081bcd12136efe66a2591d7ec87a5a1a54e21f26 GIT binary patch literal 9034 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 literal 0 HcmV?d00001 diff --git a/mcp/directory_tree_wrapper_server.py b/mcp/directory_tree_wrapper_server.py new file mode 100644 index 0000000..f533988 --- /dev/null +++ b/mcp/directory_tree_wrapper_server.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +""" +目录树 MCP包装器服务器 +提供安全的目录结构查看功能,限制在项目目录内 +""" + +import asyncio +import json +import sys +from mcp_wrapper import handle_wrapped_request + + +async def main(): + """主入口点""" + try: + while True: + # 从stdin读取 + line = await asyncio.get_event_loop().run_in_executor(None, sys.stdin.readline) + if not line: + break + + line = line.strip() + if not line: + continue + + try: + request = json.loads(line) + response = await handle_wrapped_request(request, "directory-tree-wrapper") + + # 写入stdout + sys.stdout.write(json.dumps(response) + "\n") + sys.stdout.flush() + + except json.JSONDecodeError: + error_response = { + "jsonrpc": "2.0", + "error": { + "code": -32700, + "message": "Parse error" + } + } + sys.stdout.write(json.dumps(error_response) + "\n") + sys.stdout.flush() + + except Exception as e: + error_response = { + "jsonrpc": "2.0", + "error": { + "code": -32603, + "message": f"Internal error: {str(e)}" + } + } + sys.stdout.write(json.dumps(error_response) + "\n") + sys.stdout.flush() + + except KeyboardInterrupt: + pass + + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file diff --git a/mcp/json_reader_server.py b/mcp/json_reader_server.py index 94fda8b..d2d07b3 100644 --- a/mcp/json_reader_server.py +++ b/mcp/json_reader_server.py @@ -12,6 +12,32 @@ import sys import asyncio from typing import Any, Dict, List + +def validate_file_path(file_path: str, allowed_dir: str) -> str: + """验证文件路径是否在允许的目录内""" + # 转换为绝对路径 + if not os.path.isabs(file_path): + file_path = os.path.abspath(file_path) + + allowed_dir = os.path.abspath(allowed_dir) + + # 检查路径是否在允许的目录内 + if not file_path.startswith(allowed_dir): + raise ValueError(f"访问被拒绝: 路径 {file_path} 不在允许的目录 {allowed_dir} 内") + + # 检查路径遍历攻击 + if ".." in file_path: + raise ValueError(f"访问被拒绝: 检测到路径遍历攻击尝试") + + return file_path + + +def get_allowed_directory(): + """获取允许访问的目录""" + # 从环境变量读取项目数据目录 + project_dir = os.getenv("PROJECT_DATA_DIR", "./projects") + return os.path.abspath(project_dir) + async def handle_request(request: Dict[str, Any]) -> Dict[str, Any]: """Handle MCP request""" try: @@ -130,9 +156,9 @@ async def handle_request(request: Dict[str, Any]) -> Dict[str, Any]: } try: - # Convert relative path to absolute path - if not os.path.isabs(file_path): - file_path = os.path.abspath(file_path) + # 验证文件路径是否在允许的目录内 + allowed_dir = get_allowed_directory() + file_path = validate_file_path(file_path, allowed_dir) with open(file_path, 'r', encoding='utf-8') as f: data = json.load(f) @@ -222,9 +248,9 @@ async def handle_request(request: Dict[str, Any]) -> Dict[str, Any]: } try: - # Convert relative path to absolute path - if not os.path.isabs(file_path): - file_path = os.path.abspath(file_path) + # 验证文件路径是否在允许的目录内 + allowed_dir = get_allowed_directory() + file_path = validate_file_path(file_path, allowed_dir) with open(file_path, 'r', encoding='utf-8') as f: data = json.load(f) @@ -307,9 +333,9 @@ async def handle_request(request: Dict[str, Any]) -> Dict[str, Any]: } try: - # Convert relative path to absolute path - if not os.path.isabs(file_path): - file_path = os.path.abspath(file_path) + # 验证文件路径是否在允许的目录内 + allowed_dir = get_allowed_directory() + file_path = validate_file_path(file_path, allowed_dir) with open(file_path, 'r', encoding='utf-8') as f: data = json.load(f) diff --git a/mcp/mcp_settings.json b/mcp/mcp_settings.json index ab6ed0f..ccc07f6 100644 --- a/mcp/mcp_settings.json +++ b/mcp/mcp_settings.json @@ -8,13 +8,6 @@ "@andredezzy/deep-directory-tree-mcp" ] }, - "mcp-server-code-runner": { - "command": "npx", - "args": [ - "-y", - "mcp-server-code-runner@latest" - ] - }, "ripgrep": { "command": "npx", "args": [ diff --git a/mcp/mcp_wrapper.py b/mcp/mcp_wrapper.py new file mode 100644 index 0000000..879b641 --- /dev/null +++ b/mcp/mcp_wrapper.py @@ -0,0 +1,308 @@ +#!/usr/bin/env python3 +""" +通用MCP工具包装器 +为所有MCP工具提供目录访问控制和安全限制 +""" + +import os +import sys +import json +import asyncio +import subprocess +from typing import Dict, Any, Optional +from pathlib import Path + + +class MCPSecurityWrapper: + """MCP安全包装器""" + + def __init__(self, allowed_directory: str): + self.allowed_directory = os.path.abspath(allowed_directory) + self.project_id = os.getenv("PROJECT_ID", "default") + + def validate_path(self, path: str) -> str: + """验证路径是否在允许的目录内""" + if not os.path.isabs(path): + path = os.path.abspath(path) + + # 规范化路径 + path = os.path.normpath(path) + + # 检查路径遍历 + if ".." in path.split(os.sep): + raise ValueError(f"路径遍历攻击被阻止: {path}") + + # 检查是否在允许的目录内 + if not path.startswith(self.allowed_directory): + raise ValueError(f"访问被拒绝: {path} 不在允许的目录 {self.allowed_directory} 内") + + return path + + def safe_execute_command(self, command: list, cwd: Optional[str] = None) -> Dict[str, Any]: + """安全执行命令,限制工作目录""" + if cwd is None: + cwd = self.allowed_directory + else: + cwd = self.validate_path(cwd) + + # 设置环境变量限制 + env = os.environ.copy() + env["PWD"] = cwd + env["PROJECT_DATA_DIR"] = self.allowed_directory + env["PROJECT_ID"] = self.project_id + + try: + result = subprocess.run( + command, + cwd=cwd, + env=env, + capture_output=True, + text=True, + timeout=30 # 30秒超时 + ) + + return { + "success": result.returncode == 0, + "stdout": result.stdout, + "stderr": result.stderr, + "returncode": result.returncode + } + except subprocess.TimeoutExpired: + return { + "success": False, + "stdout": "", + "stderr": "命令执行超时", + "returncode": -1 + } + except Exception as e: + return { + "success": False, + "stdout": "", + "stderr": str(e), + "returncode": -1 + } + + +async def handle_wrapped_request(request: Dict[str, Any], tool_name: str) -> Dict[str, Any]: + """处理包装后的MCP请求""" + try: + method = request.get("method") + params = request.get("params", {}) + request_id = request.get("id") + + allowed_dir = get_allowed_directory() + wrapper = MCPSecurityWrapper(allowed_dir) + + if method == "initialize": + return { + "jsonrpc": "2.0", + "id": request_id, + "result": { + "protocolVersion": "2024-11-05", + "capabilities": { + "tools": {} + }, + "serverInfo": { + "name": f"{tool_name}-wrapper", + "version": "1.0.0" + } + } + } + + elif method == "ping": + return { + "jsonrpc": "2.0", + "id": request_id, + "result": { + "pong": True + } + } + + elif method == "tools/list": + return { + "jsonrpc": "2.0", + "id": request_id, + "result": { + "tools": get_tool_definitions(tool_name) + } + } + + elif method == "tools/call": + return await execute_tool_call(wrapper, tool_name, params, request_id) + + else: + return { + "jsonrpc": "2.0", + "id": request_id, + "error": { + "code": -32601, + "message": f"Unknown method: {method}" + } + } + + except Exception as e: + return { + "jsonrpc": "2.0", + "id": request.get("id"), + "error": { + "code": -32603, + "message": f"Internal error: {str(e)}" + } + } + + +def get_allowed_directory(): + """获取允许访问的目录""" + project_dir = os.getenv("PROJECT_DATA_DIR", "./data") + return os.path.abspath(project_dir) + + +def get_tool_definitions(tool_name: str) -> list: + """根据工具名称返回工具定义""" + if tool_name == "ripgrep-wrapper": + return [ + { + "name": "ripgrep_search", + "description": "在项目目录内搜索文本", + "inputSchema": { + "type": "object", + "properties": { + "pattern": {"type": "string", "description": "搜索模式"}, + "path": {"type": "string", "description": "搜索路径(相对于项目目录)"}, + "maxResults": {"type": "integer", "default": 100} + }, + "required": ["pattern"] + } + } + ] + elif tool_name == "directory-tree-wrapper": + return [ + { + "name": "get_directory_tree", + "description": "获取项目目录结构", + "inputSchema": { + "type": "object", + "properties": { + "path": {"type": "string", "description": "目录路径(相对于项目目录)"}, + "max_depth": {"type": "integer", "default": 3} + } + } + } + ] + else: + return [] + + +async def execute_tool_call(wrapper: MCPSecurityWrapper, tool_name: str, params: Dict[str, Any], request_id: Any) -> Dict[str, Any]: + """执行工具调用""" + try: + if tool_name == "ripgrep-wrapper": + return await execute_ripgrep_search(wrapper, params, request_id) + elif tool_name == "directory-tree-wrapper": + return await execute_directory_tree(wrapper, params, request_id) + else: + return { + "jsonrpc": "2.0", + "id": request_id, + "error": { + "code": -32601, + "message": f"Unknown tool: {tool_name}" + } + } + except Exception as e: + return { + "jsonrpc": "2.0", + "id": request_id, + "error": { + "code": -32603, + "message": str(e) + } + } + + +async def execute_ripgrep_search(wrapper: MCPSecurityWrapper, params: Dict[str, Any], request_id: Any) -> Dict[str, Any]: + """执行ripgrep搜索""" + pattern = params.get("pattern", "") + path = params.get("path", ".") + max_results = params.get("maxResults", 100) + + # 验证和构建搜索路径 + search_path = os.path.join(wrapper.allowed_directory, path) + search_path = wrapper.validate_path(search_path) + + # 构建ripgrep命令 + command = [ + "rg", + "--json", + "--max-count", str(max_results), + pattern, + search_path + ] + + result = wrapper.safe_execute_command(command) + + if result["success"]: + return { + "jsonrpc": "2.0", + "id": request_id, + "result": { + "content": [ + { + "type": "text", + "text": result["stdout"] + } + ] + } + } + else: + return { + "jsonrpc": "2.0", + "id": request_id, + "error": { + "code": -32603, + "message": f"搜索失败: {result['stderr']}" + } + } + + +async def execute_directory_tree(wrapper: MCPSecurityWrapper, params: Dict[str, Any], request_id: Any) -> Dict[str, Any]: + """执行目录树获取""" + path = params.get("path", ".") + max_depth = params.get("max_depth", 3) + + # 验证和构建目录路径 + dir_path = os.path.join(wrapper.allowed_directory, path) + dir_path = wrapper.validate_path(dir_path) + + # 构建目录树命令 + command = [ + "find", + dir_path, + "-type", "d", + "-maxdepth", str(max_depth) + ] + + result = wrapper.safe_execute_command(command) + + if result["success"]: + return { + "jsonrpc": "2.0", + "id": request_id, + "result": { + "content": [ + { + "type": "text", + "text": result["stdout"] + } + ] + } + } + else: + return { + "jsonrpc": "2.0", + "id": request_id, + "error": { + "code": -32603, + "message": f"获取目录树失败: {result['stderr']}" + } + } \ No newline at end of file diff --git a/mcp/ripgrep_wrapper_server.py b/mcp/ripgrep_wrapper_server.py new file mode 100644 index 0000000..ce6ac84 --- /dev/null +++ b/mcp/ripgrep_wrapper_server.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +""" +Ripgrep MCP包装器服务器 +提供安全的文本搜索功能,限制在项目目录内 +""" + +import asyncio +import json +import sys +from mcp_wrapper import handle_wrapped_request + + +async def main(): + """主入口点""" + try: + while True: + # 从stdin读取 + line = await asyncio.get_event_loop().run_in_executor(None, sys.stdin.readline) + if not line: + break + + line = line.strip() + if not line: + continue + + try: + request = json.loads(line) + response = await handle_wrapped_request(request, "ripgrep-wrapper") + + # 写入stdout + sys.stdout.write(json.dumps(response) + "\n") + sys.stdout.flush() + + except json.JSONDecodeError: + error_response = { + "jsonrpc": "2.0", + "error": { + "code": -32700, + "message": "Parse error" + } + } + sys.stdout.write(json.dumps(error_response) + "\n") + sys.stdout.flush() + + except Exception as e: + error_response = { + "jsonrpc": "2.0", + "error": { + "code": -32603, + "message": f"Internal error: {str(e)}" + } + } + sys.stdout.write(json.dumps(error_response) + "\n") + sys.stdout.flush() + + except KeyboardInterrupt: + pass + + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file diff --git a/project_config.py b/project_config.py new file mode 100644 index 0000000..0277cd3 --- /dev/null +++ b/project_config.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 +""" +项目配置管理系统 +负责管理项目ID到数据目录的映射,以及项目访问权限控制 +""" + +import json +import os +from typing import Dict, Optional, List +from dataclasses import dataclass, asdict + + +@dataclass +class ProjectConfig: + """项目配置数据类""" + project_id: str + data_dir: str + name: str + description: str = "" + allowed_file_types: List[str] = None + max_file_size_mb: int = 100 + is_active: bool = True + + def __post_init__(self): + if self.allowed_file_types is None: + self.allowed_file_types = [".json", ".txt", ".csv", ".pdf"] + + +class ProjectManager: + """项目管理器""" + + def __init__(self, config_file: str = "./projects/project_registry.json"): + self.config_file = config_file + self.projects: Dict[str, ProjectConfig] = {} + self._ensure_config_dir() + self._load_projects() + + def _ensure_config_dir(self): + """确保配置目录存在""" + config_dir = os.path.dirname(self.config_file) + if not os.path.exists(config_dir): + os.makedirs(config_dir, exist_ok=True) + + def _load_projects(self): + """从配置文件加载项目""" + if os.path.exists(self.config_file): + try: + with open(self.config_file, 'r', encoding='utf-8') as f: + data = json.load(f) + for project_data in data.get('projects', []): + config = ProjectConfig(**project_data) + self.projects[config.project_id] = config + except Exception as e: + print(f"加载项目配置失败: {e}") + self._create_default_config() + else: + self._create_default_config() + + def _create_default_config(self): + """创建默认配置""" + default_project = ProjectConfig( + project_id="default", + data_dir="./data", + name="默认项目", + description="默认数据项目" + ) + self.projects["default"] = default_project + self._save_projects() + + def _save_projects(self): + """保存项目配置到文件""" + data = { + "projects": [asdict(project) for project in self.projects.values()] + } + try: + with open(self.config_file, 'w', encoding='utf-8') as f: + json.dump(data, f, ensure_ascii=False, indent=2) + except Exception as e: + print(f"保存项目配置失败: {e}") + + def get_project(self, project_id: str) -> Optional[ProjectConfig]: + """获取项目配置""" + return self.projects.get(project_id) + + def add_project(self, config: ProjectConfig) -> bool: + """添加项目""" + if config.project_id in self.projects: + return False + + # 确保数据目录存在 + if not os.path.isabs(config.data_dir): + config.data_dir = os.path.abspath(config.data_dir) + + os.makedirs(config.data_dir, exist_ok=True) + + self.projects[config.project_id] = config + self._save_projects() + return True + + def update_project(self, project_id: str, **kwargs) -> bool: + """更新项目配置""" + if project_id not in self.projects: + return False + + project = self.projects[project_id] + for key, value in kwargs.items(): + if hasattr(project, key): + setattr(project, key, value) + + self._save_projects() + return True + + def delete_project(self, project_id: str) -> bool: + """删除项目""" + if project_id not in self.projects: + return False + + del self.projects[project_id] + self._save_projects() + return True + + def list_projects(self) -> List[ProjectConfig]: + """列出所有项目""" + return list(self.projects.values()) + + def get_project_dir(self, project_id: str) -> str: + """获取项目数据目录""" + project = self.get_project(project_id) + if project: + return project.data_dir + + # 如果项目不存在,创建默认目录结构 + default_dir = f"./projects/{project_id}/data" + os.makedirs(default_dir, exist_ok=True) + + # 自动创建新项目配置 + new_project = ProjectConfig( + project_id=project_id, + data_dir=default_dir, + name=f"项目 {project_id}", + description=f"自动创建的项目 {project_id}" + ) + self.add_project(new_project) + + return default_dir + + def validate_project_access(self, project_id: str) -> bool: + """验证项目访问权限""" + project = self.get_project(project_id) + return project and project.is_active + + +# 全局项目管理器实例 +project_manager = ProjectManager() \ No newline at end of file diff --git a/data/all_hp_product_spec_book2506/document.txt b/projects/demo-project/all_hp_product_spec_book2506/document.txt similarity index 100% rename from data/all_hp_product_spec_book2506/document.txt rename to projects/demo-project/all_hp_product_spec_book2506/document.txt diff --git a/data/all_hp_product_spec_book2506/schema.json b/projects/demo-project/all_hp_product_spec_book2506/schema.json similarity index 100% rename from data/all_hp_product_spec_book2506/schema.json rename to projects/demo-project/all_hp_product_spec_book2506/schema.json diff --git a/data/all_hp_product_spec_book2506/serialization.txt b/projects/demo-project/all_hp_product_spec_book2506/serialization.txt similarity index 100% rename from data/all_hp_product_spec_book2506/serialization.txt rename to projects/demo-project/all_hp_product_spec_book2506/serialization.txt diff --git a/projects/project_registry.json b/projects/project_registry.json new file mode 100644 index 0000000..6d8d20d --- /dev/null +++ b/projects/project_registry.json @@ -0,0 +1,18 @@ +{ + "projects": [ + { + "project_id": "demo-project", + "data_dir": "/Users/moshui/Documents/felo/qwen-agent/projects/demo-project/", + "name": "演示项目", + "description": "演示多项目隔离功能", + "allowed_file_types": [ + ".json", + ".txt", + ".csv", + ".pdf" + ], + "max_file_size_mb": 100, + "is_active": true + } + ] +} \ No newline at end of file