xgae 0.1.6__tar.gz → 0.1.7__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.

Files changed (42) hide show
  1. {xgae-0.1.6 → xgae-0.1.7}/.env +2 -0
  2. {xgae-0.1.6 → xgae-0.1.7}/.idea/misc.xml +1 -1
  3. {xgae-0.1.6 → xgae-0.1.7}/.idea/workspace.xml +64 -58
  4. {xgae-0.1.6 → xgae-0.1.7}/.idea/xgae.iml +1 -1
  5. {xgae-0.1.6 → xgae-0.1.7}/PKG-INFO +1 -1
  6. xgae-0.1.7/mcpservers/xga_server.json +11 -0
  7. {xgae-0.1.6 → xgae-0.1.7}/pyproject.toml +2 -1
  8. xgae-0.1.7/src/examples/run_human_in_loop.py +29 -0
  9. xgae-0.1.7/src/examples/run_simple.py +12 -0
  10. xgae-0.1.7/src/examples/run_user_prompt.py +30 -0
  11. {xgae-0.1.6 → xgae-0.1.7}/src/xgae/engine/mcp_tool_box.py +6 -0
  12. {xgae-0.1.6 → xgae-0.1.7}/src/xgae/engine/prompt_builder.py +1 -1
  13. {xgae-0.1.6 → xgae-0.1.7}/src/xgae/engine/responser/non_stream_responser.py +2 -4
  14. {xgae-0.1.6 → xgae-0.1.7}/src/xgae/engine/responser/responser_base.py +42 -52
  15. {xgae-0.1.6 → xgae-0.1.7}/src/xgae/engine/task_engine.py +110 -103
  16. xgae-0.1.7/src/xgae/tools/without_general_tools_app.py +48 -0
  17. {xgae-0.1.6 → xgae-0.1.7}/src/xgae/utils/llm_client.py +8 -3
  18. {xgae-0.1.6 → xgae-0.1.7}/src/xgae/utils/setup_env.py +2 -0
  19. xgae-0.1.7/templates/example_user_prompt.txt +22 -0
  20. {xgae-0.1.6 → xgae-0.1.7}/uv.lock +1 -1
  21. xgae-0.1.6/templates/scp_test_prompt.txt +0 -21
  22. {xgae-0.1.6 → xgae-0.1.7}/.idea/.gitignore +0 -0
  23. {xgae-0.1.6 → xgae-0.1.7}/.idea/inspectionProfiles/Project_Default.xml +0 -0
  24. {xgae-0.1.6 → xgae-0.1.7}/.idea/inspectionProfiles/profiles_settings.xml +0 -0
  25. {xgae-0.1.6 → xgae-0.1.7}/.idea/modules.xml +0 -0
  26. {xgae-0.1.6 → xgae-0.1.7}/.idea/vcs.xml +0 -0
  27. {xgae-0.1.6 → xgae-0.1.7}/.python-version +0 -0
  28. {xgae-0.1.6 → xgae-0.1.7}/README.md +0 -0
  29. {xgae-0.1.6 → xgae-0.1.7}/mcpservers/custom_servers.json +0 -0
  30. /xgae-0.1.6/mcpservers/xga_server.json → /xgae-0.1.7/mcpservers/xga_server_sse.json +0 -0
  31. {xgae-0.1.6 → xgae-0.1.7}/src/xgae/__init__.py +0 -0
  32. {xgae-0.1.6 → xgae-0.1.7}/src/xgae/engine/engine_base.py +0 -0
  33. {xgae-0.1.6 → xgae-0.1.7}/src/xgae/engine/responser/stream_responser.py +0 -0
  34. {xgae-0.1.6 → xgae-0.1.7}/src/xgae/utils/__init__.py +0 -0
  35. {xgae-0.1.6 → xgae-0.1.7}/src/xgae/utils/json_helpers.py +0 -0
  36. {xgae-0.1.6 → xgae-0.1.7}/src/xgae/utils/misc.py +0 -0
  37. {xgae-0.1.6 → xgae-0.1.7}/src/xgae/utils/xml_tool_parser.py +0 -0
  38. {xgae-0.1.6 → xgae-0.1.7}/templates/custom_tool_prompt_template.txt +0 -0
  39. {xgae-0.1.6 → xgae-0.1.7}/templates/gemini_system_prompt_template.txt +0 -0
  40. {xgae-0.1.6 → xgae-0.1.7}/templates/general_tool_prompt_template.txt +0 -0
  41. {xgae-0.1.6 → xgae-0.1.7}/templates/system_prompt_response_sample.txt +0 -0
  42. {xgae-0.1.6 → xgae-0.1.7}/templates/system_prompt_template.txt +0 -0
@@ -15,6 +15,8 @@ LLM_MAX_TOKENS=16384
15
15
  LLM_TEMPERATURE=0.7
16
16
  LLM_MAX_RETRIES=2
17
17
 
18
+ # TASK
19
+ MAX_AUTO_RUN = 15
18
20
 
19
21
 
20
22
 
@@ -3,5 +3,5 @@
3
3
  <component name="Black">
4
4
  <option name="sdkName" value="Python 3.13 (xgae)" />
5
5
  </component>
6
- <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13 virtualenv at ~/DevelopSpace/xgae/.venv" project-jdk-type="Python SDK" />
6
+ <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13 virtualenv at ~/DevProjects/xga/xgae/.venv" project-jdk-type="Python SDK" />
7
7
  </project>
@@ -25,26 +25,34 @@
25
25
  <option name="hideEmptyMiddlePackages" value="true" />
26
26
  <option name="showLibraryContents" value="true" />
27
27
  </component>
