Skip to content

Commit 3aa274c

Browse files
committed
add mofa mcp
1 parent 5afea7b commit 3aa274c

File tree

22 files changed

+735
-0
lines changed

22 files changed

+735
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# OpenRA Copilot Agent for MoFA
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# OpenRA Copilot Agent Configuration
2+
# This file can be used for agent-specific configurations
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
4+
"""OpenRA Copilot Agent - MoFA 单节点版本 with AI Tool Calling"""
5+
6+
import json
7+
import os
8+
import sys
9+
from typing import Any, Dict, List
10+
from dotenv import load_dotenv
11+
from openai import OpenAI
12+
13+
# 加载环境变量
14+
load_dotenv()
15+
16+
# 添加 OpenRA 路径
17+
sys.path.append(os.getenv('OPENRA_PATH', '/Users/liyao/Code/mofa/OpenCodeAlert/Copilot/openra_ai'))
18+
19+
from mofa.agent_build.base.base_agent import MofaAgent, run_agent
20+
from .openra_tools import OpenRATools
21+
22+
23+
class OpenRACopilotAgent:
24+
"""OpenRA Copilot Agent - 真正的 MCP 风格 AI 工具调用"""
25+
26+
def __init__(self):
27+
self.tools = OpenRATools()
28+
self.client = OpenAI(
29+
api_key=os.getenv('OPENAI_API_KEY'),
30+
base_url=os.getenv('OPENAI_BASE_URL', 'https://api.openai.com/v1')
31+
)
32+
self.model = os.getenv('OPENAI_MODEL', 'gpt-4o-mini')
33+
34+
def get_available_tools(self) -> List[Dict[str, Any]]:
35+
"""返回可用工具的 OpenAI Function Calling 格式定义"""
36+
return [
37+
{
38+
"type": "function",
39+
"function": {
40+
"name": "get_game_state",
41+
"description": "获取游戏当前状态,包括资源、电力和可见单位",
42+
"parameters": {"type": "object", "properties": {}}
43+
}
44+
},
45+
{
46+
"type": "function",
47+
"function": {
48+
"name": "produce",
49+
"description": "生产指定类型和数量的单位",
50+
"parameters": {
51+
"type": "object",
52+
"properties": {
53+
"unit_type": {"type": "string", "description": "单位类型,如 '步兵', '电厂', '重坦', '矿车' 等"},
54+
"quantity": {"type": "integer", "description": "生产数量", "minimum": 1}
55+
},
56+
"required": ["unit_type", "quantity"]
57+
}
58+
}
59+
},
60+
{
61+
"type": "function",
62+
"function": {
63+
"name": "move_units",
64+
"description": "移动一批单位到指定坐标",
65+
"parameters": {
66+
"type": "object",
67+
"properties": {
68+
"actor_ids": {"type": "array", "items": {"type": "integer"}, "description": "单位ID列表"},
69+
"x": {"type": "integer", "description": "目标X坐标"},
70+
"y": {"type": "integer", "description": "目标Y坐标"},
71+
"attack_move": {"type": "boolean", "description": "是否攻击移动", "default": False}
72+
},
73+
"required": ["actor_ids", "x", "y"]
74+
}
75+
}
76+
},
77+
{
78+
"type": "function",
79+
"function": {
80+
"name": "query_actor",
81+
"description": "查询单位列表",
82+
"parameters": {
83+
"type": "object",
84+
"properties": {
85+
"type": {"type": "array", "items": {"type": "string"}, "description": "单位类型过滤,空数组表示所有类型"},
86+
"faction": {"type": "string", "description": "阵营: '己方', '敌方', '任意'", "default": "己方"},
87+
"range": {"type": "string", "description": "范围: 'screen', 'all'", "default": "all"},
88+
"restrain": {"type": "array", "items": {"type": "object"}, "description": "约束条件", "default": []}
89+
},
90+
"required": []
91+
}
92+
}
93+
},
94+
{
95+
"type": "function",
96+
"function": {
97+
"name": "attack_target",
98+
"description": "命令单位攻击目标",
99+
"parameters": {
100+
"type": "object",
101+
"properties": {
102+
"attacker_id": {"type": "integer", "description": "攻击者单位ID"},
103+
"target_id": {"type": "integer", "description": "目标单位ID"}
104+
},
105+
"required": ["attacker_id", "target_id"]
106+
}
107+
}
108+
},
109+
{
110+
"type": "function",
111+
"function": {
112+
"name": "player_base_info_query",
113+
"description": "查询玩家基地的资源、电力等基础信息",
114+
"parameters": {"type": "object", "properties": {}}
115+
}
116+
},
117+
{
118+
"type": "function",
119+
"function": {
120+
"name": "can_produce",
121+
"description": "检查是否可以生产某种单位",
122+
"parameters": {
123+
"type": "object",
124+
"properties": {
125+
"unit_type": {"type": "string", "description": "单位类型"}
126+
},
127+
"required": ["unit_type"]
128+
}
129+
}
130+
},
131+
{
132+
"type": "function",
133+
"function": {
134+
"name": "ensure_can_produce_unit",
135+
"description": "确保能生产指定单位(会自动补齐依赖建筑并等待完成)",
136+
"parameters": {
137+
"type": "object",
138+
"properties": {
139+
"unit_name": {"type": "string", "description": "单位名称"}
140+
},
141+
"required": ["unit_name"]
142+
}
143+
}
144+
}
145+
]
146+
147+
def call_tool(self, tool_name: str, arguments: Dict[str, Any]) -> Any:
148+
"""调用具体的工具函数"""
149+
try:
150+
tool_method = getattr(self.tools, tool_name)
151+
if arguments:
152+
return tool_method(**arguments)
153+
else:
154+
return tool_method()
155+
except Exception as e:
156+
return f"工具调用失败: {str(e)}"
157+
158+
def process_command_with_ai(self, user_input: str) -> str:
159+
"""使用 AI 解析用户指令并调用相应工具"""
160+
try:
161+
# 构建对话消息
162+
messages = [
163+
{
164+
"role": "system",
165+
"content": """你是 OpenRA 游戏的 AI 助手。用户会用自然语言描述他们想要执行的游戏操作,你需要:
166+
167+
1. 理解用户意图
168+
2. 调用相应的工具函数来执行操作
169+
3. 返回操作结果
170+
171+
可用的主要操作:
172+
- 生产单位:步兵、电厂、重坦、矿车、兵营等
173+
- 查询状态:游戏状态、单位信息、资源信息
174+
- 单位控制:移动、攻击等
175+
176+
请根据用户指令调用合适的工具。"""
177+
},
178+
{"role": "user", "content": user_input}
179+
]
180+
181+
# 调用 OpenAI API
182+
response = self.client.chat.completions.create(
183+
model=self.model,
184+
messages=messages,
185+
tools=self.get_available_tools(),
186+
tool_choice="auto"
187+
)
188+
189+
message = response.choices[0].message
190+
191+
# 如果 AI 选择了工具调用
192+
if message.tool_calls:
193+
results = []
194+
195+
for tool_call in message.tool_calls:
196+
tool_name = tool_call.function.name
197+
arguments = json.loads(tool_call.function.arguments)
198+
199+
print(f"🤖 AI 调用工具: {tool_name} 参数: {arguments}")
200+
201+
# 调用工具
202+
result = self.call_tool(tool_name, arguments)
203+
results.append({
204+
"tool": tool_name,
205+
"arguments": arguments,
206+
"result": result
207+
})
208+
209+
# 让 AI 总结结果
210+
summary_messages = messages + [
211+
message,
212+
{
213+
"role": "user",
214+
"content": f"工具执行结果:{json.dumps(results, ensure_ascii=False)}。请用简洁的中文总结执行情况。"
215+
}
216+
]
217+
218+
summary_response = self.client.chat.completions.create(
219+
model=self.model,
220+
messages=summary_messages
221+
)
222+
223+
return summary_response.choices[0].message.content
224+
225+
else:
226+
# AI 没有选择工具调用,直接返回回答
227+
return message.content
228+
229+
except Exception as e:
230+
return f"❌ AI 处理失败: {str(e)}"
231+
232+
233+
@run_agent
234+
def run(agent: MofaAgent):
235+
"""Agent 主运行函数"""
236+
copilot = OpenRACopilotAgent()
237+
238+
# 接收用户命令
239+
user_input = agent.receive_parameter('user_command')
240+
241+
print(f"🎮 收到用户指令: {user_input}")
242+
243+
# 使用 AI 处理命令
244+
result = copilot.process_command_with_ai(user_input)
245+
246+
print(f"📤 AI 处理结果: {result}")
247+
248+
# 发送输出
249+
agent.send_output(agent_output_name='copilot_result', agent_result=result)
250+
251+
252+
def main():
253+
"""主函数"""
254+
agent = MofaAgent(agent_name='openra-copilot-agent')
255+
run(agent=agent)
256+
257+
258+
if __name__ == "__main__":
259+
main()

0 commit comments

Comments
 (0)