xgae 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of xgae might be problematic. Click here for more details.
- xgae-0.1.1/.env +21 -0
- xgae-0.1.1/.idea/.gitignore +8 -0
- xgae-0.1.1/.idea/inspectionProfiles/Project_Default.xml +15 -0
- xgae-0.1.1/.idea/inspectionProfiles/profiles_settings.xml +6 -0
- xgae-0.1.1/.idea/misc.xml +7 -0
- xgae-0.1.1/.idea/modules.xml +8 -0
- xgae-0.1.1/.idea/vcs.xml +4 -0
- xgae-0.1.1/.idea/workspace.xml +128 -0
- xgae-0.1.1/.idea/xgae.iml +11 -0
- xgae-0.1.1/.python-version +1 -0
- xgae-0.1.1/PKG-INFO +11 -0
- xgae-0.1.1/README.md +0 -0
- xgae-0.1.1/mcpservers/custom_servers.json +8 -0
- xgae-0.1.1/mcpservers/xga_server.json +8 -0
- xgae-0.1.1/pyproject.toml +23 -0
- xgae-0.1.1/src/xgae/__init__.py +0 -0
- xgae-0.1.1/src/xgae/engine/responser/xga_non_stream_responser.py +0 -0
- xgae-0.1.1/src/xgae/engine/responser/xga_responser_utils.py +0 -0
- xgae-0.1.1/src/xgae/engine/responser/xga_stream_reponser.py +0 -0
- xgae-0.1.1/src/xgae/engine/xga_base.py +46 -0
- xgae-0.1.1/src/xgae/engine/xga_engine.py +69 -0
- xgae-0.1.1/src/xgae/engine/xga_mcp_tool_box.py +192 -0
- xgae-0.1.1/src/xgae/engine/xga_prompt_builder.py +38 -0
- xgae-0.1.1/src/xgae/utils/llm_client.py +239 -0
- xgae-0.1.1/src/xgae/utils/setup_env.py +108 -0
- xgae-0.1.1/uv.lock +1495 -0
xgae-0.1.1/.env
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# LOG
|
|
2
|
+
LOG_LEVEL=INFO
|
|
3
|
+
LOG_FILE=log/xgae.log
|
|
4
|
+
|
|
5
|
+
# LANGFUSE
|
|
6
|
+
LANGFUSE_PUBLIC_KEY=
|
|
7
|
+
LANGFUSE_SECRET_KEY=
|
|
8
|
+
LANGFUSE_HOST=https://cloud.langfuse.com
|
|
9
|
+
|
|
10
|
+
# LLM
|
|
11
|
+
LLM_MODEL=openai/qwen3-235b-a22b
|
|
12
|
+
LLM_API_BASE=https://dashscope.aliyuncs.com/compatible-mode/v1
|
|
13
|
+
LLM_API_KEY=
|
|
14
|
+
LLM_MAX_TOKENS=16384
|
|
15
|
+
LLM_TEMPERATURE=0.7
|
|
16
|
+
LLM_MAX_RETRIES=2
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<component name="InspectionProjectProfileManager">
|
|
2
|
+
<profile version="1.0">
|
|
3
|
+
<option name="myName" value="Project Default" />
|
|
4
|
+
<inspection_tool class="PyCompatibilityInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
|
5
|
+
<option name="ourVersions">
|
|
6
|
+
<value>
|
|
7
|
+
<list size="2">
|
|
8
|
+
<item index="0" class="java.lang.String" itemvalue="3.8" />
|
|
9
|
+
<item index="1" class="java.lang.String" itemvalue="3.11" />
|
|
10
|
+
</list>
|
|
11
|
+
</value>
|
|
12
|
+
</option>
|
|
13
|
+
</inspection_tool>
|
|
14
|
+
</profile>
|
|
15
|
+
</component>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<project version="4">
|
|
3
|
+
<component name="Black">
|
|
4
|
+
<option name="sdkName" value="Python 3.13 (xgae)" />
|
|
5
|
+
</component>
|
|
6
|
+
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13 virtualenv at ~/DevelopSpace/xgae/.venv" project-jdk-type="Python SDK" />
|
|
7
|
+
</project>
|
xgae-0.1.1/.idea/vcs.xml
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<project version="4">
|
|
3
|
+
<component name="AutoImportSettings">
|
|
4
|
+
<option name="autoReloadType" value="SELECTIVE" />
|
|
5
|
+
</component>
|
|
6
|
+
<component name="ChangeListManager">
|
|
7
|
+
<list default="true" id="0f1352ac-e106-4041-9704-6775ec0c7096" name="Changes" comment="" />
|
|
8
|
+
<option name="SHOW_DIALOG" value="false" />
|
|
9
|
+
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
|
10
|
+
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
|
11
|
+
<option name="LAST_RESOLUTION" value="IGNORE" />
|
|
12
|
+
</component>
|
|
13
|
+
<component name="FileTemplateManagerImpl">
|
|
14
|
+
<option name="RECENT_TEMPLATES">
|
|
15
|
+
<list>
|
|
16
|
+
<option value="Python Script" />
|
|
17
|
+
</list>
|
|
18
|
+
</option>
|
|
19
|
+
</component>
|
|
20
|
+
<component name="ProjectColorInfo">{
|
|
21
|
+
"associatedIndex": 7
|
|
22
|
+
}</component>
|
|
23
|
+
<component name="ProjectId" id="31InXSONOzq9VTdmbkoD0F0ho9q" />
|
|
24
|
+
<component name="ProjectViewState">
|
|
25
|
+
<option name="hideEmptyMiddlePackages" value="true" />
|
|
26
|
+
<option name="showLibraryContents" value="true" />
|
|
27
|
+
</component>
|
|
28
|
+
<component name="PropertiesComponent">{
|
|
29
|
+
"keyToString": {
|
|
30
|
+
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
|
31
|
+
"Python.llm_client.executor": "Run",
|
|
32
|
+
"Python.utils.executor": "Run",
|
|
33
|
+
"Python.xga_mcp_tool_box.executor": "Run",
|
|
34
|
+
"RunOnceActivity.ShowReadmeOnStart": "true",
|
|
35
|
+
"last_opened_file_path": "/Users/goosezzy/DevelopSpace/xgae",
|
|
36
|
+
"node.js.detected.package.eslint": "true",
|
|
37
|
+
"node.js.detected.package.tslint": "true",
|
|
38
|
+
"node.js.selected.package.eslint": "(autodetect)",
|
|
39
|
+
"node.js.selected.package.tslint": "(autodetect)",
|
|
40
|
+
"nodejs_package_manager_path": "npm",
|
|
41
|
+
"settings.editor.selected.configurable": "preferences.general",
|
|
42
|
+
"vue.rearranger.settings.migration": "true"
|
|
43
|
+
}
|
|
44
|
+
}</component>
|
|
45
|
+
<component name="RecentsManager">
|
|
46
|
+
<key name="MoveFile.RECENT_KEYS">
|
|
47
|
+
<recent name="$PROJECT_DIR$/src/xgae/engine/responser" />
|
|
48
|
+
</key>
|
|
49
|
+
</component>
|
|
50
|
+
<component name="RunManager">
|
|
51
|
+
<configuration default="true" type="PythonConfigurationType" factoryName="Python">
|
|
52
|
+
<module name="xgae" />
|
|
53
|
+
<option name="ENV_FILES" value="" />
|
|
54
|
+
<option name="INTERPRETER_OPTIONS" value="" />
|
|
55
|
+
<option name="PARENT_ENVS" value="true" />
|
|
56
|
+
<envs>
|
|
57
|
+
<env name="PYTHONUNBUFFERED" value="1" />
|
|
58
|
+
</envs>
|
|
59
|
+
<option name="SDK_HOME" value="" />
|
|
60
|
+
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
|
61
|
+
<option name="IS_MODULE_SDK" value="false" />
|
|
62
|
+
<option name="ADD_CONTENT_ROOTS" value="true" />
|
|
63
|
+
<option name="ADD_SOURCE_ROOTS" value="true" />
|
|
64
|
+
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
|
|
65
|
+
<EXTENSION ID="net.ashald.envfile">
|
|
66
|
+
<option name="IS_ENABLED" value="false" />
|
|
67
|
+
<option name="IS_SUBST" value="false" />
|
|
68
|
+
<option name="IS_PATH_MACRO_SUPPORTED" value="false" />
|
|
69
|
+
<option name="IS_IGNORE_MISSING_FILES" value="false" />
|
|
70
|
+
<option name="IS_ENABLE_EXPERIMENTAL_INTEGRATIONS" value="false" />
|
|
71
|
+
<ENTRIES>
|
|
72
|
+
<ENTRY IS_ENABLED="true" PARSER="runconfig" IS_EXECUTABLE="false" />
|
|
73
|
+
</ENTRIES>
|
|
74
|
+
</EXTENSION>
|
|
75
|
+
<option name="SCRIPT_NAME" value="" />
|
|
76
|
+
<option name="PARAMETERS" value="" />
|
|
77
|
+
<option name="SHOW_COMMAND_LINE" value="false" />
|
|
78
|
+
<option name="EMULATE_TERMINAL" value="false" />
|
|
79
|
+
<option name="MODULE_MODE" value="false" />
|
|
80
|
+
<option name="REDIRECT_INPUT" value="false" />
|
|
81
|
+
<option name="INPUT_FILE" value="" />
|
|
82
|
+
<method v="2" />
|
|
83
|
+
</configuration>
|
|
84
|
+
</component>
|
|
85
|
+
<component name="SharedIndexes">
|
|
86
|
+
<attachedChunks>
|
|
87
|
+
<set>
|
|
88
|
+
<option value="bundled-js-predefined-d6986cc7102b-09060db00ec0-JavaScript-PY-251.26927.90" />
|
|
89
|
+
<option value="bundled-python-sdk-41e8cd69c857-64d779b69b7a-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-251.26927.90" />
|
|
90
|
+
</set>
|
|
91
|
+
</attachedChunks>
|
|
92
|
+
</component>
|
|
93
|
+
<component name="TaskManager">
|
|
94
|
+
<task active="true" id="Default" summary="Default task">
|
|
95
|
+
<changelist id="0f1352ac-e106-4041-9704-6775ec0c7096" name="Changes" comment="" />
|
|
96
|
+
<created>1755220495138</created>
|
|
97
|
+
<option name="number" value="Default" />
|
|
98
|
+
<option name="presentableId" value="Default" />
|
|
99
|
+
<updated>1755220495138</updated>
|
|
100
|
+
<workItem from="1755220497295" duration="14755000" />
|
|
101
|
+
<workItem from="1755239458855" duration="4586000" />
|
|
102
|
+
<workItem from="1755244406907" duration="6284000" />
|
|
103
|
+
<workItem from="1755262332599" duration="306000" />
|
|
104
|
+
<workItem from="1755270285722" duration="12068000" />
|
|
105
|
+
<workItem from="1755283728206" duration="1511000" />
|
|
106
|
+
<workItem from="1755286552901" duration="3130000" />
|
|
107
|
+
<workItem from="1755307767865" duration="337000" />
|
|
108
|
+
<workItem from="1755308113420" duration="2825000" />
|
|
109
|
+
<workItem from="1755321931380" duration="8531000" />
|
|
110
|
+
<workItem from="1755330830188" duration="229000" />
|
|
111
|
+
<workItem from="1755396985976" duration="6435000" />
|
|
112
|
+
<workItem from="1755415614018" duration="461000" />
|
|
113
|
+
<workItem from="1755416476555" duration="256000" />
|
|
114
|
+
<workItem from="1755419142521" duration="150000" />
|
|
115
|
+
<workItem from="1755429851003" duration="290000" />
|
|
116
|
+
<workItem from="1755430331676" duration="21000" />
|
|
117
|
+
</task>
|
|
118
|
+
<servers />
|
|
119
|
+
</component>
|
|
120
|
+
<component name="TypeScriptGeneratedFilesManager">
|
|
121
|
+
<option name="version" value="3" />
|
|
122
|
+
</component>
|
|
123
|
+
<component name="com.intellij.coverage.CoverageDataManagerImpl">
|
|
124
|
+
<SUITE FILE_PATH="coverage/xgae$xga_mcp_tool_box.coverage" NAME="xga_mcp_tool_box Coverage Results" MODIFIED="1755430037796" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
|
|
125
|
+
<SUITE FILE_PATH="coverage/xgae$llm_client.coverage" NAME="llm_client Coverage Results" MODIFIED="1755247632274" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
|
|
126
|
+
<SUITE FILE_PATH="coverage/xgae$utils.coverage" NAME="utils Coverage Results" MODIFIED="1755226923439" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
|
|
127
|
+
</component>
|
|
128
|
+
</project>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<module type="PYTHON_MODULE" version="4">
|
|
3
|
+
<component name="NewModuleRootManager">
|
|
4
|
+
<content url="file://$MODULE_DIR$">
|
|
5
|
+
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
|
6
|
+
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
|
7
|
+
</content>
|
|
8
|
+
<orderEntry type="jdk" jdkName="Python 3.13 virtualenv at ~/DevelopSpace/xgae/.venv" jdkType="Python SDK" />
|
|
9
|
+
<orderEntry type="sourceFolder" forTests="false" />
|
|
10
|
+
</component>
|
|
11
|
+
</module>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.13
|
xgae-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: xgae
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Extreme General Agent Engine
|
|
5
|
+
Requires-Python: >=3.13
|
|
6
|
+
Requires-Dist: colorlog>=6.9.0
|
|
7
|
+
Requires-Dist: langchain-mcp-adapters>=0.1.4
|
|
8
|
+
Requires-Dist: langfuse>=2.60.5
|
|
9
|
+
Requires-Dist: langgraph>=0.3.21
|
|
10
|
+
Requires-Dist: litellm>=1.74.8
|
|
11
|
+
Requires-Dist: mcp>=1.12.1
|
xgae-0.1.1/README.md
ADDED
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "xgae"
|
|
3
|
+
version = "0.1.1"
|
|
4
|
+
description = "Extreme General Agent Engine"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.13"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"colorlog>=6.9.0",
|
|
9
|
+
"langchain-mcp-adapters>=0.1.4",
|
|
10
|
+
"langgraph>=0.3.21",
|
|
11
|
+
"litellm>=1.74.8",
|
|
12
|
+
"mcp>=1.12.1",
|
|
13
|
+
"langfuse>=2.60.5",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[build-system]
|
|
17
|
+
requires = ["hatchling"]
|
|
18
|
+
build-backend = "hatchling.build"
|
|
19
|
+
|
|
20
|
+
[tool.hatch.build]
|
|
21
|
+
exclude = ["log/*"]
|
|
22
|
+
|
|
23
|
+
[project.scripts]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from typing import Union, Optional, Dict, List, Any, Literal
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class XGAMessage:
|
|
9
|
+
message_id: str
|
|
10
|
+
type: Literal["status", "tool", "assistant", "assistant_response_end"]
|
|
11
|
+
is_llm_message: bool
|
|
12
|
+
content: Union[Dict[str, Any], List[Any], str]
|
|
13
|
+
metadata: Optional[Dict[str, Any]]
|
|
14
|
+
session_id: Optional[str]
|
|
15
|
+
agent_id: Optional[str]
|
|
16
|
+
task_id: Optional[str]
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class XGAToolSchema:
|
|
20
|
+
tool_name: str
|
|
21
|
+
server_name: str
|
|
22
|
+
description: str
|
|
23
|
+
input_schema: Optional[str]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class XGAToolResult:
|
|
28
|
+
success: bool
|
|
29
|
+
output: str
|
|
30
|
+
|
|
31
|
+
class XGAToolBox(ABC):
|
|
32
|
+
@abstractmethod
|
|
33
|
+
async def creat_task_tool_box(self, task_id: str, general_tools: List[str], custom_tools: List[str]):
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
@abstractmethod
|
|
37
|
+
async def destroy_task_tool_box(self, task_id: str):
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
@abstractmethod
|
|
41
|
+
def get_task_tool_schemas(self, task_id: str, type: Literal["general_tool", "custom_tool"]) -> List[XGAToolSchema]:
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
@abstractmethod
|
|
45
|
+
async def call_tool(self, task_id: str, tool_name: str, args: Optional[Dict[str, Any]] = None) -> XGAToolResult:
|
|
46
|
+
pass
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
|
|
2
|
+
from typing import List, Any, Dict, Optional, AsyncGenerator
|
|
3
|
+
from uuid import uuid4
|
|
4
|
+
|
|
5
|
+
from xgae.engine.xga_base import XGAMessage, XGAToolSchema, XGAToolBox
|
|
6
|
+
from xgae.utils.llm_client import LLMClient
|
|
7
|
+
from xgae.utils.setup_env import langfuse
|
|
8
|
+
from xga_prompt_builder import XGAPromptBuilder
|
|
9
|
+
from xga_mcp_tool_box import XGAMcpToolBox
|
|
10
|
+
|
|
11
|
+
class XGAEngine():
|
|
12
|
+
def __init__(self,
|
|
13
|
+
session_id: Optional[str] = None,
|
|
14
|
+
trace_id: Optional[str] = None,
|
|
15
|
+
agent_id: Optional[str] = None,
|
|
16
|
+
llm_config: Optional[Dict[str, Any]] = None,
|
|
17
|
+
prompt_builder: Optional[XGAPromptBuilder] = None,
|
|
18
|
+
tool_box: Optional[XGAToolBox] = None):
|
|
19
|
+
self.session_id = session_id if session_id else f"xga_sid_{uuid4()}"
|
|
20
|
+
self.agent_id = agent_id
|
|
21
|
+
|
|
22
|
+
self.messages: List[XGAMessage] = []
|
|
23
|
+
self.llm_client = LLMClient(llm_config)
|
|
24
|
+
self.model_name = self.llm_client.model_name
|
|
25
|
+
self.is_stream = self.llm_client.is_stream
|
|
26
|
+
|
|
27
|
+
self.prompt_builder = prompt_builder or XGAPromptBuilder()
|
|
28
|
+
self.tool_box = tool_box or XGAMcpToolBox()
|
|
29
|
+
|
|
30
|
+
self.task_id = None
|
|
31
|
+
self.trace_id = trace_id if trace_id else langfuse.create_trace_id()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
async def run_task(self,
|
|
35
|
+
task_messages: List[Dict[str, Any]],
|
|
36
|
+
task_id: Optional[str],
|
|
37
|
+
prompt_template: Optional[str] = None,
|
|
38
|
+
general_tools: Optional[List[str]] = ["*"],
|
|
39
|
+
custom_tools: Optional[List[str]] = []) -> AsyncGenerator:
|
|
40
|
+
try:
|
|
41
|
+
self.task_id = task_id if task_id else f"xga_task_{uuid4()}"
|
|
42
|
+
await self.tool_box.creat_task_tool_box(self.task_id, general_tools, custom_tools)
|
|
43
|
+
system_prompt = await self._build_system_prompt(prompt_template, general_tools, custom_tools)
|
|
44
|
+
yield system_prompt
|
|
45
|
+
|
|
46
|
+
finally:
|
|
47
|
+
await self.tool_box.destroy_task_tool_box(self.task_id)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _run_task_once(self):
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
async def _build_system_prompt(self, prompt_template: str, general_tools: List[str], custom_tools: List[str]) -> str:
|
|
54
|
+
self.task_tool_schemas: Dict[str, XGAToolSchema] = {}
|
|
55
|
+
system_prompt = self.prompt_builder.build_system_prompt(self.model_name, prompt_template)
|
|
56
|
+
|
|
57
|
+
tool_schemas = await self.tool_box.get_task_tool_schemas(self.task_id, "general_tool")
|
|
58
|
+
system_prompt = self.prompt_builder.build_general_tool_prompt(self.model_name, system_prompt, tool_schemas)
|
|
59
|
+
|
|
60
|
+
tool_schemas = await self.tool_box.get_task_tool_schemas(self.task_id, "general_tool")
|
|
61
|
+
system_prompt = self.prompt_builder.build_custom_tool_prompt(self.model_name, system_prompt, tool_schemas)
|
|
62
|
+
|
|
63
|
+
return system_prompt
|
|
64
|
+
|
|
65
|
+
def add_message(self, message: XGAMessage):
|
|
66
|
+
message.message_id = f"xga_msg_{uuid4()}"
|
|
67
|
+
message.session_id = self.session_id
|
|
68
|
+
message.agent_id = self.agent_id
|
|
69
|
+
self.messages.append(message)
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
from typing import List, Any, Dict, Optional, Literal, override
|
|
5
|
+
|
|
6
|
+
from langchain_mcp_adapters.client import MultiServerMCPClient
|
|
7
|
+
from langchain_mcp_adapters.tools import load_mcp_tools
|
|
8
|
+
|
|
9
|
+
from xgae.engine.xga_base import XGAToolSchema, XGAToolBox, XGAToolResult
|
|
10
|
+
from xgae.utils.setup_env import XGAError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class XGAMcpToolBox(XGAToolBox):
|
|
14
|
+
GENERAL_MCP_SERVER_NAME = "xga_general"
|
|
15
|
+
|
|
16
|
+
def __init__(self,
|
|
17
|
+
custom_mcp_server_config: Optional[Dict[str, Any]] = None,
|
|
18
|
+
custom_mcp_server_file: Optional[str] = None):
|
|
19
|
+
general_mcp_server_config = self._load_mcp_servers_config("mcpservers/xga_server.json")
|
|
20
|
+
tool_box_mcp_server_config = general_mcp_server_config.get("mcpServers", {})
|
|
21
|
+
|
|
22
|
+
if custom_mcp_server_config:
|
|
23
|
+
tool_box_mcp_server_config.update(custom_mcp_server_config)
|
|
24
|
+
elif custom_mcp_server_file:
|
|
25
|
+
custom_mcp_server_config = self._load_mcp_servers_config(custom_mcp_server_file)
|
|
26
|
+
custom_mcp_server_config = custom_mcp_server_config.get("mcpServers", {})
|
|
27
|
+
tool_box_mcp_server_config.update(custom_mcp_server_config)
|
|
28
|
+
|
|
29
|
+
self._mcp_client = MultiServerMCPClient(tool_box_mcp_server_config)
|
|
30
|
+
self.mcp_server_names: List[str] = [server_name for server_name in tool_box_mcp_server_config]
|
|
31
|
+
self.mcp_tool_schemas: Dict[str, List[XGAToolSchema]] = {}
|
|
32
|
+
self.task_tool_schemas: Dict[str, Dict[str,XGAToolSchema]] = {}
|
|
33
|
+
|
|
34
|
+
@override
|
|
35
|
+
async def creat_task_tool_box(self, task_id: str, general_tools: List[str], custom_tools: List[str]):
|
|
36
|
+
task_tool_schemas = {}
|
|
37
|
+
general_tool_schemas = self.mcp_tool_schemas.get(XGAMcpToolBox.GENERAL_MCP_SERVER_NAME, {})
|
|
38
|
+
if len(general_tools) > 0 and general_tools[0] == "*":
|
|
39
|
+
task_tool_schemas = {tool_schema.tool_name: tool_schema for tool_schema in general_tool_schemas}
|
|
40
|
+
else:
|
|
41
|
+
for tool_schema in general_tool_schemas:
|
|
42
|
+
if tool_schema.tool_name in general_tools:
|
|
43
|
+
task_tool_schemas[tool_schema.tool_name] = tool_schema
|
|
44
|
+
task_tool_schemas.pop("end_task")
|
|
45
|
+
|
|
46
|
+
for server_tool_name in custom_tools:
|
|
47
|
+
parts = server_tool_name.split(".")
|
|
48
|
+
if len(parts) != 2:
|
|
49
|
+
continue
|
|
50
|
+
custom_server_name, custom_tool_name = parts
|
|
51
|
+
if (not custom_server_name ) or (not custom_tool_name):
|
|
52
|
+
continue
|
|
53
|
+
|
|
54
|
+
custom_tool_schemas = self.mcp_tool_schemas.get(custom_server_name, None)
|
|
55
|
+
if custom_tool_schemas is None:
|
|
56
|
+
continue
|
|
57
|
+
if custom_tool_name == "*":
|
|
58
|
+
custom_tool_schema_d = {tool_schema.tool_name: tool_schema for tool_schema in custom_tool_schemas}
|
|
59
|
+
task_tool_schemas.update(custom_tool_schema_d)
|
|
60
|
+
else:
|
|
61
|
+
for tool_schema in custom_tool_schemas:
|
|
62
|
+
if custom_tool_name == tool_schema.tool_name:
|
|
63
|
+
task_tool_schemas[custom_tool_name] = tool_schema
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
self.task_tool_schemas[task_id] = task_tool_schemas
|
|
67
|
+
|
|
68
|
+
@override
|
|
69
|
+
async def destroy_task_tool_box(self, task_id: str):
|
|
70
|
+
tool_schemas = self.get_task_tool_schemas(task_id, type="general_tool")
|
|
71
|
+
if len(tool_schemas) > 0:
|
|
72
|
+
await self.call_tool(task_id, "end_task", {"task_id": task_id})
|
|
73
|
+
self.task_tool_schemas.pop(task_id, None)
|
|
74
|
+
|
|
75
|
+
@override
|
|
76
|
+
def get_task_tool_schemas(self, task_id: str, type: Literal["general_tool", "custom_tool"]) -> List[XGAToolSchema]:
|
|
77
|
+
task_tool_schemas = []
|
|
78
|
+
|
|
79
|
+
all_task_tool_schemas = self.task_tool_schemas.get(task_id, {})
|
|
80
|
+
for tool_schema in all_task_tool_schemas.values():
|
|
81
|
+
if type == "general_tool" and tool_schema.server_name == self.GENERAL_MCP_SERVER_NAME:
|
|
82
|
+
task_tool_schemas.append(tool_schema)
|
|
83
|
+
elif type == "custom_tool" and tool_schema.server_name != self.GENERAL_MCP_SERVER_NAME:
|
|
84
|
+
task_tool_schemas.append(tool_schema)
|
|
85
|
+
|
|
86
|
+
return task_tool_schemas
|
|
87
|
+
|
|
88
|
+
@override
|
|
89
|
+
async def call_tool(self, task_id: str, tool_name: str, args: Optional[Dict[str, Any]] = None) -> XGAToolResult:
|
|
90
|
+
if tool_name == "end_task":
|
|
91
|
+
server_name = self.GENERAL_MCP_SERVER_NAME
|
|
92
|
+
else:
|
|
93
|
+
task_tool_schemas = self.task_tool_schemas.get(task_id, {})
|
|
94
|
+
tool_schema = task_tool_schemas.get(tool_name, None)
|
|
95
|
+
if tool_schema is None:
|
|
96
|
+
raise XGAError(f"MCP tool not found: '{tool_name}'")
|
|
97
|
+
server_name = tool_schema.server_name
|
|
98
|
+
|
|
99
|
+
async with self._mcp_client.session(server_name) as session:
|
|
100
|
+
tools = await load_mcp_tools(session)
|
|
101
|
+
mcp_tool = next((t for t in tools if t.name == tool_name), None)
|
|
102
|
+
|
|
103
|
+
if mcp_tool:
|
|
104
|
+
tool_args = args or {}
|
|
105
|
+
if server_name == self.GENERAL_MCP_SERVER_NAME:
|
|
106
|
+
pass
|
|
107
|
+
#tool_args["task_id"] = task_id #xga general tool, first param must be task_id
|
|
108
|
+
else:
|
|
109
|
+
tool_args = args
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
tool_result = await mcp_tool.arun(tool_args)
|
|
113
|
+
result = XGAToolResult(success=True, output=str(tool_result))
|
|
114
|
+
except Exception as e:
|
|
115
|
+
error = f"Call mcp tool '{tool_name}' error: {str(e)}"
|
|
116
|
+
logging.error(f"XGAMcpToolBox.call_tool: {error}")
|
|
117
|
+
result = XGAToolResult(success=False, output=error)
|
|
118
|
+
else:
|
|
119
|
+
error = f"No MCP tool found with name: {tool_name}"
|
|
120
|
+
logging.info(f"XGAMcpToolBox.call_tool: error={error}")
|
|
121
|
+
result = XGAToolResult(success=False, output=error)
|
|
122
|
+
|
|
123
|
+
return result
|
|
124
|
+
|
|
125
|
+
async def load_mcp_tools_schema(self)-> None:
|
|
126
|
+
for server_name in self.mcp_server_names:
|
|
127
|
+
self.mcp_tool_schemas[server_name] = []
|
|
128
|
+
mcp_tools = await self._mcp_client.get_tools(server_name=server_name)
|
|
129
|
+
for tool in mcp_tools:
|
|
130
|
+
input_schema = tool.args_schema
|
|
131
|
+
if server_name == self.GENERAL_MCP_SERVER_NAME:
|
|
132
|
+
input_schema = str(tool.args_schema) # @todo remove task_id param
|
|
133
|
+
input_schema_str = str(input_schema) # @todo convert input tool.args_schema
|
|
134
|
+
tool_schema = XGAToolSchema(tool_name=tool.name,
|
|
135
|
+
server_name=server_name,
|
|
136
|
+
description=tool.description,
|
|
137
|
+
input_schema=input_schema_str)
|
|
138
|
+
self.mcp_tool_schemas[server_name].append(tool_schema)
|
|
139
|
+
|
|
140
|
+
@staticmethod
|
|
141
|
+
def _load_mcp_servers_config(mcp_config_path: str) -> Dict[str, Any]:
|
|
142
|
+
try:
|
|
143
|
+
if os.path.exists(mcp_config_path):
|
|
144
|
+
with open(mcp_config_path, 'r', encoding='utf-8') as f:
|
|
145
|
+
server_config = json.load(f)
|
|
146
|
+
|
|
147
|
+
for server_name, server_info in server_config["mcpServers"].items():
|
|
148
|
+
if "transport" not in server_info:
|
|
149
|
+
if "url" in server_info:
|
|
150
|
+
server_info["transport"] = "streamable_http" if "mcp" in server_info["url"] else "sse"
|
|
151
|
+
else:
|
|
152
|
+
server_info["transport"] = "stdio"
|
|
153
|
+
|
|
154
|
+
return server_config
|
|
155
|
+
else:
|
|
156
|
+
logging.warning("MCP servers config file not found at: %s", mcp_config_path)
|
|
157
|
+
return {"mcpServers": {}}
|
|
158
|
+
|
|
159
|
+
except Exception as e:
|
|
160
|
+
logging.error("Failed to load MCP servers config: %s", str(e))
|
|
161
|
+
return {"mcpServers": {}}
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
if __name__ == "__main__":
|
|
165
|
+
import asyncio
|
|
166
|
+
from dataclasses import asdict
|
|
167
|
+
async def main():
|
|
168
|
+
task_id = "task1"
|
|
169
|
+
mcp_tool_box = XGAMcpToolBox(custom_mcp_server_file="mcpservers/custom_servers.json")
|
|
170
|
+
#mcp_tool_box = XGAMcpToolBox()
|
|
171
|
+
await mcp_tool_box.load_mcp_tools_schema()
|
|
172
|
+
await mcp_tool_box.creat_task_tool_box(task_id=task_id, general_tools=["*"], custom_tools=["bomc_fault.*"])
|
|
173
|
+
tool_schemas = mcp_tool_box.get_task_tool_schemas(task_id, "general_tool")
|
|
174
|
+
print("general_tools_schemas" + "*"*50)
|
|
175
|
+
for tool_schema in tool_schemas:
|
|
176
|
+
print(asdict(tool_schema))
|
|
177
|
+
print()
|
|
178
|
+
|
|
179
|
+
tool_schemas = mcp_tool_box.get_task_tool_schemas(task_id, "custom_tool")
|
|
180
|
+
print("custom_tools_schemas" + "*" * 50)
|
|
181
|
+
for tool_schema in tool_schemas:
|
|
182
|
+
print(asdict(tool_schema))
|
|
183
|
+
print()
|
|
184
|
+
|
|
185
|
+
result = await mcp_tool_box.call_tool(task_id=task_id, tool_name="web_search", args={"task_id": task_id, "query": "查询天津天气"})
|
|
186
|
+
print(f"call web_search result: {result}")
|
|
187
|
+
|
|
188
|
+
result = await mcp_tool_box.call_tool(task_id=task_id, tool_name="complete", args={"task_id": task_id})
|
|
189
|
+
print(f"call complete result: {result}")
|
|
190
|
+
|
|
191
|
+
await mcp_tool_box.destroy_task_tool_box(task_id)
|
|
192
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
|
|
3
|
+
from typing import Optional, List
|
|
4
|
+
|
|
5
|
+
from xga_base import XGAToolSchema
|
|
6
|
+
from xgae.utils.setup_env import read_file, XGAError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class XGAPromptBuilder():
|
|
10
|
+
def __init__(self,
|
|
11
|
+
prompt_template: Optional[str] = None,
|
|
12
|
+
prompt_template_file: Optional[str] = None):
|
|
13
|
+
self.system_prompt_template = None
|
|
14
|
+
if prompt_template:
|
|
15
|
+
self.system_prompt_template = prompt_template
|
|
16
|
+
elif prompt_template_file:
|
|
17
|
+
self.system_prompt_template = read_file(prompt_template_file)
|
|
18
|
+
else:
|
|
19
|
+
_system_prompt_template = read_file("templates/system_prompt_template.txt")
|
|
20
|
+
self.system_prompt_template = _system_prompt_template.format(
|
|
21
|
+
current_date=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d'),
|
|
22
|
+
current_time=datetime.datetime.now(datetime.timezone.utc).strftime('%H:%M:%S'),
|
|
23
|
+
current_year=datetime.datetime.now(datetime.timezone.utc).strftime('%Y')
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def build_system_prompt(self, model_name:str, prompt_template: Optional[str]=None)-> str:
|
|
28
|
+
system_prompt = prompt_template if prompt_template else self.system_prompt_template
|
|
29
|
+
|
|
30
|
+
return system_prompt
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def build_general_tool_prompt(self, model_name:str, prompt_template: str, tool_schemas:List[XGAToolSchema])-> str:
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def build_custom_tool_prompt(self, model_name:str, prompt_template: str, tool_schemas:List[XGAToolSchema])-> str:
|
|
38
|
+
pass
|