From 621e31941e6d14b53c7572e584c700043a03ca91 Mon Sep 17 00:00:00 2001
From: Dame-time-0034 <812793632@qq.com>
Date: Mon, 8 Jun 2026 16:19:41 +0800
Subject: [PATCH 1/9] =?UTF-8?q?=E6=9B=B4=E6=96=B0=EF=BC=9A=E5=88=A0?=
=?UTF-8?q?=E9=99=A4=20FastMCP=E8=AE=BE=E7=BD=AE=E6=8C=87=E5=8D=97=20-=20?=
=?UTF-8?q?=E5=AE=8C=E6=95=B4=E9=85=8D=E7=BD=AE=E5=92=8C=E4=BD=BF=E7=94=A8?=
=?UTF-8?q?=E6=95=99=E7=A8=8B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
llm/README.md | 1 -
1 file changed, 1 deletion(-)
diff --git a/llm/README.md b/llm/README.md
index d3cf436..71b0eb9 100644
--- a/llm/README.md
+++ b/llm/README.md
@@ -246,7 +246,6 @@ async def get_trending_repositories(language: Optional[str] = None,
## 📚 详细文档
-- 📖 **[FastMCP设置指南](FASTMCP_SETUP.md)** - 完整配置和使用教程
- 🤝 **[贡献指南](CONTRIBUTING.md)** - 参与项目开发
## 🔑 API密钥获取
From 9cd33a4730a9d24cf61f77e208ede3364c3fa30b Mon Sep 17 00:00:00 2001
From: Dame-time-0034 <812793632@qq.com>
Date: Wed, 17 Jun 2026 21:36:20 +0800
Subject: [PATCH 2/9] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E8=87=AA?=
=?UTF-8?q?=E7=84=B6=E8=AF=AD=E8=A8=80=E7=94=9F=E6=88=90SUMO=E8=B7=AF?=
=?UTF-8?q?=E7=BD=91=E5=92=8C=E8=BD=A6=E6=B5=81=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
llm/main_ai.py | 170 +++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 149 insertions(+), 21 deletions(-)
diff --git a/llm/main_ai.py b/llm/main_ai.py
index 051a559..6e021fd 100644
--- a/llm/main_ai.py
+++ b/llm/main_ai.py
@@ -17,13 +17,14 @@
from typing import Optional
import carla
import subprocess
+import os
# 添加src目录到Python路径
current_dir = Path(__file__).parent
-sys.path.insert(0, str(current_dir))
+sys.path.insert(0, str(current_dir/ "src"))
from fastmcp import FastMCP
from src.github_client import GitHubClient
-from src.config import config
+from src.config import config
from src.utils.logger import app_logger
# 创建FastMCP实例
@@ -1792,7 +1793,25 @@ def __init__(self):
}
}
},
- ]
+
+ { # ← 新添加的工具
+ "type": "function",
+ "function": {
+ "name": "generate_sumo_network",
+ "description": "生成 SUMO 路网和车流...",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "grid_x": {"type": "integer", "description": "X方向网格数,默认3"},
+ "grid_y": {"type": "integer", "description": "Y方向网格数,默认3"},
+ "duration": {"type": "integer", "description": "仿真时长(秒),默认200"},
+ "rate": {"type": "number", "description": "发车间隔(秒/辆),默认2.0"}
+ },
+ "required": []
+ }
+ }
+ }
+ ]
def process_markdown(self, text):
"""在Python端处理Markdown格式"""
@@ -1949,6 +1968,17 @@ async def execute_fastmcp_tool_call(self, tool_call):
"success": True,
"data": result
}
+ elif function_name == "generate_sumo_network":
+ result = await generate_sumo_network(
+ grid_x=arguments.get("grid_x", 3),
+ grid_y=arguments.get("grid_y", 3),
+ duration=arguments.get("duration", 200),
+ rate=arguments.get("rate", 2.0)
+ )
+ return {
+ "success": True,
+ "data": result
+ }
elif function_name == "search_github_repositories":
result = await search_github_repositories_impl(
query=arguments["query"],
@@ -2144,15 +2174,24 @@ async def execute_fastmcp_tool_call(self, tool_call):
"跑步者": "jogger",
"跑步的人": "jogger"
}
-
def _check_spawn_intent(self, message):
- """检测用户是否有生成车辆或行人的意图,但缺少必要参数
- 只有当缺少类型时才询问,数量默认为1,不询问
- """
+ """检测用户是否有生成车辆或行人的意图,但缺少必要参数"""
message = message.lower()
- # 首先排除视角控制相关的指令(这些不是生成请求)
+ # 首先排除 SUMO 路网生成相关的指令
+ sumo_keywords = ['路网', '网格', 'sumo', '仿真', '交通']
+ for kw in sumo_keywords:
+ if kw in message:
+ return {
+ 'needs_vehicle_type': False,
+ 'needs_vehicle_count': False,
+ 'needs_pedestrian_type': False,
+ 'needs_pedestrian_count': False,
+ 'is_ambiguous': False
+ }
+
+ # 排除视角控制相关的指令
view_keywords = ['视角', '切换', '人称', '俯视', '鸟瞰', '自由视角', '录制', '录像', '视频']
if any(kw in message for kw in view_keywords):
return {
@@ -2165,31 +2204,22 @@ def _check_spawn_intent(self, message):
# 车辆相关关键词
vehicle_keywords = ['车', '车辆', '汽车', '生成车', '创建车', '来车', '加车', '添加车辆']
- # 行人相关关键词(更精确,避免误判)
pedestrian_keywords = ['行人', '生成行人', '创建行人', '添加行人', '路人']
- # 单独的"人"字需要结合生成类动词才认为是生成行人
person_spawn_verbs = ['生成', '创建', '来', '加', '添加', '放', 'spawn']
- # 数量相关模式
import re
has_count = bool(re.search(r'\d+\s*[辆个]', message))
- # 检测车辆类型(支持英文和中文)
has_vehicle_type = any(vtype in message for vtype in self.VEHICLE_TYPES.keys()) or \
any(vname in message for vname in self.VEHICLE_TYPE_MAP.keys())
- # 检测行人类型(支持英文和中文)- 但排除泛指的"行人"
specific_pedestrian_keywords = set(self.PEDESTRIAN_TYPES.keys()) | set(self.PEDESTRIAN_TYPE_MAP.keys()) - {"行人", "人"}
has_pedestrian_type = any(ptype in message for ptype in specific_pedestrian_keywords)
- # 检查是否是模糊的车辆生成请求
is_vehicle_request = any(kw in message for kw in vehicle_keywords)
- # 检查是否是模糊的行人生成请求
is_pedestrian_request = any(kw in message for kw in pedestrian_keywords)
- # 单独的"人"需要配合生成动词才算
if not is_pedestrian_request and '人' in message:
has_spawn_verb = any(verb in message for verb in person_spawn_verbs)
- # 排除"人称"(第一人称、第三人称等)
if has_spawn_verb and '人称' not in message:
is_pedestrian_request = True
@@ -2201,16 +2231,14 @@ def _check_spawn_intent(self, message):
'is_ambiguous': False
}
- # 如果是车辆请求但没有指定类型,需要询问
if is_vehicle_request and not has_vehicle_type:
result['needs_vehicle_type'] = True
- result['needs_vehicle_count'] = not has_count # 记录是否也缺少数量
+ result['needs_vehicle_count'] = not has_count
result['is_ambiguous'] = True
- # 如果是行人请求但没有指定类型,需要询问
if is_pedestrian_request and not has_pedestrian_type:
result['needs_pedestrian_type'] = True
- result['needs_pedestrian_count'] = not has_count # 记录是否也缺少数量
+ result['needs_pedestrian_count'] = not has_count
result['is_ambiguous'] = True
return result
@@ -3237,5 +3265,105 @@ def main():
print(f"[INFO] 访问地址: http://{host_ip}:3000")
uvicorn.run(app='main_ai:app', host=host_ip, port=3000, reload=True)
+@mcp.tool()
+async def generate_sumo_network(
+ grid_x: int = 3,
+ grid_y: int = 3,
+ duration: int = 200,
+ rate: float = 2.0
+) -> str:
+ """
+ 生成 SUMO 路网和车流。
+ 参数:
+ - grid_x: X方向网格数,默认3
+ - grid_y: Y方向网格数,默认3
+ - duration: 仿真时长(秒),默认200
+ - rate: 发车间隔(秒/辆),默认2.0
+
+ 示例:
+ - "生成3x3网格路网,跑200秒,每2秒发一辆车"
+ - "生成4x4网格,跑300秒"
+ """
+ sumo_home = os.environ.get("SUMO_HOME")
+ if not sumo_home:
+ return "❌ 错误:未设置 SUMO_HOME 环境变量。请在终端中设置:`$env:SUMO_HOME = 'D:\\mcp\\sumo\\sumo_install\\sumo-win64-1.27.0\\sumo-1.27.0'`"
+
+ bin_dir = os.path.join(sumo_home, "bin")
+ tools_dir = os.path.join(sumo_home, "tools")
+
+ prefix = "web_generated"
+ net_file = f"{prefix}.net.xml"
+ trips_file = f"{prefix}.trips.xml"
+ rou_file = f"{prefix}.rou.xml"
+ cfg_file = f"{prefix}.sumocfg"
+
+ try:
+ # 1. 生成路网
+ subprocess.run([
+ os.path.join(bin_dir, "netgenerate"),
+ "--grid",
+ f"--grid-x-number={grid_x}",
+ f"--grid-y-number={grid_y}",
+ f"--grid-x-length=500",
+ f"--grid-y-length=500",
+ f"--output-file={net_file}"
+ ], check=True, capture_output=True, text=True)
+
+ # 2. 生成出行
+ subprocess.run([
+ "python",
+ os.path.join(tools_dir, "randomTrips.py"),
+ f"-n={net_file}",
+ f"-e={duration}",
+ "-l",
+ f"-p={rate}",
+ f"-o={trips_file}"
+ ], check=True, capture_output=True, text=True)
+
+ # 3. 生成路由
+ subprocess.run([
+ os.path.join(bin_dir, "duarouter"),
+ f"-n={net_file}",
+ f"-t={trips_file}",
+ f"-o={rou_file}",
+ "--ignore-errors"
+ ], check=True, capture_output=True, text=True)
+
+ # 4. 创建配置文件
+ with open(cfg_file, "w", encoding="utf-8") as f:
+ f.write(f'''
+