28
- <component name="PropertiesComponent">{
29
- &quot;keyToString&quot;: {
30
- &quot;ModuleVcsDetector.initialDetectionPerformed&quot;: &quot;true&quot;,
31
- &quot;Python.llm_client.executor&quot;: &quot;Run&quot;,
32
- &quot;Python.run_xga_engine.executor&quot;: &quot;Run&quot;,
33
- &quot;Python.setup_env.executor&quot;: &quot;Run&quot;,
34
- &quot;Python.utils.executor&quot;: &quot;Run&quot;,
35
- &quot;Python.xga_engine.executor&quot;: &quot;Run&quot;,
36
- &quot;Python.xga_mcp_tool_box.executor&quot;: &quot;Run&quot;,
37
- &quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
38
- &quot;last_opened_file_path&quot;: &quot;/Users/goosezzy/DevelopSpace/xgae&quot;,
39
- &quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
40
- &quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
41
- &quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
42
- &quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
43
- &quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
44
- &quot;settings.editor.selected.configurable&quot;: &quot;preferences.pluginManager&quot;,
45
- &quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
28
+ <component name="PropertiesComponent"><![CDATA[{
29
+ "keyToString": {
30
+ "ModuleVcsDetector.initialDetectionPerformed": "true",
31
+ "Python.llm_client.executor": "Run",
32
+ "Python.message_tools_app.executor": "Run",
33
+ "Python.responser_base.executor": "Run",
34
+ "Python.run_engine_with_human_in_loop.executor": "Run",
35
+ "Python.run_simple.executor": "Run",
36
+ "Python.run_task_engine.executor": "Run",
37
+ "Python.run_user_prompt.executor": "Run",
38
+ "Python.run_xga_engine.executor": "Run",
39
+ "Python.setup_env.executor": "Run",
40
+ "Python.task_engine.executor": "Run",
41
+ "Python.utils.executor": "Run",
42
+ "Python.xga_engine.executor": "Run",
43
+ "Python.xga_mcp_tool_box.executor": "Debug",
44
+ "Python.xga_prompt_builder.executor": "Debug",
45
+ "RunOnceActivity.ShowReadmeOnStart": "true",
46
+ "last_opened_file_path": "/Users/sharkystar/DevProjects/xga/xgae",
47
+ "node.js.detected.package.eslint": "true",
48
+ "node.js.detected.package.tslint": "true",
49
+ "node.js.selected.package.eslint": "(autodetect)",
50
+ "node.js.selected.package.tslint": "(autodetect)",
51
+ "nodejs_package_manager_path": "npm",
52
+ "settings.editor.selected.configurable": "com.intellij.pycharm.community.ide.impl.configuration.PythonContentEntriesConfigurable",
53
+ "vue.rearranger.settings.migration": "true"
46
54
  }
47
- }</component>
55
+ }]]></component>
48
56
  <component name="RecentsManager">
49
57
  <key name="MoveFile.RECENT_KEYS">
50
58
  <recent name="$PROJECT_DIR$/src/xgae/engine/responser" />
@@ -60,6 +68,7 @@
60
68
  <env name="PYTHONUNBUFFERED" value="1" />
61
69
  </envs>
62
70
  <option name="SDK_HOME" value="" />
71
+ <option name="SDK_NAME" value="Python 3.13 virtualenv at ~/DevProjects/xga/xgae/.venv" />
63
72
  <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
64
73
  <option name="IS_MODULE_SDK" value="false" />
65
74
  <option name="ADD_CONTENT_ROOTS" value="true" />
@@ -84,7 +93,7 @@
84
93
  <option name="INPUT_FILE" value="" />
85
94
  <method v="2" />
86
95
  </configuration>
87
- <configuration name="run_xga_engine" type="PythonConfigurationType" factoryName="Python" temporary="true">
96
+ <configuration name="run_task_engine" type="PythonConfigurationType" factoryName="Python">
88
97
  <module name="xgae" />
89
98
  <option name="ENV_FILES" value="" />
90
99
  <option name="INTERPRETER_OPTIONS" value="" />
@@ -117,11 +126,6 @@
117
126
  <option name="INPUT_FILE" value="" />
118
127
  <method v="2" />
119
128
  </configuration>
120
- <recent_temporary>
121
- <list>
122
- <item itemvalue="Python.run_xga_engine" />
123
- </list>
124
- </recent_temporary>
125
129
  </component>
126
130
  <component name="SharedIndexes">
127
131
  <attachedChunks>
@@ -145,46 +149,48 @@
145
149
  <workItem from="1755270285722" duration="12068000" />
146
150
  <workItem from="1755283728206" duration="1511000" />
147
151
  <workItem from="1755286552901" duration="3130000" />
148
- <workItem from="1755307767865" duration="337000" />
149
- <workItem from="1755308113420" duration="2825000" />
150
- <workItem from="1755321931380" duration="8531000" />
151
- <workItem from="1755330830188" duration="229000" />
152
- <workItem from="1755396985976" duration="6435000" />
153
- <workItem from="1755415614018" duration="461000" />
154
- <workItem from="1755416476555" duration="256000" />
155
- <workItem from="1755419142521" duration="150000" />
156
- <workItem from="1755429851003" duration="290000" />
157
- <workItem from="1755430331676" duration="21000" />
158
- <workItem from="1755529583730" duration="1643000" />
159
- <workItem from="1755609955702" duration="37000" />
160
- <workItem from="1755612319869" duration="621000" />
161
- <workItem from="1755690345086" duration="6847000" />
162
- <workItem from="1755781193226" duration="485000" />
163
- <workItem from="1755781685826" duration="38000" />
164
- <workItem from="1755781735460" duration="170000" />
165
- <workItem from="1755817545372" duration="285000" />
166
- <workItem from="1755862006389" duration="1051000" />
167
- <workItem from="1755863094184" duration="553000" />
168
- <workItem from="1755863979826" duration="4236000" />
169
- <workItem from="1755875255916" duration="2308000" />
170
- <workItem from="1755877576514" duration="2000" />
171
- <workItem from="1755878805800" duration="3590000" />
172
- <workItem from="1755904284653" duration="7803000" />
173
- <workItem from="1755912586948" duration="1553000" />
174
- <workItem from="1755915353109" duration="5872000" />
175
- <workItem from="1755997335603" duration="7550000" />
152
+ <workItem from="1755409529770" duration="670000" />
153
+ <workItem from="1755478595168" duration="20000" />
154
+ <workItem from="1755479257156" duration="16895000" />
155
+ <workItem from="1755525531052" duration="13030000" />
156
+ <workItem from="1755585869510" duration="5796000" />
157
+ <workItem from="1755593112104" duration="786000" />
158
+ <workItem from="1755611972189" duration="13340000" />
159
+ <workItem from="1755668525673" duration="14877000" />
160
+ <workItem from="1755700523844" duration="24000" />
161
+ <workItem from="1755737435202" duration="48139000" />
162
+ <workItem from="1756044658912" duration="1248000" />
163
+ <workItem from="1756082326044" duration="23338000" />
176
164
  </task>
177
165
  <servers />
178
166
  </component>
179
167
  <component name="TypeScriptGeneratedFilesManager">
180
168
  <option name="version" value="3" />
181
169
  </component>
170
+ <component name="XDebuggerManager">
171
+ <breakpoint-manager>
172
+ <default-breakpoints>
173
+ <breakpoint type="python-exception">
174
+ <properties notifyOnTerminate="true" exception="BaseException">
175
+ <option name="notifyOnTerminate" value="true" />
176
+ </properties>
177
+ </breakpoint>
178
+ </default-breakpoints>
179
+ </breakpoint-manager>
180
+ </component>
182
181
  <component name="com.intellij.coverage.CoverageDataManagerImpl">
183
- <SUITE FILE_PATH="coverage/xgae$xga_mcp_tool_box.coverage" NAME="xga_mcp_tool_box Coverage Results" MODIFIED="1755864064753" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
184
- <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$" />
185
- <SUITE FILE_PATH="coverage/xgae$setup_env.coverage" NAME="setup_env Coverage Results" MODIFIED="1755876041210" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
186
- <SUITE FILE_PATH="coverage/xgae$run_xga_engine.coverage" NAME="run_xga_engine Coverage Results" MODIFIED="1756003050644" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
187
- <SUITE FILE_PATH="coverage/xgae$xga_engine.coverage" NAME="xga_engine Coverage Results" MODIFIED="1755876180376" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
182
+ <SUITE FILE_PATH="coverage/xgae$xga_engine.coverage" NAME="xga_engine Coverage Results" MODIFIED="1755580277172" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
183
+ <SUITE FILE_PATH="coverage/xgae$run_simple.coverage" NAME="run_simple Coverage Results" MODIFIED="1756111714718" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
184
+ <SUITE FILE_PATH="coverage/xgae$run_xga_engine.coverage" NAME="run_task_engine Coverage Results" MODIFIED="1756111613459" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
185
+ <SUITE FILE_PATH="coverage/xgae$message_tools_app.coverage" NAME="message_tools_app Coverage Results" MODIFIED="1756094157566" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
186
+ <SUITE FILE_PATH="coverage/xgae$run_engine_with_human_in_loop.coverage" NAME="run_engine_with_human_in_loop Coverage Results" MODIFIED="1756089269027" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
187
+ <SUITE FILE_PATH="coverage/xgae$xga_prompt_builder.coverage" NAME="xga_prompt_builder Coverage Results" MODIFIED="1755587456555" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
188
+ <SUITE FILE_PATH="coverage/xgae$responser_base.coverage" NAME="responser_base Coverage Results" MODIFIED="1756103040764" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
188
189
  <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$" />
190
+ <SUITE FILE_PATH="coverage/xgae$setup_env.coverage" NAME="setup_env Coverage Results" MODIFIED="1755657717310" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
191
+ <SUITE FILE_PATH="coverage/xgae$run_user_prompt.coverage" NAME="run_user_prompt Coverage Results" MODIFIED="1756089624828" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
192
+ <SUITE FILE_PATH="coverage/xgae$xga_mcp_tool_box.coverage" NAME="xga_mcp_tool_box Coverage Results" MODIFIED="1755583099719" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
193
+ <SUITE FILE_PATH="coverage/xgae$task_engine.coverage" NAME="task_engine Coverage Results" MODIFIED="1756102942600" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
194
+ <SUITE FILE_PATH="coverage/xgae$llm_client.coverage" NAME="llm_client Coverage Results" MODIFIED="1756112044142" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
189
195
  </component>
190
196
  </project>
@@ -5,7 +5,7 @@
5
5
  <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
6
6
  <excludeFolder url="file://$MODULE_DIR$/.venv" />
7
7
  </content>
8
- <orderEntry type="jdk" jdkName="Python 3.13 virtualenv at ~/DevelopSpace/xgae/.venv" jdkType="Python SDK" />
8
+ <orderEntry type="jdk" jdkName="Python 3.13 virtualenv at ~/DevProjects/xga/xgae/.venv" jdkType="Python SDK" />
9
9
  <orderEntry type="sourceFolder" forTests="false" />
10
10
  </component>
11
11
  </module>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xgae
3
- Version: 0.1.6
3
+ Version: 0.1.7
4
4
  Summary: Extreme General Agent Engine
5
5
  Requires-Python: >=3.13
6
6
  Requires-Dist: colorlog>=6.9.0
@@ -0,0 +1,11 @@
1
+ {
2
+ "mcpServers":{
3
+ "xga_general": {
4
+ "command": "uv",
5
+ "args": [
6
+ "run",
7
+ "xgae-tools"
8
+ ]
9
+ }
10
+ }
11
+ }
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "xgae"
3
- version = "0.1.6"
3
+ version = "0.1.7"
4
4
  description = "Extreme General Agent Engine"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -21,3 +21,4 @@ build-backend = "hatchling.build"
21
21
  exclude = ["log/*"]
22
22
 
23
23
  [project.scripts]
24
+ xgae-tools = "xgae.tools.without_general_tools_app:main"
@@ -0,0 +1,29 @@
1
+ import asyncio
2
+
3
+ from xgae.engine.mcp_tool_box import XGAMcpToolBox
4
+ from xgae.engine.task_engine import XGATaskEngine
5
+ from xgae.utils.llm_client import LLMConfig
6
+ from xgae.utils.misc import read_file
7
+
8
+
9
+ async def main() -> None:
10
+ tool_box = XGAMcpToolBox(custom_mcp_server_file="mcpservers/custom_servers.json")
11
+ system_prompt = read_file("templates/example_user_prompt.txt")
12
+ engine = XGATaskEngine(tool_box=tool_box,
13
+ general_tools=[],
14
+ custom_tools=["*"],
15
+ llm_config=LLMConfig(stream=False),
16
+ system_prompt=system_prompt,
17
+ max_auto_run=8)
18
+
19
+ user_input = "locate fault and solution"
20
+ final_result = await engine.run_task_with_final_answer(task_message={"role": "user", "content": user_input})
21
+ print("FINAL RESULT:", final_result)
22
+
23
+ if final_result["type"] == "ask":
24
+ print("====== Wait for user input ... ======")
25
+ user_input = "ip=10.0.1.1"
26
+ final_result = await engine.run_task_with_final_answer(task_message={"role": "user", "content": user_input})
27
+ print("FINAL RESULT:", final_result)
28
+
29
+ asyncio.run(main())
@@ -0,0 +1,12 @@
1
+ import asyncio
2
+
3
+ from xgae.engine.task_engine import XGATaskEngine
4
+ from xgae.utils.llm_client import LLMConfig
5
+
6
+
7
+ async def main() -> None:
8
+ engine = XGATaskEngine(llm_config=LLMConfig(stream=False), max_auto_run=1)
9
+ final_result = await engine.run_task_with_final_answer(task_message={"role": "user", "content": "1+1"})
10
+ print("FINAL RESULT:", final_result)
11
+
12
+ asyncio.run(main())
@@ -0,0 +1,30 @@
1
+ import asyncio
2
+
3
+ from xgae.engine.mcp_tool_box import XGAMcpToolBox
4
+ from xgae.engine.task_engine import XGATaskEngine
5
+ from xgae.utils.llm_client import LLMConfig
6
+ from xgae.utils.misc import read_file
7
+
8
+
9
+ async def main() -> None:
10
+ tool_box = XGAMcpToolBox(custom_mcp_server_file="mcpservers/custom_servers.json")
11
+ system_prompt = read_file("templates/example_user_prompt.txt")
12
+ engine = XGATaskEngine(tool_box=tool_box,
13
+ general_tools=[],
14
+ custom_tools=["*"],
15
+ llm_config=LLMConfig(stream=False),
16
+ system_prompt=system_prompt,
17
+ max_auto_run=8)
18
+
19
+ user_input = "locate 10.2.3.4 fault and solution"
20
+ is_final_result = False
21
+
22
+ if is_final_result:
23
+ final_result = await engine.run_task_with_final_answer(task_message={"role": "user", "content": user_input})
24
+ print("FINAL RESULT:", final_result)
25
+ else:
26
+ # Get All Task Process Message
27
+ async for chunk in engine.run_task(task_message={"role": "user", "content": user_input}):
28
+ print(chunk)
29
+
30
+ asyncio.run(main())
@@ -46,6 +46,12 @@ class XGAMcpToolBox(XGAToolBox):
46
46
  task_tool_schemas[tool_schema.tool_name] = tool_schema
47
47
  task_tool_schemas.pop("end_task", None)
48
48
 
49
+ if len(custom_tools) == 1 and custom_tools[0] == "*":
50
+ custom_tools = []
51
+ for server_name in self.mcp_server_names:
52
+ if server_name != XGAMcpToolBox.GENERAL_MCP_SERVER_NAME:
53
+ custom_tools.append(f"{server_name}.*")
54
+
49
55
  for server_tool_name in custom_tools:
50
56
  parts = server_tool_name.split(".")
51
57
  if len(parts) != 2:
@@ -3,7 +3,7 @@ import datetime
3
3
 
4
4
  from typing import Optional, List
5
5
 
6
- from engine_base import XGAToolSchema, XGAError
6
+ from xgae.engine.engine_base import XGAToolSchema, XGAError
7
7
  from xgae.utils.misc import read_file, format_file_with_args
8
8
 
9
9
 
@@ -24,8 +24,8 @@ class NonStreamTaskResponser(TaskResponseProcessor):
24
24
  finish_reason = llm_response.choices[0].finish_reason
25
25
  logging.info(f"NonStreamTask:LLM response finish_reason={finish_reason}")
26
26
 
27
- langfuse.create_event(trace_context=self.trace_context, name="non_streaming_finish_reason", level="DEFAULT",
28
- status_message=(f"Non-streaming finish_reason: {finish_reason}"))
27
+ langfuse.create_event(trace_context=self.trace_context, name="non_stream_processor_start", level="DEFAULT",
28
+ status_message=(f"finish_reason={finish_reason}, tool_exec_strategy={self.tool_execution_strategy}"))
29
29
 
30
30
  response_message = llm_response.choices[0].message if hasattr(llm_response.choices[0], 'message') else None
31
31
  if response_message:
@@ -54,8 +54,6 @@ class NonStreamTaskResponser(TaskResponseProcessor):
54
54
  tool_calls_to_execute = [item['tool_call'] for item in parsed_xml_data]
55
55
  if len(tool_calls_to_execute) > 0:
56
56
  logging.info(f"NonStreamTask:Executing {len(tool_calls_to_execute)} tools with strategy: {self.tool_execution_strategy}")
57
- langfuse.create_event(trace_context=self.trace_context, name="executing_tools_with_strategy", level="DEFAULT", status_message=(
58
- f"NonStreamTask Executing {len(tool_calls_to_execute)} tools with strategy: {self.tool_execution_strategy}"))
59
57
 
60
58
  tool_results = await self._execute_tools(tool_calls_to_execute, self.tool_execution_strategy)
61
59
 
@@ -22,6 +22,7 @@ class TaskResponserContext(TypedDict, total=False):
22
22
  task_id: str
23
23
  task_run_id: str
24
24
  trace_id: str
25
+ root_span_id: str
25
26
  model_name: str
26
27
  max_xml_tool_calls: int # LLM generate max_xml_tool limit, 0 is no limit
27
28
  add_response_msg_func: Callable
@@ -34,7 +35,7 @@ class TaskRunContinuousState(TypedDict, total=False):
34
35
  accumulated_content: str
35
36
  auto_continue_count: int
36
37
  auto_continue: bool
37
- max_auto_run: int
38
+
38
39
 
39
40
  @dataclass
40
41
  class ToolExecutionContext:
@@ -61,7 +62,7 @@ class TaskResponseProcessor(ABC):
61
62
 
62
63
  self.trace_context = {
63
64
  "trace_id": self.response_context.get("trace_id"),
64
- "parent_span_id": None
65
+ "parent_span_id": self.response_context.get("root_span_id"),
65
66
  }
66
67
 
67
68
  self.add_response_message = response_context.get("add_response_msg_func")
@@ -257,38 +258,36 @@ class TaskResponseProcessor(ABC):
257
258
 
258
259
  async def _execute_tool(self, tool_call: Dict[str, Any]) -> XGAToolResult:
259
260
  """Execute a single tool call and return the result."""
260
- span = langfuse.start_span(trace_context=self.trace_context, name=f"execute_tool.{tool_call['function_name']}", input=tool_call["arguments"])
261
- self.trace_context["parent_span_id"] = span.id
262
- try:
263
- function_name = tool_call["function_name"]
264
- arguments = tool_call["arguments"]
265
-
266
- logging.info(f"Executing tool: {function_name} with arguments: {arguments}")
267
- langfuse.create_event(trace_context=self.trace_context, name="executing_tool", level="DEFAULT",
268
- status_message=(f"Executing tool: {function_name} with arguments: {arguments}"))
269
-
270
- if isinstance(arguments, str):
271
- try:
272
- arguments = safe_json_parse(arguments)
273
- except json.JSONDecodeError:
274
- arguments = {"text": arguments} # @todo modify
275
-
276
- result = None
277
- available_tool_names = self.tool_box.get_task_tool_names(self.task_id)
278
- if function_name in available_tool_names:
279
- result = await self.tool_box.call_tool(self.task_id, function_name, arguments)
280
- else:
281
- logging.error(f"Tool function '{function_name}' not found in registry")
282
- result = XGAToolResult(success=False, output=f"Tool function '{function_name}' not found")
283
- logging.info(f"Tool execution complete: {function_name} -> {result}")
284
- langfuse.update_current_span(status_message="tool_executed", output=result)
285
-
286
- return result
287
- except Exception as e:
288
- logging.error(f"Error executing tool {tool_call['function_name']}: {str(e)}", exc_info=True)
261
+ with langfuse.start_as_current_span(trace_context=self.trace_context, name=f"execute_tool.{tool_call['function_name']}", input=tool_call["arguments"]
262
+ ) as exec_tool_span:
263
+ try:
264
+ function_name = tool_call["function_name"]
265
+ arguments = tool_call["arguments"]
266
+
267
+ logging.info(f"Executing tool: {function_name} with arguments: {arguments}")
268
+
269
+ if isinstance(arguments, str):
270
+ try:
271
+ arguments = safe_json_parse(arguments)
272
+ except json.JSONDecodeError:
273
+ arguments = {"text": arguments} # @todo modify
274
+
275
+ result = None
276
+ available_tool_names = self.tool_box.get_task_tool_names(self.task_id)
277
+ if function_name in available_tool_names:
278
+ result = await self.tool_box.call_tool(self.task_id, function_name, arguments)
279
+ else:
280
+ logging.error(f"Tool function '{function_name}' not found in registry")
281
+ result = XGAToolResult(success=False, output=f"Tool function '{function_name}' not found")
282
+ logging.info(f"Tool execution complete: {function_name} -> {result}")
283
+ exec_tool_span.update(status_message="tool_executed", output=result)
284
+
285
+ return result
286
+ except Exception as e:
287
+ logging.error(f"Error executing tool {tool_call['function_name']}: {str(e)}", exc_info=True)
289
288
 
290
- langfuse.update_current_span(status_message="tool_execution_error", output=f"Error executing tool: {str(e)}", level="ERROR")
291
- return XGAToolResult(success=False, output=f"Error executing tool: {str(e)}")
289
+ exec_tool_span.update(status_message="tool_execution_error", output=f"Error executing tool: {str(e)}", level="ERROR")
290
+ return XGAToolResult(success=False, output=f"Error executing tool: {str(e)}")
292
291
 
293
292
  async def _execute_tools(
294
293
  self,
@@ -296,8 +295,6 @@ class TaskResponseProcessor(ABC):
296
295
  execution_strategy: ToolExecutionStrategy = "sequential"
297
296
  ) -> List[Tuple[Dict[str, Any], XGAToolResult]]:
298
297
  logging.info(f"Executing {len(tool_calls)} tools with strategy: {execution_strategy}")
299
- langfuse.create_event(trace_context=self.trace_context, name="executing_tools_with_strategy", level="DEFAULT",
300
- status_message=(f"Executing {len(tool_calls)} tools with strategy: {execution_strategy}"))
301
298
 
302
299
  if execution_strategy == "sequential":
303
300
  return await self._execute_tools_sequentially(tool_calls)
@@ -340,9 +337,8 @@ class TaskResponseProcessor(ABC):
340
337
  # Check if this is a terminating tool (ask or complete)
341
338
  if tool_name in ['ask', 'complete']:
342
339
  logging.info(f"Terminating tool '{tool_name}' executed. Stopping further tool execution.")
343
- langfuse.create_event(trace_context=self.trace_context, name="terminating_tool_executed",
344
- level="DEFAULT", status_message=(
345
- f"Terminating tool '{tool_name}' executed. Stopping further tool execution."))
340
+ # langfuse.create_event(trace_context=self.trace_context, name="terminating_tool_executed",
341
+ # level="DEFAULT", status_message=(f"Terminating tool '{tool_name}' executed. Stopping further tool execution."))
346
342
  break # Stop executing remaining tools
347
343
 
348
344
  except Exception as e:
@@ -353,9 +349,8 @@ class TaskResponseProcessor(ABC):
353
349
  results.append((tool_call, error_result))
354
350
 
355
351
  logging.info(f"Sequential execution completed for {len(results)} tools (out of {len(tool_calls)} total)")
356
- langfuse.create_event(trace_context=self.trace_context, name="sequential_execution_completed", level="DEFAULT",
357
- status_message=(
358
- f"Sequential execution completed for {len(results)} tools (out of {len(tool_calls)} total)"))
352
+ # langfuse.create_event(trace_context=self.trace_context, name="sequential_execution_completed", level="DEFAULT",
353
+ # status_message=(f"Sequential execution completed for {len(results)} tools (out of {len(tool_calls)} total)"))
359
354
  return results
360
355
 
361
356
 
@@ -366,8 +361,8 @@ class TaskResponseProcessor(ABC):
366
361
  try:
367
362
  tool_names = [t.get('function_name', 'unknown') for t in tool_calls]
368
363
  logging.info(f"Executing {len(tool_calls)} tools in parallel: {tool_names}")
369
- langfuse.create_event(trace_context=self.trace_context, name="executing_tools_in_parallel", level="DEFAULT",
370
- status_message=(f"Executing {len(tool_calls)} tools in parallel: {tool_names}"))
364
+ # langfuse.create_event(trace_context=self.trace_context, name="executing_tools_in_parallel", level="DEFAULT",
365
+ # status_message=(f"Executing {len(tool_calls)} tools in parallel: {tool_names}"))
371
366
 
372
367
  # Create tasks for all tool calls
373
368
  tasks = [self._execute_tool(tool_call) for tool_call in tool_calls]
@@ -389,8 +384,8 @@ class TaskResponseProcessor(ABC):
389
384
  processed_results.append((tool_call, result))
390
385
 
391
386
  logging.info(f"Parallel execution completed for {len(tool_calls)} tools")
392
- langfuse.create_event(trace_context=self.trace_context, name="parallel_execution_completed", level="DEFAULT",
393
- status_message=(f"Parallel execution completed for {len(tool_calls)} tools"))
387
+ # langfuse.create_event(trace_context=self.trace_context, name="parallel_execution_completed", level="DEFAULT",
388
+ # status_message=(f"Parallel execution completed for {len(tool_calls)} tools"))
394
389
  return processed_results
395
390
 
396
391
  except Exception as e:
@@ -417,16 +412,11 @@ class TaskResponseProcessor(ABC):
417
412
  if assistant_message_id:
418
413
  metadata["assistant_message_id"] = assistant_message_id
419
414
  logging.info(f"Linking tool result to assistant message: {assistant_message_id}")
420
- langfuse.create_event(trace_context=self.trace_context, name="linking_tool_result_to_assistant_message", level="DEFAULT",
421
- status_message=(f"Linking tool result to assistant message: {assistant_message_id}"))
422
415
 
423
416
  # --- Add parsing details to metadata if available ---
424
417
  if parsing_details:
425
418
  metadata["parsing_details"] = parsing_details
426
419
  logging.info("Adding parsing_details to tool result metadata")
427
- langfuse.create_event(trace_context=self.trace_context, name="adding_parsing_details_to_tool_result_metadata", level="DEFAULT",
428
- status_message=(f"Adding parsing_details to tool result metadata"),
429
- metadata={"parsing_details": parsing_details})
430
420
 
431
421
  # For XML and other non-native tools, use the new structured format
432
422
  # Determine message role based on strategy
@@ -599,8 +589,8 @@ class TaskResponseProcessor(ABC):
599
589
  if context.function_name in ['ask', 'complete']:
600
590
  metadata["agent_should_terminate"] = "true"
601
591
  logging.info(f"Marking tool status for '{context.function_name}' with termination signal.")
602
- langfuse.create_event(trace_context=self.trace_context, name="marking_tool_status_for_termination", level="DEFAULT", status_message=(
603
- f"Marking tool status for '{context.function_name}' with termination signal."))
592
+ # langfuse.create_event(trace_context=self.trace_context, name="marking_tool_status_for_termination", level="DEFAULT", status_message=(
593
+ # f"Marking tool status for '{context.function_name}' with termination signal."))
604
594
  # <<< END ADDED >>>
605
595
 
606
596
  return self.add_response_message(
@@ -1,6 +1,7 @@
1
1
 
2
2
  import logging
3
3
  import json
4
+ import os
4
5
 
5
6
  from typing import List, Any, Dict, Optional, AsyncGenerator, Union, Literal
6
7
  from uuid import uuid4
@@ -12,16 +13,19 @@ from xgae.utils import langfuse, handle_error
12
13
  from xgae.utils.llm_client import LLMClient, LLMConfig
13
14
 
14
15
  from xgae.utils.json_helpers import format_for_yield
15
- from prompt_builder import XGAPromptBuilder
16
- from mcp_tool_box import XGAMcpToolBox
16
+ from xgae.engine.prompt_builder import XGAPromptBuilder
17
+ from xgae.engine.mcp_tool_box import XGAMcpToolBox
17
18
 
18
19
  class XGATaskEngine:
19
20
  def __init__(self,
20
21
  session_id: Optional[str] = None,
21
22
  task_id: Optional[str] = None,
22
23
  agent_id: Optional[str] = None,
23
- trace_id: Optional[str] = None,
24
+ general_tools: Optional[List[str]] = None,
25
+ custom_tools: Optional[List[str]] = None,
24
26
  system_prompt: Optional[str] = None,
27
+ max_auto_run: Optional[int] = None,
28
+ tool_exec_parallel: Optional[bool] = None,
25
29
  llm_config: Optional[LLMConfig] = None,
26
30
  prompt_builder: Optional[XGAPromptBuilder] = None,
27
31
  tool_box: Optional[XGAToolBox] = None):
@@ -34,125 +38,109 @@ class XGATaskEngine:
34
38
  self.is_stream = self.llm_client.is_stream
35
39
 
36
40
  self.prompt_builder = prompt_builder or XGAPromptBuilder(system_prompt)
37
- self.tool_box = tool_box or XGAMcpToolBox()
41
+ self.tool_box: XGAToolBox = tool_box or XGAMcpToolBox()
38
42
 
43
+ self.general_tools:List[str] = general_tools
44
+ self.custom_tools:List[str] = custom_tools
39
45
  self.task_response_msgs: List[XGAResponseMessage] = []
40
- self.task_no = -1
41
- self.task_run_id = f"{self.task_id}[{self.task_no}]"
42
- self.trace_id :str = trace_id or langfuse.create_trace_id()
43
-
44
- async def _post_init_(self, general_tools:List[str], custom_tools: List[str]) -> None:
45
- await self.tool_box.load_mcp_tools_schema()
46
- await self.tool_box.creat_task_tool_box(self.task_id, general_tools, custom_tools)
47
- general_tool_schemas = self.tool_box.get_task_tool_schemas(self.task_id, "general_tool")
48
- custom_tool_schemas = self.tool_box.get_task_tool_schemas(self.task_id, "custom_tool")
49
46
 
50
- self.task_prompt = self.prompt_builder.build_task_prompt(self.model_name, general_tool_schemas, custom_tool_schemas)
47
+ max_auto_run = max_auto_run if max_auto_run else int(os.getenv("MAX_AUTO_RUN", 15))
48
+ self.max_auto_run: int = 1 if max_auto_run <= 1 else max_auto_run
49
+ self.tool_exec_parallel = True if tool_exec_parallel is None else tool_exec_parallel
51
50
 
52
- @classmethod
53
- async def create(cls,
54
- session_id: Optional[str] = None,
55
- task_id: Optional[str] = None,
56
- agent_id: Optional[str] = None,
57
- trace_id: Optional[str] = None,
58
- system_prompt: Optional[str] = None,
59
- general_tools: Optional[List[str]] = None,
60
- custom_tools: Optional[List[str]] = None,
61
- llm_config: Optional[LLMConfig] = None,
62
- prompt_builder: Optional[XGAPromptBuilder] = None,
63
- tool_box: Optional[XGAToolBox] = None) -> 'XGATaskEngine':
64
- engine: XGATaskEngine = cls(session_id=session_id,
65
- task_id=task_id,
66
- agent_id=agent_id,
67
- trace_id=trace_id,
68
- system_prompt=system_prompt,
69
- llm_config=llm_config,
70
- prompt_builder=prompt_builder,
71
- tool_box=tool_box)
51
+ self.task_no = -1
52
+ self.task_run_id :str = None
72
53
 
73
- general_tools = general_tools or ["complete", "ask"]
74
- if "*" not in general_tools:
75
- if "complete" not in general_tools:
76
- general_tools.append("complete")
77
- elif "ask" not in general_tools:
78
- general_tools.append("ask")
54
+ self.task_prompt :str = None
55
+ self.trace_id :str = None
56
+ self.root_span_id :str = None
79
57
 
80
- custom_tools = custom_tools or []
81
- await engine._post_init_(general_tools, custom_tools)
58
+ async def run_task_with_final_answer(self,
59
+ task_message: Dict[str, Any],
60
+ trace_id: Optional[str] = None) -> XGATaskResult:
61
+ self.trace_id = trace_id or langfuse.create_trace_id()
62
+ with langfuse.start_as_current_span(trace_context={"trace_id": self.trace_id},
63
+ name="run_task_with_final_answer",
64
+ input=task_message,
65
+ metadata={"task_id": self.task_id},
66
+ ) as root_span:
67
+ self.root_span_id = root_span.id
68
+
69
+ chunks = []
70
+ async for chunk in self.run_task(task_message=task_message, trace_id=trace_id):
71
+ chunks.append(chunk)
72
+
73
+ if len(chunks) > 0:
74
+ final_result = self._parse_final_result(chunks)
75
+ else:
76
+ final_result = XGATaskResult(type="error", content="LLM Answer is Empty")
82
77
 
83
- logging.info("*"*30 + f" XGATaskEngine Task'{engine.task_id}' Initialized " + "*"*30)
84
- logging.info(f"model_name={engine.model_name}, is_stream={engine.is_stream}, trace_id={engine.trace_id}")
85
- logging.info(f"general_tools={general_tools}, custom_tools={custom_tools}")
78
+ root_span.update(output=final_result)
79
+ return final_result
86
80
 
87
- return engine
88
-
89
- async def run_task_with_final_answer(self,
90
- task_message: Dict[str, Any],
91
- max_auto_run: int = 25,
92
- trace_id: Optional[str] = None) -> XGATaskResult:
93
- chunks = []
94
- async for chunk in self.run_task(task_message=task_message, max_auto_run=max_auto_run, trace_id=trace_id):
95
- chunks.append(chunk)
96
- if len(chunks) > 0:
97
- final_result = self._parse_final_result(chunks)
98
- else:
99
- final_result = XGATaskResult(type="error", content="LLM Answer is Empty")
100
- return final_result
101
81
 
102
82
  async def run_task(self,
103
83
  task_message: Dict[str, Any],
104
- max_auto_run: int = 25,
105
84
  trace_id: Optional[str] = None) -> AsyncGenerator[Dict[str, Any], None]:
106
85
  try:
107
- self.trace_id = trace_id or self.trace_id or langfuse.create_trace_id()
86
+ await self._init_task()
87
+ if self.root_span_id is None:
88
+ self.trace_id = trace_id or langfuse.create_trace_id()
89
+ with langfuse.start_as_current_span(trace_context={"trace_id": self.trace_id},
90
+ name="run_task",
91
+ input=task_message
92
+ ) as root_span:
93
+ self.root_span_id = root_span.id
108
94
 
109
- self.task_no += 1
110
- self.task_run_id = f"{self.task_id}[{self.task_no}]"
111
95
 
112
96
  self.add_response_message(type="user", content=task_message, is_llm_message=True)
113
97
 
114
98
  continuous_state: TaskRunContinuousState = {
115
99
  "accumulated_content": "",
116
100
  "auto_continue_count": 0,
117
- "auto_continue": False if max_auto_run <= 1 else True,
118
- "max_auto_run": max_auto_run
101
+ "auto_continue": False if self.max_auto_run <= 1 else True
119
102
  }
120
103
  async for chunk in self._run_task_auto(continuous_state):
121
104
  yield chunk
122
105
  finally:
123
106
  await self.tool_box.destroy_task_tool_box(self.task_id)
107
+ self.root_span_id = None
124
108
 
125
- async def _run_task_once(self, continuous_state: TaskRunContinuousState) -> AsyncGenerator[Dict[str, Any], None]:
126
- llm_messages = [{"role": "system", "content": self.task_prompt}]
127
- cxt_llm_contents = self.get_history_llm_messages()
128
- llm_messages.extend(cxt_llm_contents)
129
109
 
130
- partial_content = continuous_state.get('accumulated_content', '')
131
- if partial_content:
132
- temp_assistant_message = {
133
- "role": "assistant",
134
- "content": partial_content
135
- }
136
- llm_messages.append(temp_assistant_message)
110
+ async def _init_task(self) -> None:
111
+ self.task_no = self.task_no + 1
112
+ self.task_run_id = f"{self.task_id}[{self.task_no}]"
137
113
 
138
- llm_response = await self.llm_client.create_completion(llm_messages)
139
- response_processor = self._create_response_processer()
114
+ general_tools = self.general_tools or ["complete", "ask"]
115
+ if "*" not in general_tools:
116
+ if "complete" not in general_tools:
117
+ general_tools.append("complete")
118
+ elif "ask" not in general_tools:
119
+ general_tools.append("ask")
140
120
 
141
- async for chunk in response_processor.process_response(llm_response, llm_messages, continuous_state):
142
- self._logging_reponse_chunk(chunk)
143
- yield chunk
121
+ custom_tools = self.custom_tools or []
122
+ if isinstance(self.tool_box, XGAMcpToolBox):
123
+ await self.tool_box.load_mcp_tools_schema()
144
124
 
145
- async def _run_task_auto(self, continuous_state: TaskRunContinuousState) -> AsyncGenerator[Dict[str, Any], None]:
146
- max_auto_run = continuous_state['max_auto_run']
147
- max_auto_run = max_auto_run if max_auto_run > 0 else 1
125
+ await self.tool_box.creat_task_tool_box(self.task_id, general_tools, custom_tools)
126
+ general_tool_schemas = self.tool_box.get_task_tool_schemas(self.task_id, "general_tool")
127
+ custom_tool_schemas = self.tool_box.get_task_tool_schemas(self.task_id, "custom_tool")
128
+
129
+ self.task_prompt = self.prompt_builder.build_task_prompt(self.model_name, general_tool_schemas, custom_tool_schemas)
148
130
 
131
+ logging.info("*" * 30 + f" XGATaskEngine Task'{self.task_id}' Initialized " + "*" * 30)
132
+ logging.info(f"model_name={self.model_name}, is_stream={self.is_stream}, trace_id={self.trace_id}")
133
+ logging.info(f"general_tools={general_tools}, custom_tools={custom_tools}")
134
+
135
+
136
+ async def _run_task_auto(self, continuous_state: TaskRunContinuousState) -> AsyncGenerator[Dict[str, Any], None]:
149
137
  def update_continuous_state(_auto_continue_count, _auto_continue):
150
138
  continuous_state["auto_continue_count"] = _auto_continue_count
151
139
  continuous_state["auto_continue"] = _auto_continue
152
140
 
153
141
  auto_continue_count = 0
154
142
  auto_continue = True
155
- while auto_continue and auto_continue_count < max_auto_run:
143
+ while auto_continue and auto_continue_count < self.max_auto_run:
156
144
  auto_continue = False
157
145
 
158
146
  try:
@@ -180,7 +168,7 @@ class XGATaskEngine:
180
168
  auto_continue = True
181
169
  auto_continue_count += 1
182
170
  update_continuous_state(auto_continue_count, auto_continue)
183
- logging.info(f"run_task_auto: Detected finish_reason='{finish_reason}', auto-continuing ({auto_continue_count}/{max_auto_run})")
171
+ logging.info(f"run_task_auto: Detected finish_reason='{finish_reason}', auto-continuing ({auto_continue_count}/{self.max_auto_run})")
184
172
  except Exception as parse_error:
185
173
  logging.error(f"run_task_auto: Error in parse chunk: {str(parse_error)}")
186
174
  content = {"role": "system", "status_type": "error", "message": "Parse response chunk Error"}
@@ -194,6 +182,28 @@ class XGATaskEngine:
194
182
  error_msg = self.add_response_message(type="status", content=content, is_llm_message=False)
195
183
  yield format_for_yield(error_msg)
196
184
 
185
+
186
+ async def _run_task_once(self, continuous_state: TaskRunContinuousState) -> AsyncGenerator[Dict[str, Any], None]:
187
+ llm_messages = [{"role": "system", "content": self.task_prompt}]
188
+ cxt_llm_contents = self.get_history_llm_messages()
189
+ llm_messages.extend(cxt_llm_contents)
190
+
191
+ partial_content = continuous_state.get('accumulated_content', '')
192
+ if partial_content:
193
+ temp_assistant_message = {
194
+ "role": "assistant",
195
+ "content": partial_content
196
+ }
197
+ llm_messages.append(temp_assistant_message)
198
+
199
+ llm_response = await self.llm_client.create_completion(llm_messages, self.trace_id)
200
+ response_processor = self._create_response_processer()
201
+
202
+ async for chunk in response_processor.process_response(llm_response, llm_messages, continuous_state):
203
+ self._logging_reponse_chunk(chunk)
204
+ yield chunk
205
+
206
+
197
207
  def _parse_final_result(self, chunks: List[Dict[str, Any]]) -> XGATaskResult:
198
208
  final_result: XGATaskResult = None
199
209
  try:
@@ -245,6 +255,7 @@ class XGATaskEngine:
245
255
 
246
256
  return final_result
247
257
 
258
+
248
259
  def add_response_message(self, type: XGAResponseMsgType,
249
260
  content: Union[Dict[str, Any], List[Any], str],
250
261
  is_llm_message: bool,
@@ -289,6 +300,7 @@ class XGATaskEngine:
289
300
 
290
301
  return response_llm_contents
291
302
 
303
+
292
304
  def _create_response_processer(self) -> TaskResponseProcessor:
293
305
  response_context = self._create_response_context()
294
306
  is_stream = response_context.get("is_stream", False)
@@ -305,11 +317,12 @@ class XGATaskEngine:
305
317
  "task_id": self.task_id,
306
318
  "task_run_id": self.task_run_id,
307
319
  "trace_id": self.trace_id,
320
+ "root_span_id": self.root_span_id,
308
321
  "model_name": self.model_name,
309
322
  "max_xml_tool_calls": 0,
310
323
  "add_response_msg_func": self.add_response_message,
311
324
  "tool_box": self.tool_box,
312
- "tool_execution_strategy": "sequential" ,#"parallel",
325
+ "tool_execution_strategy": "parallel" if self.tool_exec_parallel else "sequential" ,#,
313
326
  "xml_adding_strategy": "user_message",
314
327
  }
315
328
  return response_context
@@ -338,23 +351,17 @@ if __name__ == "__main__":
338
351
 
339
352
  async def main():
340
353
  tool_box = XGAMcpToolBox(custom_mcp_server_file="mcpservers/custom_servers.json")
341
- system_prompt = read_file("templates/scp_test_prompt.txt")
342
- engine = await XGATaskEngine.create(tool_box=tool_box,
343
- general_tools=[],
344
- custom_tools=["bomc_fault.*"],
345
- llm_config=LLMConfig(stream=False),
346
- system_prompt=system_prompt)
347
-
348
- final_result = await engine.run_task_with_final_answer(task_message={"role": "user", "content": "定位10.0.1.1故障"},max_auto_run=8)
354
+ system_prompt = read_file("templates/example_user_prompt.txt")
355
+ engine = XGATaskEngine(tool_box=tool_box,
356
+ general_tools=[],
357
+ custom_tools=["*"],
358
+ llm_config=LLMConfig(stream=False),
359
+ system_prompt=system_prompt,
360
+ max_auto_run=8)
361
+
362
+ final_result = await engine.run_task_with_final_answer(task_message={"role": "user",
363
+ "content": "locate 10.0.0.1 fault and solution"})
349
364
  print("FINAL RESULT:", final_result)
350
365
 
351
- # ==== test streaming response ========
352
- #chunks = []
353
- # async for chunk in engine.run_task(task_message={"role": "user", "content": "定位10.0.0.1的故障"}, max_auto_run=8):
354
- # print(chunk)
355
366
 
356
- # ==== test no tool call ========
357
- # engine = await XGATaskEngine.create(llm_config=LLMConfig(stream=False))
358
- # final_result = await engine.run_task_with_final_answer(task_message={"role": "user", "content": "1+1"}, max_auto_run=2)
359
- # print("FINAL RESULT:", final_result)
360
367
  asyncio.run(main())
@@ -0,0 +1,48 @@
1
+ from typing import Annotated, Optional
2
+ from pydantic import Field
3
+
4
+ from mcp.server.fastmcp import FastMCP
5
+
6
+ from xgae.engine.engine_base import XGAToolResult
7
+
8
+ mcp = FastMCP(name="XGAE Message Tools")
9
+
10
+ @mcp.tool(
11
+ description="""A special tool to indicate you have completed all tasks and are about to enter complete state. Use ONLY when: 1) All tasks in todo.md are marked complete [x], 2) The user's original request has been fully addressed, 3) There are no pending actions or follow-ups required, 4) You've delivered all final outputs and results to the user. IMPORTANT: This is the ONLY way to properly terminate execution. Never use this tool unless ALL tasks are complete and verified. Always ensure you've provided all necessary outputs and references before using this tool. Include relevant attachments when the completion relates to specific files or resources."""
12
+ )
13
+ async def complete(task_id: str,
14
+ text: Annotated[Optional[str], Field(default=None,
15
+ description="Completion summary. Include: 1) Task summary 2) Key deliverables 3) Next steps 4) Impact achieved")],
16
+ attachments: Annotated[Optional[str], Field(default=None,
17
+ description="Comma-separated list of final outputs. Use when: 1) Completion relates to files 2) User needs to review outputs 3) Deliverables in files")]
18
+ ):
19
+ print(f"<XGAETools-complete>: task_id={task_id}, text={text}, attachments={attachments}")
20
+ return XGAToolResult(success=True, output=str({"status": "complete"}))
21
+
22
+
23
+ @mcp.tool(
24
+ description="""Ask user a question and wait for response. Use for: 1) Requesting clarification on ambiguous requirements, 2) Seeking confirmation before proceeding with high-impact changes, 3) Gathering additional information needed to complete a task, 4) Offering options and requesting user preference, 5) Validating assumptions when critical to task success, 6) When encountering unclear or ambiguous results during task execution, 7) When tool results don't match expectations, 8) For natural conversation and follow-up questions, 9) When research reveals multiple entities with the same name, 10) When user requirements are unclear or could be interpreted differently. IMPORTANT: Use this tool when user input is essential to proceed. Always provide clear context and options when applicable. Use natural, conversational language that feels like talking with a helpful friend. Include relevant attachments when the question relates to specific files or resources. CRITICAL: When you discover ambiguity (like multiple people with the same name), immediately stop and ask for clarification rather than making assumptions."""
25
+ )
26
+ async def ask(task_id: str,
27
+ text: Annotated[str, Field(
28
+ description="Question text to present to user. Include: 1) Clear question/request 2) Context why input is needed 3) Available options 4) Impact of choices 5) Relevant constraints")],
29
+ attachments: Annotated[Optional[str], Field(default=None,
30
+ description="Comma-separated list of files/URLs to attach. Use when: 1) Question relates to files/configs 2) User needs to review content 3) Options documented in files 4) Supporting evidence needed")]
31
+ ):
32
+ print(f"<XGAETools-ask>: task_id={task_id}, text={text}, attachments={attachments}")
33
+ return XGAToolResult(success=True, output=str({"status": "Awaiting user response..."}))
34
+
35
+ @mcp.tool(
36
+ description="end task, destroy sandbox"
37
+ )
38
+ async def end_task(task_id: str) :
39
+ print(f"<XGAETools-end_task> task_id: {task_id}")
40
+
41
+
42
+
43
+ def main():
44
+ #print("="*20 + " XGAE Message Tools Sever Started in Stdio mode " + "="*20)
45
+ mcp.run(transport="stdio")
46
+
47
+ if __name__ == "__main__":
48
+ main()
@@ -47,6 +47,7 @@ class LLMClient:
47
47
  reasoning_effort: Optional level of reasoning effort, default is ‘low’
48
48
  top_p: Optional Top-p sampling parameter, default is None
49
49
  """
50
+
50
51
  llm_config = llm_config or LLMConfig()
51
52
  litellm.modify_params = True
52
53
  litellm.drop_params = True
@@ -205,9 +206,10 @@ class LLMClient:
205
206
  logging.debug(f"LLMClient: Waiting {delay} seconds before retry llm completion...")
206
207
  await asyncio.sleep(delay)
207
208
 
208
-
209
- async def create_completion(self, messages: List[Dict[str, Any]]) -> Union[ModelResponse, CustomStreamWrapper]:
209
+ async def create_completion(self, messages: List[Dict[str, Any]], trace_id: Optional[str]=None) -> Union[ModelResponse, CustomStreamWrapper]:
210
210
  complete_params = self._prepare_complete_params(messages)
211
+ if trace_id:
212
+ complete_params["litellm_trace_id"] = trace_id
211
213
 
212
214
  last_error = None
213
215
  for attempt in range(self.max_retries):
@@ -226,10 +228,13 @@ class LLMClient:
226
228
  raise LLMError(f"LLM completion failed after {self.max_retries} attempts !")
227
229
 
228
230
  if __name__ == "__main__":
231
+ from xgae.utils import langfuse
232
+
229
233
  async def llm_completion():
230
234
  llm_client = LLMClient(LLMConfig(stream=False))
231
235
  messages = [{"role": "user", "content": "今天是2025年8月15日,北京本周每天温度"}]
232
- response = await llm_client.create_completion(messages)
236
+ trace_id = langfuse.create_trace_id()
237
+ response = await llm_client.create_completion(messages, trace_id)
233
238
  if llm_client.is_stream:
234
239
  async for chunk in response:
235
240
  choices = chunk.get("choices", [{}])
@@ -64,6 +64,7 @@ def setup_langfuse() -> Langfuse:
64
64
  public_key=env_public_key,
65
65
  secret_key=env_secret_key,
66
66
  host=env_host)
67
+
67
68
  logging.info("Langfuse initialized Successfully by Key !")
68
69
  else:
69
70
  _langfuse = Langfuse(tracing_enabled=False)
@@ -74,5 +75,6 @@ def setup_langfuse() -> Langfuse:
74
75
 
75
76
  if __name__ == "__main__":
76
77
  from xgae.utils import langfuse
78
+
77
79
  trace_id = langfuse.create_trace_id()
78
80
  logging.warning(f"trace_id={trace_id}")
@@ -0,0 +1,22 @@
1
+ #1. Objective: Strictly follow the task steps in TODO.MD to process user instructions. Do not execute any steps not listed. Terminate the process immediately if any task fails without retrying.
2
+ #2. Call MCP tools directly , do not retry if the MCP tool call fails.
3
+ #3. After completing all task steps, output the result and terminate the task by calling the 'complete' tool.
4
+ #4. Extract tool call parameters exclusively from user input and tool outputs. If parameters cannot be extracted, directly call the 'ask' tool to request user input. Never generate parameters outside of user input and tool output.
5
+ #5. TODO.MD
6
+ Process Title: Alarm and Fault Query Process
7
+ Process Summary: Achieve precise fault location through resource alarm detection and classification
8
+ Task[1]: Fault Object Processing
9
+ Step[1]: Query alarm records of target resource
10
+ Step[2]: Check for uncleared alarms
11
+ Step[3]: If no alarms exist, terminate process and return normal status
12
+ Step[4]: If alarms exist, proceed to alarm type analysis
13
+ Step[5]: Determine if alarm type is business layer (type=1)
14
+ Step[6]: If yes, trigger business fault location module
15
+ Step[7]: Else determine if alarm type is device layer (type=2)
16
+ Step[8]: If yes, trigger device fault location module
17
+ Step[9]: Else determine if alarm type is middleware (type=3)
18
+ Step[10]: If yes, trigger middleware fault location module
19
+ Task[2]: Solution Retrieval
20
+ Step[1]: For business alarms, call getBusiFaultCause to query business fault solutions
21
+ Step[2]: For device alarms, call getHostFaultCause to query device fault solutions
22
+ Step[3]: For middleware alarms, call web_search to query middleware fault solutions
@@ -1366,7 +1366,7 @@ wheels = [
1366
1366
 
1367
1367
  [[package]]
1368
1368
  name = "xgae"
1369
- version = "0.1.1"
1369
+ version = "0.1.5"
1370
1370
  source = { editable = "." }
1371
1371
  dependencies = [
1372
1372
  { name = "colorlog" },
@@ -1,21 +0,0 @@
1
- #1. 目标:严格按照TODO.MD中的任务步骤处理用户指令,不要执行没有的任务步骤,任务异常直接结束,不重复执行任务步骤
2
- #2. 调用MCP工具,不要通过"call_mcp_tool", 直接调用,MCP工具调用不成功不重试。
3
- #3. 任务步骤执行完毕,输出结果,就直接调用complete工具终止任务。
4
- #5. TODO.MD
5
- 流程标题:告警与故障查询流程
6
- 流程概要:通过资源告警检测及分类实现故障精准定位
7
- Task[1]: 用户输入故障对象处理
8
- Step[1]: 查询目标资源的告警记录
9
- Step[2]: 判断是否存在未清除的告警
10
- Step[3]: 无告警则终止流程并反馈正常状态
11
- Step[4]: 存在告警则进入告警类型分析
12
- Step[5]: 判断告警类型为业务层告警(类型=1)
13
- Step[6]: 是则触发业务故障定位模块
14
- Step[7]: 否则判断告警类型为设备层告警(类型=2)
15
- Step[8]: 是则触发设备故障定位模块
16
- Step[9]: 否则判断告警类型为中间件告警(类型=3)
17
- Step[10]: 是则触发中间件故障定位模块
18
- Task[2]: 解决方案获取
19
- Step[1]: 若业务告警调用getBusiFaultCause查询业务故障解决方案
20
- Step[2]: 若设备告警调用getHostFaultCause查询设备故障解决方案
21
- Step[3]: 若中间件告警调用web_search查询中间件故障解决方案
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes