xgae 0.1.8__tar.gz → 0.1.9__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.8 → xgae-0.1.9}/.env +1 -0
- {xgae-0.1.8 → xgae-0.1.9}/.idea/workspace.xml +11 -8
- {xgae-0.1.8 → xgae-0.1.9}/PKG-INFO +1 -1
- {xgae-0.1.8 → xgae-0.1.9}/pyproject.toml +2 -1
- {xgae-0.1.8/src/examples → xgae-0.1.9/src/examples/engine}/run_human_in_loop.py +15 -2
- {xgae-0.1.8/src/examples → xgae-0.1.9/src/examples/engine}/run_simple.py +5 -1
- {xgae-0.1.8/src/examples → xgae-0.1.9/src/examples/engine}/run_user_prompt.py +2 -0
- xgae-0.1.9/src/examples/tools/custom_fault_tools_app.py +104 -0
- {xgae-0.1.8 → xgae-0.1.9}/src/xgae/engine/engine_base.py +1 -1
- {xgae-0.1.8 → xgae-0.1.9}/src/xgae/engine/mcp_tool_box.py +6 -1
- {xgae-0.1.8 → xgae-0.1.9}/src/xgae/engine/prompt_builder.py +1 -1
- {xgae-0.1.8 → xgae-0.1.9}/src/xgae/engine/responser/non_stream_responser.py +4 -3
- {xgae-0.1.8 → xgae-0.1.9}/src/xgae/engine/responser/responser_base.py +9 -9
- {xgae-0.1.8 → xgae-0.1.9}/src/xgae/engine/task_engine.py +52 -79
- xgae-0.1.9/src/xgae/engine/task_langfuse.py +63 -0
- {xgae-0.1.8 → xgae-0.1.9}/src/xgae/tools/without_general_tools_app.py +1 -1
- {xgae-0.1.8 → xgae-0.1.9}/src/xgae/utils/__init__.py +6 -0
- {xgae-0.1.8 → xgae-0.1.9}/src/xgae/utils/json_helpers.py +7 -13
- {xgae-0.1.8 → xgae-0.1.9}/src/xgae/utils/llm_client.py +19 -8
- {xgae-0.1.8 → xgae-0.1.9}/src/xgae/utils/misc.py +3 -1
- {xgae-0.1.8 → xgae-0.1.9}/src/xgae/utils/setup_env.py +30 -27
- {xgae-0.1.8 → xgae-0.1.9}/src/xgae/utils/xml_tool_parser.py +4 -80
- {xgae-0.1.8 → xgae-0.1.9}/templates/example_user_prompt.txt +2 -2
- {xgae-0.1.8 → xgae-0.1.9}/test/test_langfuse.py +3 -1
- {xgae-0.1.8 → xgae-0.1.9}/uv.lock +1 -1
- {xgae-0.1.8 → xgae-0.1.9}/.idea/.gitignore +0 -0
- {xgae-0.1.8 → xgae-0.1.9}/.idea/inspectionProfiles/Project_Default.xml +0 -0
- {xgae-0.1.8 → xgae-0.1.9}/.idea/inspectionProfiles/profiles_settings.xml +0 -0
- {xgae-0.1.8 → xgae-0.1.9}/.idea/misc.xml +0 -0
- {xgae-0.1.8 → xgae-0.1.9}/.idea/modules.xml +0 -0
- {xgae-0.1.8 → xgae-0.1.9}/.idea/vcs.xml +0 -0
- {xgae-0.1.8 → xgae-0.1.9}/.idea/xgae.iml +0 -0
- {xgae-0.1.8 → xgae-0.1.9}/.python-version +0 -0
- {xgae-0.1.8 → xgae-0.1.9}/README.md +0 -0
- {xgae-0.1.8 → xgae-0.1.9}/mcpservers/custom_servers.json +0 -0
- {xgae-0.1.8 → xgae-0.1.9}/mcpservers/xga_server.json +0 -0
- {xgae-0.1.8 → xgae-0.1.9}/mcpservers/xga_server_sse.json +0 -0
- {xgae-0.1.8 → xgae-0.1.9}/src/xgae/__init__.py +0 -0
- {xgae-0.1.8 → xgae-0.1.9}/src/xgae/engine/responser/stream_responser.py +0 -0
- {xgae-0.1.8 → xgae-0.1.9}/templates/custom_tool_prompt_template.txt +0 -0
- {xgae-0.1.8 → xgae-0.1.9}/templates/gemini_system_prompt_template.txt +0 -0
- {xgae-0.1.8 → xgae-0.1.9}/templates/general_tool_prompt_template.txt +0 -0
- {xgae-0.1.8 → xgae-0.1.9}/templates/system_prompt_response_sample.txt +0 -0
- {xgae-0.1.8 → xgae-0.1.9}/templates/system_prompt_template.txt +0 -0
- {xgae-0.1.8 → xgae-0.1.9}/test/test_litellm_langfuse.py +0 -0
{xgae-0.1.8 → xgae-0.1.9}/.env
RENAMED
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"Python.message_tools_app.executor": "Run",
|
|
34
34
|
"Python.responser_base.executor": "Run",
|
|
35
35
|
"Python.run_engine_with_human_in_loop.executor": "Run",
|
|
36
|
+
"Python.run_human_in_loop.executor": "Run",
|
|
36
37
|
"Python.run_simple.executor": "Run",
|
|
37
38
|
"Python.run_task_engine.executor": "Run",
|
|
38
39
|
"Python.run_user_prompt.executor": "Run",
|
|
@@ -58,6 +59,7 @@
|
|
|
58
59
|
}]]></component>
|
|
59
60
|
<component name="RecentsManager">
|
|
60
61
|
<key name="MoveFile.RECENT_KEYS">
|
|
62
|
+
<recent name="$PROJECT_DIR$/src/examples/engine" />
|
|
61
63
|
<recent name="$PROJECT_DIR$/src/xgae/engine/responser" />
|
|
62
64
|
</key>
|
|
63
65
|
</component>
|
|
@@ -164,7 +166,7 @@
|
|
|
164
166
|
<workItem from="1755737435202" duration="48139000" />
|
|
165
167
|
<workItem from="1756044658912" duration="1248000" />
|
|
166
168
|
<workItem from="1756082326044" duration="23657000" />
|
|
167
|
-
<workItem from="1756168626188" duration="
|
|
169
|
+
<workItem from="1756168626188" duration="45746000" />
|
|
168
170
|
</task>
|
|
169
171
|
<servers />
|
|
170
172
|
</component>
|
|
@@ -185,20 +187,21 @@
|
|
|
185
187
|
<component name="com.intellij.coverage.CoverageDataManagerImpl">
|
|
186
188
|
<SUITE FILE_PATH="coverage/xgae$test_litellm_langfuse.coverage" NAME="test_litellm_langfuse Coverage Results" MODIFIED="1756196476262" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
|
|
187
189
|
<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$" />
|
|
188
|
-
<SUITE FILE_PATH="coverage/xgae$run_simple.coverage" NAME="run_simple Coverage Results" MODIFIED="
|
|
190
|
+
<SUITE FILE_PATH="coverage/xgae$run_simple.coverage" NAME="run_simple Coverage Results" MODIFIED="1756280626870" 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_human_in_loop.coverage" NAME="run_human_in_loop Coverage Results" MODIFIED="1756279131815" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
|
|
189
192
|
<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$" />
|
|
190
193
|
<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$" />
|
|
191
194
|
<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$" />
|
|
192
195
|
<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$" />
|
|
193
196
|
<SUITE FILE_PATH="coverage/xgae$test_langfuse.coverage" NAME="test_langfuse Coverage Results" MODIFIED="1756196410142" 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$run_task_engine.coverage" NAME="run_task_engine Coverage Results" MODIFIED="
|
|
197
|
+
<SUITE FILE_PATH="coverage/xgae$run_task_engine.coverage" NAME="run_task_engine Coverage Results" MODIFIED="1756280748615" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
|
|
195
198
|
<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$" />
|
|
196
|
-
<SUITE FILE_PATH="coverage/xgae$mcp_tool_box.coverage" NAME="mcp_tool_box Coverage Results" MODIFIED="
|
|
199
|
+
<SUITE FILE_PATH="coverage/xgae$mcp_tool_box.coverage" NAME="mcp_tool_box Coverage Results" MODIFIED="1756274403389" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
|
|
197
200
|
<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$" />
|
|
198
|
-
<SUITE FILE_PATH="coverage/xgae$setup_env.coverage" NAME="setup_env Coverage Results" MODIFIED="
|
|
199
|
-
<SUITE FILE_PATH="coverage/xgae$run_user_prompt.coverage" NAME="run_user_prompt Coverage Results" MODIFIED="
|
|
201
|
+
<SUITE FILE_PATH="coverage/xgae$setup_env.coverage" NAME="setup_env Coverage Results" MODIFIED="1756273791782" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
|
|
202
|
+
<SUITE FILE_PATH="coverage/xgae$run_user_prompt.coverage" NAME="run_user_prompt Coverage Results" MODIFIED="1756279512361" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
|
|
200
203
|
<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$" />
|
|
201
|
-
<SUITE FILE_PATH="coverage/xgae$llm_client.coverage" NAME="llm_client Coverage Results" MODIFIED="
|
|
202
|
-
<SUITE FILE_PATH="coverage/xgae$task_engine.coverage" NAME="task_engine Coverage Results" MODIFIED="
|
|
204
|
+
<SUITE FILE_PATH="coverage/xgae$llm_client.coverage" NAME="llm_client Coverage Results" MODIFIED="1756261767854" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
|
|
205
|
+
<SUITE FILE_PATH="coverage/xgae$task_engine.coverage" NAME="task_engine Coverage Results" MODIFIED="1756269353871" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
|
|
203
206
|
</component>
|
|
204
207
|
</project>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "xgae"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.9"
|
|
4
4
|
description = "Extreme General Agent Engine"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.13"
|
|
@@ -22,3 +22,4 @@ exclude = ["log/*"]
|
|
|
22
22
|
|
|
23
23
|
[project.scripts]
|
|
24
24
|
xgae-tools = "xgae.tools.without_general_tools_app:main"
|
|
25
|
+
custom_fault_tools = "examples.tools.custom_fault_tools_app:main"
|
|
@@ -5,10 +5,13 @@ from xgae.engine.task_engine import XGATaskEngine
|
|
|
5
5
|
from xgae.utils.llm_client import LLMConfig
|
|
6
6
|
from xgae.utils.misc import read_file
|
|
7
7
|
|
|
8
|
+
from xgae.utils.setup_env import setup_langfuse
|
|
8
9
|
|
|
9
10
|
async def main() -> None:
|
|
11
|
+
# Before Run Exec: uv run custom_fault_tools
|
|
10
12
|
tool_box = XGAMcpToolBox(custom_mcp_server_file="mcpservers/custom_servers.json")
|
|
11
13
|
system_prompt = read_file("templates/example_user_prompt.txt")
|
|
14
|
+
|
|
12
15
|
engine = XGATaskEngine(tool_box=tool_box,
|
|
13
16
|
general_tools=[],
|
|
14
17
|
custom_tools=["*"],
|
|
@@ -16,14 +19,24 @@ async def main() -> None:
|
|
|
16
19
|
system_prompt=system_prompt,
|
|
17
20
|
max_auto_run=8)
|
|
18
21
|
|
|
22
|
+
langfuse = setup_langfuse()
|
|
23
|
+
# Two task run in same langfuse trace
|
|
24
|
+
trace_id = langfuse.trace(name="xgae_example_run_human_in_loop").trace_id
|
|
25
|
+
|
|
19
26
|
user_input = "locate fault and solution"
|
|
20
|
-
final_result = await engine.run_task_with_final_answer(
|
|
27
|
+
final_result = await engine.run_task_with_final_answer(
|
|
28
|
+
task_message={"role": "user", "content": user_input},
|
|
29
|
+
trace_id=trace_id
|
|
30
|
+
)
|
|
21
31
|
print("FINAL RESULT:", final_result)
|
|
22
32
|
|
|
23
33
|
if final_result["type"] == "ask":
|
|
24
34
|
print("====== Wait for user input ... ======")
|
|
25
35
|
user_input = "ip=10.0.1.1"
|
|
26
|
-
final_result = await engine.run_task_with_final_answer(
|
|
36
|
+
final_result = await engine.run_task_with_final_answer(
|
|
37
|
+
task_message={"role": "user", "content": user_input},
|
|
38
|
+
trace_id=trace_id
|
|
39
|
+
)
|
|
27
40
|
print("FINAL RESULT:", final_result)
|
|
28
41
|
|
|
29
42
|
asyncio.run(main())
|
|
@@ -6,7 +6,11 @@ from xgae.utils.llm_client import LLMConfig
|
|
|
6
6
|
|
|
7
7
|
async def main() -> None:
|
|
8
8
|
engine = XGATaskEngine(llm_config=LLMConfig(stream=False), max_auto_run=1)
|
|
9
|
-
|
|
9
|
+
|
|
10
|
+
final_result = await engine.run_task_with_final_answer(
|
|
11
|
+
task_message={"role": "user", "content": "1+1"}
|
|
12
|
+
)
|
|
13
|
+
|
|
10
14
|
print("FINAL RESULT:", final_result)
|
|
11
15
|
|
|
12
16
|
asyncio.run(main())
|
|
@@ -7,8 +7,10 @@ from xgae.utils.misc import read_file
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
async def main() -> None:
|
|
10
|
+
# Before Run Exec: uv run custom_fault_tools
|
|
10
11
|
tool_box = XGAMcpToolBox(custom_mcp_server_file="mcpservers/custom_servers.json")
|
|
11
12
|
system_prompt = read_file("templates/example_user_prompt.txt")
|
|
13
|
+
|
|
12
14
|
engine = XGATaskEngine(tool_box=tool_box,
|
|
13
15
|
general_tools=[],
|
|
14
16
|
custom_tools=["*"],
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
from typing import Annotated
|
|
5
|
+
from typing import Dict, Any
|
|
6
|
+
from pydantic import Field
|
|
7
|
+
|
|
8
|
+
from mcp.server.fastmcp import FastMCP
|
|
9
|
+
|
|
10
|
+
mcp = FastMCP(name="Fault Location Tools")
|
|
11
|
+
alarm_type = 0
|
|
12
|
+
|
|
13
|
+
@mcp.tool(
|
|
14
|
+
description="Get Alarm Object",
|
|
15
|
+
)
|
|
16
|
+
def get_alarm(ip: Annotated[str, Field(description="Alarm Object IP Address")]) -> Dict[str, str]:
|
|
17
|
+
logging.info(f"get_alarm: ip={ip}")
|
|
18
|
+
return {"alarmId":"alm0123", "objId": "obj567"}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@mcp.tool(
|
|
22
|
+
description="Get alarm type, return result enum: 1: Business Type Alarm; 2: Equipment Type Alarm; 3: Middleware Type Alarm",
|
|
23
|
+
)
|
|
24
|
+
def get_alarm_type(alarm_id: Annotated[str, Field(description="Alarm Object Id")]) -> str:
|
|
25
|
+
logging.info(f"get_alarm_type: alarm_id={alarm_id}, alarm_type={alarm_type}")
|
|
26
|
+
return f"{alarm_type}"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@mcp.tool(
|
|
30
|
+
description="Locate Business Type Fault",
|
|
31
|
+
)
|
|
32
|
+
def get_busi_fault(obj_id: Annotated[str, Field(description="Alarm Object Id")]) -> Dict[str, Any]:
|
|
33
|
+
logging.info(f"get_busi_fault: obj_id={obj_id}")
|
|
34
|
+
return {"fault_code": "F01", "fault_desc": "Business Recharge Fault"}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@mcp.tool(
|
|
38
|
+
description="Locate Equipment Type Fault",
|
|
39
|
+
)
|
|
40
|
+
def get_equip_fault(obj_id: Annotated[str, Field(description="Alarm Object Id")]) -> Dict[str, Any]:
|
|
41
|
+
logging.info(f"get_equip_fault: obj_id={obj_id}")
|
|
42
|
+
return {"fault_code": "F02", "fault_desc": "Host Disk Fault"}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@mcp.tool(
|
|
46
|
+
description="Locate Middleware Type Fault",
|
|
47
|
+
)
|
|
48
|
+
def get_middle_fault(obj_id: Annotated[str, Field(description="Alarm Object Id")]) -> Dict[str, Any]:
|
|
49
|
+
logging.info(f"get_middle_fault: obj_id={obj_id}")
|
|
50
|
+
return {"fault_code": "F03", "fault_desc": "Redis Fault"}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@mcp.tool(
|
|
54
|
+
description="Get Business Type Fault Solution and Cause",
|
|
55
|
+
)
|
|
56
|
+
async def get_busi_fault_cause(fault_code: Annotated[str, Field(description="Fault Code")]) -> str:
|
|
57
|
+
logging.info(f"get_busi_fault_cause: faultCode={fault_code}")
|
|
58
|
+
|
|
59
|
+
fault_cause = ""
|
|
60
|
+
if (fault_code == 'F01'):
|
|
61
|
+
fault_cause = "Business Recharge Fault, Fault Cause is 'Phone Recharge Application Crash' ,Solution is 'Restart Phone Recharge Application'"
|
|
62
|
+
else:
|
|
63
|
+
fault_cause = f"FaultCode '{fault_code}' is not Business Type"
|
|
64
|
+
|
|
65
|
+
return fault_cause
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@mcp.tool(
|
|
69
|
+
description="Get Equipment Type Fault Solution and Cause",
|
|
70
|
+
)
|
|
71
|
+
async def get_equip_fault_cause(fault_code: Annotated[str, Field(description="Fault Code")]) -> str:
|
|
72
|
+
logging.info(f"get_equip_fault_cause: faultCode={fault_code}")
|
|
73
|
+
|
|
74
|
+
fault_cause = ""
|
|
75
|
+
if (fault_code == 'F01'):
|
|
76
|
+
fault_cause = "Host Fault, Fault Cause is 'Host Disk is Damaged' ,Solution is 'Change Host Disk'"
|
|
77
|
+
else:
|
|
78
|
+
fault_cause = f"FaultCode '{fault_code}' is not Equipment Type"
|
|
79
|
+
|
|
80
|
+
return fault_cause
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@click.command()
|
|
84
|
+
@click.option("--transport", type=click.Choice(["stdio", "sse"]), default="sse", help="Transport type")
|
|
85
|
+
@click.option("--host", default="0.0.0.0", help="Host to listen on for SSE")
|
|
86
|
+
@click.option("--port", default=17070, help="Port to listen on for SSE")
|
|
87
|
+
@click.option("--alarmtype", default=1, help="AlarmType Set")
|
|
88
|
+
def main(transport: str, host: str, port: int, alarmtype:int):
|
|
89
|
+
if transport != "stdio":
|
|
90
|
+
from xgae.utils.setup_env import setup_logging
|
|
91
|
+
setup_logging()
|
|
92
|
+
logging.info("=" * 20 + f" Custom Fault Tools Sever Started in {transport} mode " + "=" * 20)
|
|
93
|
+
|
|
94
|
+
global alarm_type
|
|
95
|
+
alarm_type = alarmtype
|
|
96
|
+
|
|
97
|
+
mcp.settings.host = host
|
|
98
|
+
mcp.settings.port = port
|
|
99
|
+
|
|
100
|
+
mcp.run(transport=transport)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
if __name__ == '__main__':
|
|
104
|
+
main()
|
|
@@ -7,7 +7,7 @@ class XGAError(Exception):
|
|
|
7
7
|
pass
|
|
8
8
|
|
|
9
9
|
XGAMsgStatusType = Literal["error", "finish", "tool_error", "tool_started", "tool_completed", "tool_failed", "thread_run_start", "thread_run_end", "assistant_response_start", "assistant_response_end"]
|
|
10
|
-
XGAResponseMsgType = Literal["user", "status", "tool", "assistant"]
|
|
10
|
+
XGAResponseMsgType = Literal["user", "status", "tool", "assistant", "assistant_complete"]
|
|
11
11
|
|
|
12
12
|
class XGAResponseMessage(TypedDict, total=False):
|
|
13
13
|
message_id: str
|
|
@@ -190,11 +190,16 @@ class XGAMcpToolBox(XGAToolBox):
|
|
|
190
190
|
if __name__ == "__main__":
|
|
191
191
|
import asyncio
|
|
192
192
|
from dataclasses import asdict
|
|
193
|
+
from xgae.utils.setup_env import setup_logging
|
|
194
|
+
|
|
195
|
+
setup_logging()
|
|
193
196
|
|
|
194
197
|
async def main():
|
|
195
|
-
|
|
198
|
+
## Before Run Exec: uv run custom_fault_tools
|
|
196
199
|
mcp_tool_box = XGAMcpToolBox(custom_mcp_server_file="mcpservers/custom_servers.json")
|
|
197
200
|
#mcp_tool_box = XGAMcpToolBox()
|
|
201
|
+
|
|
202
|
+
task_id = "task1"
|
|
198
203
|
await mcp_tool_box.load_mcp_tools_schema()
|
|
199
204
|
await mcp_tool_box.creat_task_tool_box(task_id=task_id, general_tools=["*"], custom_tools=["bomc_fault.*"])
|
|
200
205
|
tool_schemas = mcp_tool_box.get_task_tool_schemas(task_id, "general_tool")
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
|
|
3
3
|
from typing import List, Dict, Any, AsyncGenerator, override,Optional
|
|
4
|
+
from xgae.utils.json_helpers import format_for_yield
|
|
4
5
|
|
|
5
6
|
from xgae.engine.responser.responser_base import TaskResponseProcessor, TaskResponserContext, TaskRunContinuousState
|
|
6
|
-
|
|
7
|
+
|
|
7
8
|
|
|
8
9
|
class NonStreamTaskResponser(TaskResponseProcessor):
|
|
9
10
|
def __init__(self, response_context: TaskResponserContext):
|
|
@@ -47,8 +48,8 @@ class NonStreamTaskResponser(TaskResponseProcessor):
|
|
|
47
48
|
else:
|
|
48
49
|
logging.warning(f"NonStreamTask:LLM response_message is empty")
|
|
49
50
|
|
|
50
|
-
message_data = {"role": "assistant", "content": llm_content
|
|
51
|
-
assistant_msg = self.add_response_message(type="
|
|
51
|
+
message_data = {"role": "assistant", "content": llm_content} # index=-1, full llm_content
|
|
52
|
+
assistant_msg = self.add_response_message(type="assistant_complete", content=message_data, is_llm_message=True)
|
|
52
53
|
yield assistant_msg
|
|
53
54
|
|
|
54
55
|
tool_calls_to_execute = [item['tool_call'] for item in parsed_xml_data]
|
|
@@ -6,11 +6,12 @@ from abc import ABC, abstractmethod
|
|
|
6
6
|
from dataclasses import dataclass
|
|
7
7
|
from typing import List, Dict, Any, Optional, Tuple, Union, Literal, Callable, TypedDict, AsyncGenerator
|
|
8
8
|
|
|
9
|
+
from xgae.utils.json_helpers import safe_json_parse
|
|
10
|
+
from xgae.utils.xml_tool_parser import XMLToolParser
|
|
11
|
+
|
|
9
12
|
from xgae.engine.engine_base import XGAToolResult, XGAToolBox
|
|
10
|
-
from xgae.
|
|
13
|
+
from xgae.engine.task_langfuse import XGATaskLangFuse
|
|
11
14
|
|
|
12
|
-
from xgae.utils.json_helpers import safe_json_parse, format_for_yield
|
|
13
|
-
from xgae.utils.xml_tool_parser import XMLToolParser
|
|
14
15
|
|
|
15
16
|
# Type alias for XML result adding strategy
|
|
16
17
|
XmlAddingStrategy = Literal["user_message", "assistant_message", "inline_edit"]
|
|
@@ -23,14 +24,13 @@ class TaskResponserContext(TypedDict, total=False):
|
|
|
23
24
|
task_id: str
|
|
24
25
|
task_run_id: str
|
|
25
26
|
task_no: int
|
|
26
|
-
trace_id: str
|
|
27
|
-
root_span_id: str
|
|
28
27
|
model_name: str
|
|
29
28
|
max_xml_tool_calls: int # LLM generate max_xml_tool limit, 0 is no limit
|
|
30
|
-
add_response_msg_func: Callable
|
|
31
|
-
tool_box: XGAToolBox
|
|
32
29
|
tool_execution_strategy: ToolExecutionStrategy
|
|
33
30
|
xml_adding_strategy: XmlAddingStrategy
|
|
31
|
+
add_response_msg_func: Callable
|
|
32
|
+
tool_box: XGAToolBox
|
|
33
|
+
task_langfuse: XGATaskLangFuse
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
class TaskRunContinuousState(TypedDict, total=False):
|
|
@@ -63,14 +63,14 @@ class TaskResponseProcessor(ABC):
|
|
|
63
63
|
self.xml_adding_strategy = self.response_context.get("xml_adding_strategy", "user_message")
|
|
64
64
|
self.max_xml_tool_calls = self.response_context.get("max_xml_tool_calls", 0)
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
task_langfuse = response_context.get("task_langfuse")
|
|
67
|
+
self.root_span = task_langfuse.root_span
|
|
67
68
|
self.add_response_message = response_context.get("add_response_msg_func")
|
|
68
69
|
|
|
69
70
|
self.tool_box = response_context.get("tool_box")
|
|
70
71
|
self.xml_parser = XMLToolParser()
|
|
71
72
|
|
|
72
73
|
|
|
73
|
-
|
|
74
74
|
@abstractmethod
|
|
75
75
|
async def process_response(self,
|
|
76
76
|
llm_response: AsyncGenerator,
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
import logging
|
|
3
2
|
import json
|
|
4
3
|
import os
|
|
@@ -6,18 +5,15 @@ import os
|
|
|
6
5
|
from typing import List, Any, Dict, Optional, AsyncGenerator, Union, Literal
|
|
7
6
|
from uuid import uuid4
|
|
8
7
|
|
|
9
|
-
from xgae.engine.responser.responser_base import TaskResponserContext, TaskResponseProcessor, TaskRunContinuousState
|
|
10
|
-
from xgae.engine.engine_base import XGAResponseMsgType, XGAResponseMessage, XGAToolBox, XGATaskResult
|
|
11
|
-
|
|
12
8
|
from xgae.utils import handle_error
|
|
13
|
-
from xgae.utils.
|
|
14
|
-
|
|
15
|
-
from xgae.utils.llm_client import LLMClient, LLMConfig, LangfuseMetadata
|
|
16
|
-
|
|
9
|
+
from xgae.utils.llm_client import LLMClient, LLMConfig
|
|
17
10
|
from xgae.utils.json_helpers import format_for_yield
|
|
11
|
+
|
|
12
|
+
from xgae.engine.engine_base import XGAResponseMsgType, XGAResponseMessage, XGAToolBox, XGATaskResult
|
|
13
|
+
from xgae.engine.task_langfuse import XGATaskLangFuse
|
|
18
14
|
from xgae.engine.prompt_builder import XGAPromptBuilder
|
|
19
15
|
from xgae.engine.mcp_tool_box import XGAMcpToolBox
|
|
20
|
-
|
|
16
|
+
from xgae.engine.responser.responser_base import TaskResponserContext, TaskResponseProcessor, TaskRunContinuousState
|
|
21
17
|
|
|
22
18
|
class XGATaskEngine:
|
|
23
19
|
def __init__(self,
|
|
@@ -42,6 +38,7 @@ class XGATaskEngine:
|
|
|
42
38
|
|
|
43
39
|
self.prompt_builder = prompt_builder or XGAPromptBuilder(system_prompt)
|
|
44
40
|
self.tool_box: XGAToolBox = tool_box or XGAMcpToolBox()
|
|
41
|
+
self.task_langfuse: XGATaskLangFuse = None
|
|
45
42
|
|
|
46
43
|
self.general_tools:List[str] = general_tools
|
|
47
44
|
self.custom_tools:List[str] = custom_tools
|
|
@@ -53,18 +50,18 @@ class XGATaskEngine:
|
|
|
53
50
|
|
|
54
51
|
self.task_no = -1
|
|
55
52
|
self.task_run_id :str = None
|
|
56
|
-
|
|
57
53
|
self.task_prompt :str = None
|
|
58
|
-
|
|
59
|
-
self.root_span_id :str = None
|
|
60
|
-
self.root_span_name :str = None
|
|
54
|
+
|
|
61
55
|
|
|
62
56
|
async def run_task_with_final_answer(self,
|
|
63
57
|
task_message: Dict[str, Any],
|
|
64
58
|
trace_id: Optional[str] = None) -> XGATaskResult:
|
|
65
59
|
final_result:XGATaskResult = None
|
|
66
60
|
try:
|
|
67
|
-
self.
|
|
61
|
+
await self._init_task()
|
|
62
|
+
|
|
63
|
+
self.task_langfuse.start_root_span("run_task_with_final_answer", task_message, trace_id)
|
|
64
|
+
|
|
68
65
|
chunks = []
|
|
69
66
|
async for chunk in self.run_task(task_message=task_message, trace_id=trace_id):
|
|
70
67
|
chunks.append(chunk)
|
|
@@ -76,7 +73,7 @@ class XGATaskEngine:
|
|
|
76
73
|
|
|
77
74
|
return final_result
|
|
78
75
|
finally:
|
|
79
|
-
self.
|
|
76
|
+
self.task_langfuse.end_root_span("run_task_with_final_answer", final_result)
|
|
80
77
|
|
|
81
78
|
|
|
82
79
|
async def run_task(self,
|
|
@@ -84,7 +81,8 @@ class XGATaskEngine:
|
|
|
84
81
|
trace_id: Optional[str] = None) -> AsyncGenerator[Dict[str, Any], None]:
|
|
85
82
|
try:
|
|
86
83
|
await self._init_task()
|
|
87
|
-
|
|
84
|
+
|
|
85
|
+
self.task_langfuse.start_root_span("run_task", task_message, trace_id)
|
|
88
86
|
|
|
89
87
|
self.add_response_message(type="user", content=task_message, is_llm_message=True)
|
|
90
88
|
|
|
@@ -92,33 +90,36 @@ class XGATaskEngine:
|
|
|
92
90
|
yield chunk
|
|
93
91
|
finally:
|
|
94
92
|
await self.tool_box.destroy_task_tool_box(self.task_id)
|
|
95
|
-
self.
|
|
96
|
-
|
|
93
|
+
self.task_langfuse.end_root_span("run_task")
|
|
94
|
+
self.task_run_id = None
|
|
97
95
|
|
|
98
96
|
async def _init_task(self) -> None:
|
|
99
|
-
|
|
100
|
-
|
|
97
|
+
if self.task_run_id is None:
|
|
98
|
+
self.task_no = self.task_no + 1
|
|
99
|
+
self.task_run_id = f"{self.task_id}[{self.task_no}]"
|
|
100
|
+
|
|
101
|
+
self.task_langfuse =self._create_task_langfuse()
|
|
101
102
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
103
|
+
general_tools = self.general_tools or ["complete", "ask"]
|
|
104
|
+
if "*" not in general_tools:
|
|
105
|
+
if "complete" not in general_tools:
|
|
106
|
+
general_tools.append("complete")
|
|
107
|
+
elif "ask" not in general_tools:
|
|
108
|
+
general_tools.append("ask")
|
|
108
109
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
custom_tools = self.custom_tools or []
|
|
111
|
+
if isinstance(self.tool_box, XGAMcpToolBox):
|
|
112
|
+
await self.tool_box.load_mcp_tools_schema()
|
|
112
113
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
await self.tool_box.creat_task_tool_box(self.task_id, general_tools, custom_tools)
|
|
115
|
+
general_tool_schemas = self.tool_box.get_task_tool_schemas(self.task_id, "general_tool")
|
|
116
|
+
custom_tool_schemas = self.tool_box.get_task_tool_schemas(self.task_id, "custom_tool")
|
|
116
117
|
|
|
117
|
-
|
|
118
|
+
self.task_prompt = self.prompt_builder.build_task_prompt(self.model_name, general_tool_schemas, custom_tool_schemas)
|
|
118
119
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
120
|
+
logging.info("*" * 30 + f" XGATaskEngine Task'{self.task_id}' Initialized " + "*" * 30)
|
|
121
|
+
logging.info(f"model_name={self.model_name}, is_stream={self.is_stream}")
|
|
122
|
+
logging.info(f"general_tools={general_tools}, custom_tools={custom_tools}")
|
|
122
123
|
|
|
123
124
|
|
|
124
125
|
async def _run_task_auto(self) -> AsyncGenerator[Dict[str, Any], None]:
|
|
@@ -191,7 +192,8 @@ class XGATaskEngine:
|
|
|
191
192
|
llm_messages.append(temp_assistant_message)
|
|
192
193
|
|
|
193
194
|
llm_count = continuous_state.get("auto_continue_count")
|
|
194
|
-
langfuse_metadata = self.
|
|
195
|
+
langfuse_metadata = self.task_langfuse.create_llm_langfuse_meta(llm_count)
|
|
196
|
+
|
|
195
197
|
llm_response = await self.llm_client.create_completion(llm_messages, langfuse_metadata)
|
|
196
198
|
response_processor = self._create_response_processer()
|
|
197
199
|
|
|
@@ -236,7 +238,7 @@ class XGATaskEngine:
|
|
|
236
238
|
result_type = "answer" if success else "error"
|
|
237
239
|
result_content = f"Task execute '{tool_name}' {result_type}: {output}"
|
|
238
240
|
final_result = XGATaskResult(type=result_type, content=result_content)
|
|
239
|
-
elif chunk_type == "
|
|
241
|
+
elif chunk_type == "assistant_complete" and finish_reason == 'stop':
|
|
240
242
|
assis_content = chunk.get('content', {})
|
|
241
243
|
result_content = assis_content.get("content", "LLM output is empty")
|
|
242
244
|
final_result = XGATaskResult(type="answer", content=result_content)
|
|
@@ -258,7 +260,7 @@ class XGATaskEngine:
|
|
|
258
260
|
metadata = metadata or {}
|
|
259
261
|
metadata["task_id"] = self.task_id
|
|
260
262
|
metadata["task_run_id"] = self.task_run_id
|
|
261
|
-
metadata["trace_id"] = self.trace_id
|
|
263
|
+
metadata["trace_id"] = self.task_langfuse.trace_id
|
|
262
264
|
metadata["session_id"] = self.session_id
|
|
263
265
|
metadata["agent_id"] = self.agent_id
|
|
264
266
|
|
|
@@ -295,41 +297,6 @@ class XGATaskEngine:
|
|
|
295
297
|
|
|
296
298
|
return response_llm_contents
|
|
297
299
|
|
|
298
|
-
|
|
299
|
-
def _create_llm_langfuse_meta(self, llm_count:int)-> LangfuseMetadata:
|
|
300
|
-
generation_name = f"xga_task_engine_llm_completion[{self.task_no}]({llm_count})"
|
|
301
|
-
generation_id = f"{self.task_run_id}({llm_count})"
|
|
302
|
-
return LangfuseMetadata(
|
|
303
|
-
generation_name=generation_name,
|
|
304
|
-
generation_id=generation_id,
|
|
305
|
-
existing_trace_id=self.trace_id,
|
|
306
|
-
session_id=self.session_id,
|
|
307
|
-
)
|
|
308
|
-
|
|
309
|
-
def _init_langfuse(self,
|
|
310
|
-
root_span_name: str,
|
|
311
|
-
task_message: Dict[str, Any],
|
|
312
|
-
trace_id: Optional[str] = None):
|
|
313
|
-
|
|
314
|
-
if self.root_span_id is None:
|
|
315
|
-
trace = None
|
|
316
|
-
if trace_id:
|
|
317
|
-
self.trace_id = trace_id
|
|
318
|
-
trace = langfuse.trace(id=trace_id)
|
|
319
|
-
else:
|
|
320
|
-
trace = langfuse.trace(name="xga_task_engine")
|
|
321
|
-
self.trace_id = trace.id
|
|
322
|
-
|
|
323
|
-
span = trace.span(name=root_span_name, input=task_message,metadata={"task_id": self.task_id})
|
|
324
|
-
self.root_span_id = span.id
|
|
325
|
-
self.root_span_name = root_span_name
|
|
326
|
-
|
|
327
|
-
def _end_langfuse(self, root_span_name:str, output: Optional[XGATaskResult]=None):
|
|
328
|
-
if self.root_span_id and self.root_span_name == root_span_name:
|
|
329
|
-
langfuse.span(trace_id=self.trace_id, id=self.root_span_id).end(output=output)
|
|
330
|
-
self.root_span_id = None
|
|
331
|
-
self.root_span_name = None
|
|
332
|
-
|
|
333
300
|
def _create_response_processer(self) -> TaskResponseProcessor:
|
|
334
301
|
response_context = self._create_response_context()
|
|
335
302
|
is_stream = response_context.get("is_stream", False)
|
|
@@ -346,18 +313,21 @@ class XGATaskEngine:
|
|
|
346
313
|
"task_id": self.task_id,
|
|
347
314
|
"task_run_id": self.task_run_id,
|
|
348
315
|
"task_no": self.task_no,
|
|
349
|
-
"trace_id": self.trace_id,
|
|
350
|
-
"root_span_id": self.root_span_id,
|
|
351
316
|
"model_name": self.model_name,
|
|
352
317
|
"max_xml_tool_calls": 0,
|
|
318
|
+
"tool_execution_strategy": "parallel" if self.tool_exec_parallel else "sequential", # ,
|
|
319
|
+
"xml_adding_strategy": "user_message",
|
|
353
320
|
"add_response_msg_func": self.add_response_message,
|
|
354
321
|
"tool_box": self.tool_box,
|
|
355
|
-
"
|
|
356
|
-
"xml_adding_strategy": "user_message",
|
|
322
|
+
"task_langfuse": self.task_langfuse,
|
|
357
323
|
}
|
|
358
324
|
return response_context
|
|
359
325
|
|
|
360
326
|
|
|
327
|
+
def _create_task_langfuse(self)-> XGATaskLangFuse:
|
|
328
|
+
return XGATaskLangFuse(self.session_id, self.task_id, self.task_run_id, self.task_no, self.agent_id)
|
|
329
|
+
|
|
330
|
+
|
|
361
331
|
def _logging_reponse_chunk(self, chunk):
|
|
362
332
|
chunk_type = chunk.get('type')
|
|
363
333
|
prefix = ""
|
|
@@ -380,6 +350,7 @@ if __name__ == "__main__":
|
|
|
380
350
|
from xgae.utils.misc import read_file
|
|
381
351
|
|
|
382
352
|
async def main():
|
|
353
|
+
# Before Run Exec: uv run custom_fault_tools
|
|
383
354
|
tool_box = XGAMcpToolBox(custom_mcp_server_file="mcpservers/custom_servers.json")
|
|
384
355
|
system_prompt = read_file("templates/example_user_prompt.txt")
|
|
385
356
|
engine = XGATaskEngine(tool_box=tool_box,
|
|
@@ -387,7 +358,9 @@ if __name__ == "__main__":
|
|
|
387
358
|
custom_tools=["*"],
|
|
388
359
|
llm_config=LLMConfig(stream=False),
|
|
389
360
|
system_prompt=system_prompt,
|
|
390
|
-
max_auto_run=8
|
|
361
|
+
max_auto_run=8,
|
|
362
|
+
session_id="session_1",
|
|
363
|
+
agent_id="agent_1",)
|
|
391
364
|
|
|
392
365
|
final_result = await engine.run_task_with_final_answer(task_message={"role": "user",
|
|
393
366
|
"content": "locate 10.0.0.1 fault and solution"})
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
|
|
2
|
+
from typing import Any, Dict, Optional
|
|
3
|
+
from langfuse import Langfuse
|
|
4
|
+
|
|
5
|
+
from xgae.utils.setup_env import setup_langfuse, setup_env_logging
|
|
6
|
+
from xgae.utils.llm_client import LangfuseMetadata
|
|
7
|
+
from xgae.engine.engine_base import XGATaskResult
|
|
8
|
+
|
|
9
|
+
setup_env_logging()
|
|
10
|
+
langfuse:Langfuse = setup_langfuse()
|
|
11
|
+
|
|
12
|
+
class XGATaskLangFuse:
|
|
13
|
+
def __init__(self,
|
|
14
|
+
session_id: str,
|
|
15
|
+
task_id:str,
|
|
16
|
+
task_run_id: str,
|
|
17
|
+
task_no: int,
|
|
18
|
+
agent_id: str) -> None:
|
|
19
|
+
self.session_id = session_id
|
|
20
|
+
self.task_id = task_id
|
|
21
|
+
self.task_run_id = task_run_id
|
|
22
|
+
self.task_no = task_no
|
|
23
|
+
self.agent_id = agent_id
|
|
24
|
+
|
|
25
|
+
self.trace_id = None
|
|
26
|
+
self.root_span = None
|
|
27
|
+
self.root_span_name = None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def start_root_span(self,
|
|
31
|
+
root_span_name: str,
|
|
32
|
+
task_message: Dict[str, Any],
|
|
33
|
+
trace_id: Optional[str] = None):
|
|
34
|
+
if self.root_span is None:
|
|
35
|
+
trace = None
|
|
36
|
+
if trace_id:
|
|
37
|
+
self.trace_id = trace_id
|
|
38
|
+
trace = langfuse.trace(id=trace_id)
|
|
39
|
+
else:
|
|
40
|
+
trace = langfuse.trace(name="xga_task_engine")
|
|
41
|
+
self.trace_id = trace.id
|
|
42
|
+
|
|
43
|
+
metadata = {"task_id": self.task_id, "session_id": self.session_id, "agent_id": self.agent_id}
|
|
44
|
+
self.root_span = trace.span(id=self.task_run_id, name=root_span_name, input=task_message,metadata=metadata)
|
|
45
|
+
self.root_span_name = root_span_name
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def end_root_span(self, root_span_name:str, output: Optional[XGATaskResult]=None):
|
|
49
|
+
if self.root_span and self.root_span_name == root_span_name:
|
|
50
|
+
self.root_span.end(output=output)
|
|
51
|
+
self.root_span = None
|
|
52
|
+
self.root_span_name = None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def create_llm_langfuse_meta(self, llm_count:int)-> LangfuseMetadata:
|
|
56
|
+
generation_name = f"xga_task_engine_llm_completion[{self.task_no}]({llm_count})"
|
|
57
|
+
generation_id = f"{self.task_run_id}({llm_count})"
|
|
58
|
+
return LangfuseMetadata(
|
|
59
|
+
generation_name=generation_name,
|
|
60
|
+
generation_id=generation_id,
|
|
61
|
+
existing_trace_id=self.trace_id,
|
|
62
|
+
session_id=self.session_id,
|
|
63
|
+
)
|
|
@@ -41,7 +41,7 @@ async def end_task(task_id: str) :
|
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
def main():
|
|
44
|
-
|
|
44
|
+
print("="*20 + " XGAE Message Tools Sever Started in Stdio mode " + "="*20)
|
|
45
45
|
mcp.run(transport="stdio")
|
|
46
46
|
|
|
47
47
|
if __name__ == "__main__":
|
|
@@ -26,8 +26,7 @@ def ensure_dict(value: Union[str, Dict[str, Any], None], default: Dict[str, Any]
|
|
|
26
26
|
Returns:
|
|
27
27
|
A dictionary
|
|
28
28
|
"""
|
|
29
|
-
|
|
30
|
-
default = {}
|
|
29
|
+
default = default or {}
|
|
31
30
|
|
|
32
31
|
if value is None:
|
|
33
32
|
return default
|
|
@@ -64,8 +63,7 @@ def ensure_list(value: Union[str, List[Any], None], default: List[Any] = None) -
|
|
|
64
63
|
Returns:
|
|
65
64
|
A list
|
|
66
65
|
"""
|
|
67
|
-
|
|
68
|
-
default = []
|
|
66
|
+
default = default or []
|
|
69
67
|
|
|
70
68
|
if value is None:
|
|
71
69
|
return default
|
|
@@ -84,7 +82,7 @@ def ensure_list(value: Union[str, List[Any], None], default: List[Any] = None) -
|
|
|
84
82
|
|
|
85
83
|
return default
|
|
86
84
|
|
|
87
|
-
|
|
85
|
+
# @todo if all call value is str, delete useless code
|
|
88
86
|
def safe_json_parse(value: Union[str, Dict, List, Any], default: Any = None) -> Any:
|
|
89
87
|
"""
|
|
90
88
|
Safely parse a value that might be JSON string or already parsed.
|
|
@@ -105,16 +103,13 @@ def safe_json_parse(value: Union[str, Dict, List, Any], default: Any = None) ->
|
|
|
105
103
|
# If it's already a dict or list, return as-is
|
|
106
104
|
if isinstance(value, (dict, list)):
|
|
107
105
|
return value
|
|
108
|
-
|
|
109
|
-
# If it's a string, try to parse it
|
|
106
|
+
|
|
110
107
|
if isinstance(value, str):
|
|
111
108
|
try:
|
|
112
109
|
return json.loads(value)
|
|
113
110
|
except (json.JSONDecodeError, TypeError):
|
|
114
|
-
# If it's not valid JSON, return the string itself
|
|
115
111
|
return value
|
|
116
|
-
|
|
117
|
-
# For any other type, return as-is
|
|
112
|
+
|
|
118
113
|
return value
|
|
119
114
|
|
|
120
115
|
|
|
@@ -137,9 +132,8 @@ def to_json_string(value: Any) -> str:
|
|
|
137
132
|
json.loads(value)
|
|
138
133
|
return value # It's already a JSON string
|
|
139
134
|
except (json.JSONDecodeError, TypeError):
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
135
|
+
pass
|
|
136
|
+
|
|
143
137
|
# For all other types, convert to JSON
|
|
144
138
|
return json.dumps(value)
|
|
145
139
|
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import json
|
|
3
|
-
import logging
|
|
4
3
|
import os
|
|
4
|
+
import logging
|
|
5
5
|
import litellm
|
|
6
6
|
|
|
7
7
|
from typing import Union, Dict, Any, Optional, List, TypedDict
|
|
8
|
-
|
|
9
|
-
from litellm.utils import ModelResponse, CustomStreamWrapper
|
|
10
8
|
from openai import OpenAIError
|
|
9
|
+
from litellm.utils import ModelResponse, CustomStreamWrapper
|
|
11
10
|
|
|
12
|
-
from xgae.utils.setup_env import
|
|
11
|
+
from xgae.utils.setup_env import setup_langfuse
|
|
13
12
|
|
|
14
13
|
class LLMConfig(TypedDict, total=False):
|
|
15
14
|
model: str # Optional Name of the model to use , Override .env LLM_MODEL
|
|
@@ -85,7 +84,8 @@ class LLMClient:
|
|
|
85
84
|
def _init_langfuse():
|
|
86
85
|
if not LLMClient.langfuse_inited:
|
|
87
86
|
LLMClient.langfuse_inited =True
|
|
88
|
-
|
|
87
|
+
env_langfuse = setup_langfuse()
|
|
88
|
+
if env_langfuse and env_langfuse.enabled:
|
|
89
89
|
litellm.success_callback = ["langfuse"]
|
|
90
90
|
litellm.failure_callback = ["langfuse"]
|
|
91
91
|
LLMClient.langfuse_enabled = True
|
|
@@ -95,7 +95,6 @@ class LLMClient:
|
|
|
95
95
|
logging.warning("*** LiteLLM Langfuse is disable !")
|
|
96
96
|
|
|
97
97
|
|
|
98
|
-
|
|
99
98
|
def _prepare_llm_params(self, llm_config_params: Dict[str, Any]) -> Dict[str, Any]:
|
|
100
99
|
prepared_llm_params = llm_config_params.copy()
|
|
101
100
|
|
|
@@ -240,12 +239,24 @@ class LLMClient:
|
|
|
240
239
|
raise LLMError(f"LLM completion failed after {self.max_retries} attempts !")
|
|
241
240
|
|
|
242
241
|
if __name__ == "__main__":
|
|
242
|
+
from xgae.utils.setup_env import setup_logging
|
|
243
|
+
|
|
244
|
+
setup_logging()
|
|
245
|
+
langfuse = setup_langfuse()
|
|
246
|
+
|
|
243
247
|
async def llm_completion():
|
|
244
248
|
llm_client = LLMClient(LLMConfig(stream=False))
|
|
249
|
+
|
|
245
250
|
messages = [{"role": "user", "content": "1+1="}]
|
|
246
251
|
trace_id = langfuse.trace(name = "xgae_litellm_test").trace_id
|
|
247
|
-
|
|
248
|
-
|
|
252
|
+
meta = LangfuseMetadata(
|
|
253
|
+
generation_name="llm_completion_test",
|
|
254
|
+
generation_id="generation_id",
|
|
255
|
+
existing_trace_id=trace_id,
|
|
256
|
+
session_id="session_0",
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
response = await llm_client.create_completion(messages, meta)
|
|
249
260
|
if llm_client.is_stream:
|
|
250
261
|
async for chunk in response:
|
|
251
262
|
choices = chunk.get("choices", [{}])
|
|
@@ -4,6 +4,8 @@ import sys
|
|
|
4
4
|
|
|
5
5
|
from typing import Any, Dict
|
|
6
6
|
|
|
7
|
+
from xgae.utils import handle_error
|
|
8
|
+
|
|
7
9
|
def read_file(file_path: str) -> str:
|
|
8
10
|
if not os.path.exists(file_path):
|
|
9
11
|
logging.error(f"File '{file_path}' not found")
|
|
@@ -31,4 +33,4 @@ def format_file_with_args(file_content:str, args: Dict[str, Any])-> str:
|
|
|
31
33
|
finally:
|
|
32
34
|
sys.stdout = original_stdout
|
|
33
35
|
|
|
34
|
-
return formated
|
|
36
|
+
return formated
|
|
@@ -4,24 +4,14 @@ import os
|
|
|
4
4
|
from dotenv import load_dotenv
|
|
5
5
|
from langfuse import Langfuse
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
from xgae.utils import to_bool
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
log_enable = bool(os.getenv("LOG_ENABLE", True))
|
|
11
|
-
if not log_enable :
|
|
12
|
-
return
|
|
9
|
+
load_dotenv()
|
|
13
10
|
|
|
11
|
+
def setup_logging(log_file: str=None, log_level: str="INFO") :
|
|
14
12
|
import colorlog
|
|
15
13
|
|
|
16
|
-
|
|
17
|
-
log_file = os.getenv("LOG_FILE", "log/xga.log")
|
|
18
|
-
log_level = getattr(logging, env_log_level.upper(), logging.INFO)
|
|
19
|
-
|
|
20
|
-
log_dir = os.path.dirname(log_file)
|
|
21
|
-
if log_dir and not os.path.exists(log_dir):
|
|
22
|
-
os.makedirs(log_dir, exist_ok=True)
|
|
23
|
-
else:
|
|
24
|
-
os.remove(log_file)
|
|
14
|
+
logging_level = getattr(logging, log_level.upper(), logging.INFO)
|
|
25
15
|
|
|
26
16
|
logger = logging.getLogger()
|
|
27
17
|
for handler in logger.handlers[:]:
|
|
@@ -40,25 +30,36 @@ def setup_logging() :
|
|
|
40
30
|
datefmt='%Y-%m-%d %H:%M:%S'
|
|
41
31
|
)
|
|
42
32
|
|
|
43
|
-
file_formatter = logging.Formatter(
|
|
44
|
-
'%(asctime)s -%(levelname)-8s %(message)s',
|
|
45
|
-
datefmt='%Y-%m-%d %H:%M:%S'
|
|
46
|
-
)
|
|
47
|
-
|
|
48
33
|
console_handler = logging.StreamHandler()
|
|
49
34
|
console_handler.setFormatter(console_formatter)
|
|
35
|
+
logger.addHandler(console_handler)
|
|
50
36
|
|
|
51
|
-
|
|
52
|
-
|
|
37
|
+
if log_file:
|
|
38
|
+
log_dir = os.path.dirname(log_file)
|
|
39
|
+
if log_dir and not os.path.exists(log_dir):
|
|
40
|
+
os.makedirs(log_dir, exist_ok=True)
|
|
41
|
+
else:
|
|
42
|
+
os.remove(log_file)
|
|
53
43
|
|
|
54
|
-
|
|
55
|
-
|
|
44
|
+
file_formatter = logging.Formatter(
|
|
45
|
+
'%(asctime)s -%(levelname)-8s %(message)s',
|
|
46
|
+
datefmt='%Y-%m-%d %H:%M:%S'
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
file_handler = logging.FileHandler(log_file, encoding='utf-8')
|
|
50
|
+
file_handler.setFormatter(file_formatter)
|
|
51
|
+
logger.addHandler(file_handler)
|
|
56
52
|
|
|
57
|
-
logger.setLevel(
|
|
53
|
+
logger.setLevel(logging_level)
|
|
58
54
|
|
|
59
|
-
logging.info(f"📡 XGAE_LOGGING is initialized, log_level={
|
|
55
|
+
logging.info(f"📡 XGAE_LOGGING is initialized, log_level={log_level}, log_file={log_file}")
|
|
60
56
|
|
|
61
|
-
|
|
57
|
+
def setup_env_logging():
|
|
58
|
+
log_enable = to_bool(os.getenv("LOG_ENABLE", True))
|
|
59
|
+
log_level = os.getenv("LOG_LEVEL", "INFO")
|
|
60
|
+
log_file = os.getenv("LOG_FILE", "log/xga.log")
|
|
61
|
+
if log_enable :
|
|
62
|
+
setup_logging(log_file, log_level)
|
|
62
63
|
|
|
63
64
|
def setup_langfuse() -> Langfuse:
|
|
64
65
|
env_public_key = os.getenv("LANGFUSE_PUBLIC_KEY")
|
|
@@ -77,4 +78,6 @@ def setup_langfuse() -> Langfuse:
|
|
|
77
78
|
|
|
78
79
|
return _langfuse
|
|
79
80
|
|
|
80
|
-
|
|
81
|
+
if __name__ == "__main__":
|
|
82
|
+
langfuse = setup_langfuse()
|
|
83
|
+
logging.warning(f"langfuse is enable={langfuse.enabled}")
|
|
@@ -68,16 +68,16 @@ class XMLToolParser:
|
|
|
68
68
|
# Find function_calls blocks
|
|
69
69
|
function_calls_matches = self.FUNCTION_CALLS_PATTERN.findall(content)
|
|
70
70
|
|
|
71
|
-
for
|
|
71
|
+
for func_content in function_calls_matches:
|
|
72
72
|
# Find all invoke blocks within this function_calls block
|
|
73
|
-
invoke_matches = self.INVOKE_PATTERN.findall(
|
|
73
|
+
invoke_matches = self.INVOKE_PATTERN.findall(func_content)
|
|
74
74
|
|
|
75
75
|
for function_name, invoke_content in invoke_matches:
|
|
76
76
|
try:
|
|
77
77
|
tool_call = self._parse_invoke_block(
|
|
78
78
|
function_name,
|
|
79
79
|
invoke_content,
|
|
80
|
-
|
|
80
|
+
func_content
|
|
81
81
|
)
|
|
82
82
|
if tool_call:
|
|
83
83
|
tool_calls.append(tool_call)
|
|
@@ -98,17 +98,11 @@ class XMLToolParser:
|
|
|
98
98
|
"function_name": function_name,
|
|
99
99
|
"raw_parameters": {}
|
|
100
100
|
}
|
|
101
|
-
|
|
102
|
-
# Extract all parameters
|
|
101
|
+
|
|
103
102
|
param_matches = self.PARAMETER_PATTERN.findall(invoke_content)
|
|
104
|
-
|
|
105
103
|
for param_name, param_value in param_matches:
|
|
106
|
-
# Clean up the parameter value
|
|
107
104
|
param_value = param_value.strip()
|
|
108
|
-
|
|
109
|
-
# Try to parse as JSON if it looks like JSON
|
|
110
105
|
parsed_value = self._parse_parameter_value(param_value)
|
|
111
|
-
|
|
112
106
|
parameters[param_name] = parsed_value
|
|
113
107
|
parsing_details["raw_parameters"][param_name] = param_value
|
|
114
108
|
|
|
@@ -161,73 +155,3 @@ class XMLToolParser:
|
|
|
161
155
|
|
|
162
156
|
# Return as string
|
|
163
157
|
return value
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
def format_tool_call(self, function_name: str, parameters: Dict[str, Any]) -> str:
|
|
167
|
-
"""
|
|
168
|
-
Format a tool call in the XML format.
|
|
169
|
-
|
|
170
|
-
Args:
|
|
171
|
-
function_name: Name of the function to call
|
|
172
|
-
parameters: Dictionary of parameters
|
|
173
|
-
|
|
174
|
-
Returns:
|
|
175
|
-
Formatted XML string
|
|
176
|
-
"""
|
|
177
|
-
lines = ['<function_calls>', '<invoke name="{}">'.format(function_name)]
|
|
178
|
-
|
|
179
|
-
for param_name, param_value in parameters.items():
|
|
180
|
-
# Convert value to string representation
|
|
181
|
-
if isinstance(param_value, (dict, list)):
|
|
182
|
-
value_str = json.dumps(param_value)
|
|
183
|
-
elif isinstance(param_value, bool):
|
|
184
|
-
value_str = str(param_value).lower()
|
|
185
|
-
else:
|
|
186
|
-
value_str = str(param_value)
|
|
187
|
-
|
|
188
|
-
lines.append('<parameter name="{}">{}</parameter>'.format(
|
|
189
|
-
param_name, value_str
|
|
190
|
-
))
|
|
191
|
-
|
|
192
|
-
lines.extend(['</invoke>', '</function_calls>'])
|
|
193
|
-
return '\n'.join(lines)
|
|
194
|
-
|
|
195
|
-
def validate_tool_call(self, tool_call: XMLToolCall, expected_params: Optional[Dict[str, type]] = None) -> Tuple[bool, Optional[str]]:
|
|
196
|
-
"""
|
|
197
|
-
Validate a tool call against expected parameters.
|
|
198
|
-
|
|
199
|
-
Args:
|
|
200
|
-
tool_call: The XMLToolCall to validate
|
|
201
|
-
expected_params: Optional dict of parameter names to expected types
|
|
202
|
-
|
|
203
|
-
Returns:
|
|
204
|
-
Tuple of (is_valid, error_message)
|
|
205
|
-
"""
|
|
206
|
-
if not tool_call.function_name:
|
|
207
|
-
return False, "Function name is required"
|
|
208
|
-
|
|
209
|
-
if expected_params:
|
|
210
|
-
for param_name, expected_type in expected_params.items():
|
|
211
|
-
if param_name not in tool_call.parameters:
|
|
212
|
-
return False, f"Missing required parameter: {param_name}"
|
|
213
|
-
|
|
214
|
-
param_value = tool_call.parameters[param_name]
|
|
215
|
-
if not isinstance(param_value, expected_type):
|
|
216
|
-
return False, f"Parameter {param_name} should be of type {expected_type.__name__}"
|
|
217
|
-
|
|
218
|
-
return True, None
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
# Convenience function for quick parsing
|
|
222
|
-
def parse_xml_tool_calls(content: str) -> List[XMLToolCall]:
|
|
223
|
-
"""
|
|
224
|
-
Parse XML tool calls from content.
|
|
225
|
-
|
|
226
|
-
Args:
|
|
227
|
-
content: The text content potentially containing XML tool calls
|
|
228
|
-
|
|
229
|
-
Returns:
|
|
230
|
-
List of parsed XMLToolCall objects
|
|
231
|
-
"""
|
|
232
|
-
parser = XMLToolParser()
|
|
233
|
-
return parser.parse_content(content)
|
|
@@ -17,6 +17,6 @@
|
|
|
17
17
|
Step[9]: Else determine if alarm type is middleware (type=3)
|
|
18
18
|
Step[10]: If yes, trigger middleware fault location module
|
|
19
19
|
Task[2]: Solution Retrieval
|
|
20
|
-
Step[1]: For business alarms, call
|
|
21
|
-
Step[2]: For device alarms, call
|
|
20
|
+
Step[1]: For business alarms, call get_busi_fault_cause to query business fault solutions
|
|
21
|
+
Step[2]: For device alarms, call get_equip_fault_cause to query device fault solutions
|
|
22
22
|
Step[3]: For middleware alarms, call web_search to query middleware fault solutions
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|