xgae 0.1.5__py3-none-any.whl → 0.1.7__py3-none-any.whl
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/engine/{xga_base.py → engine_base.py} +6 -9
- xgae/engine/{xga_mcp_tool_box.py → mcp_tool_box.py} +11 -5
- xgae/engine/{xga_prompt_builder.py → prompt_builder.py} +3 -2
- xgae/engine/responser/non_stream_responser.py +108 -0
- xgae/engine/responser/{xga_responser_base.py → responser_base.py} +124 -218
- xgae/engine/responser/{xga_stream_responser.py → stream_responser.py} +51 -55
- xgae/engine/{xga_engine.py → task_engine.py} +166 -146
- xgae/tools/without_general_tools_app.py +48 -0
- xgae/utils/__init__.py +13 -0
- xgae/utils/llm_client.py +8 -3
- xgae/utils/{utils.py → misc.py} +0 -8
- xgae/utils/setup_env.py +53 -66
- xgae/utils/xml_tool_parser.py +4 -7
- {xgae-0.1.5.dist-info → xgae-0.1.7.dist-info}/METADATA +1 -1
- xgae-0.1.7.dist-info/RECORD +19 -0
- xgae-0.1.7.dist-info/entry_points.txt +2 -0
- xgae/engine/responser/xga_non_stream_responser.py +0 -216
- xgae-0.1.5.dist-info/RECORD +0 -16
- {xgae-0.1.5.dist-info → xgae-0.1.7.dist-info}/WHEEL +0 -0
|
@@ -1,25 +1,31 @@
|
|
|
1
1
|
|
|
2
2
|
import logging
|
|
3
3
|
import json
|
|
4
|
+
import os
|
|
4
5
|
|
|
5
|
-
from typing import List, Any, Dict, Optional, AsyncGenerator,
|
|
6
|
+
from typing import List, Any, Dict, Optional, AsyncGenerator, Union, Literal
|
|
6
7
|
from uuid import uuid4
|
|
7
8
|
|
|
8
|
-
from xgae.engine.responser.
|
|
9
|
-
from xgae.engine.
|
|
9
|
+
from xgae.engine.responser.responser_base import TaskResponserContext, TaskResponseProcessor, TaskRunContinuousState
|
|
10
|
+
from xgae.engine.engine_base import XGAResponseMsgType, XGAResponseMessage, XGAToolBox, XGATaskResult
|
|
11
|
+
|
|
12
|
+
from xgae.utils import langfuse, handle_error
|
|
10
13
|
from xgae.utils.llm_client import LLMClient, LLMConfig
|
|
11
|
-
from xgae.utils.setup_env import langfuse
|
|
12
|
-
from xgae.utils.utils import handle_error
|
|
13
14
|
|
|
14
|
-
from
|
|
15
|
-
from
|
|
15
|
+
from xgae.utils.json_helpers import format_for_yield
|
|
16
|
+
from xgae.engine.prompt_builder import XGAPromptBuilder
|
|
17
|
+
from xgae.engine.mcp_tool_box import XGAMcpToolBox
|
|
16
18
|
|
|
17
19
|
class XGATaskEngine:
|
|
18
20
|
def __init__(self,
|
|
19
21
|
session_id: Optional[str] = None,
|
|
20
22
|
task_id: Optional[str] = None,
|
|
21
23
|
agent_id: Optional[str] = None,
|
|
24
|
+
general_tools: Optional[List[str]] = None,
|
|
25
|
+
custom_tools: Optional[List[str]] = None,
|
|
22
26
|
system_prompt: Optional[str] = None,
|
|
27
|
+
max_auto_run: Optional[int] = None,
|
|
28
|
+
tool_exec_parallel: Optional[bool] = None,
|
|
23
29
|
llm_config: Optional[LLMConfig] = None,
|
|
24
30
|
prompt_builder: Optional[XGAPromptBuilder] = None,
|
|
25
31
|
tool_box: Optional[XGAToolBox] = None):
|
|
@@ -32,121 +38,109 @@ class XGATaskEngine:
|
|
|
32
38
|
self.is_stream = self.llm_client.is_stream
|
|
33
39
|
|
|
34
40
|
self.prompt_builder = prompt_builder or XGAPromptBuilder(system_prompt)
|
|
35
|
-
self.tool_box = tool_box or XGAMcpToolBox()
|
|
36
|
-
|
|
37
|
-
self.task_response_msgs: List[XGAResponseMsg] = []
|
|
38
|
-
self.task_no = -1
|
|
39
|
-
self.task_run_id = f"{self.task_id}[{self.task_no}]"
|
|
40
|
-
self.trace_id = None
|
|
41
|
-
|
|
42
|
-
async def _post_init_(self, general_tools:List[str], custom_tools: List[str]) -> None:
|
|
43
|
-
await self.tool_box.load_mcp_tools_schema()
|
|
44
|
-
await self.tool_box.creat_task_tool_box(self.task_id, general_tools, custom_tools)
|
|
45
|
-
general_tool_schemas = self.tool_box.get_task_tool_schemas(self.task_id, "general_tool")
|
|
46
|
-
custom_tool_schemas = self.tool_box.get_task_tool_schemas(self.task_id, "custom_tool")
|
|
47
|
-
|
|
48
|
-
self.task_prompt = self.prompt_builder.build_task_prompt(self.model_name, general_tool_schemas, custom_tool_schemas)
|
|
41
|
+
self.tool_box: XGAToolBox = tool_box or XGAMcpToolBox()
|
|
49
42
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
task_id: Optional[str] = None,
|
|
54
|
-
agent_id: Optional[str] = None,
|
|
55
|
-
system_prompt: Optional[str] = None,
|
|
56
|
-
general_tools: Optional[List[str]] = None,
|
|
57
|
-
custom_tools: Optional[List[str]] = None,
|
|
58
|
-
llm_config: Optional[LLMConfig] = None,
|
|
59
|
-
prompt_builder: Optional[XGAPromptBuilder] = None,
|
|
60
|
-
tool_box: Optional[XGAToolBox] = None) -> 'XGATaskEngine':
|
|
61
|
-
engine: XGATaskEngine = cls(session_id=session_id,
|
|
62
|
-
task_id=task_id,
|
|
63
|
-
agent_id=agent_id,
|
|
64
|
-
system_prompt=system_prompt,
|
|
65
|
-
llm_config=llm_config,
|
|
66
|
-
prompt_builder=prompt_builder,
|
|
67
|
-
tool_box=tool_box)
|
|
43
|
+
self.general_tools:List[str] = general_tools
|
|
44
|
+
self.custom_tools:List[str] = custom_tools
|
|
45
|
+
self.task_response_msgs: List[XGAResponseMessage] = []
|
|
68
46
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
|
72
50
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
logging.info(f"general_tools={general_tools}, custom_tools={custom_tools}")
|
|
51
|
+
self.task_no = -1
|
|
52
|
+
self.task_run_id :str = None
|
|
76
53
|
|
|
77
|
-
|
|
54
|
+
self.task_prompt :str = None
|
|
55
|
+
self.trace_id :str = None
|
|
56
|
+
self.root_span_id :str = None
|
|
78
57
|
|
|
79
58
|
async def run_task_with_final_answer(self,
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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")
|
|
77
|
+
|
|
78
|
+
root_span.update(output=final_result)
|
|
79
|
+
return final_result
|
|
86
80
|
|
|
87
|
-
final_result = self._parse_final_result(chunks)
|
|
88
|
-
return final_result
|
|
89
81
|
|
|
90
82
|
async def run_task(self,
|
|
91
83
|
task_message: Dict[str, Any],
|
|
92
|
-
max_auto_run: int = 25,
|
|
93
84
|
trace_id: Optional[str] = None) -> AsyncGenerator[Dict[str, Any], None]:
|
|
94
85
|
try:
|
|
95
|
-
|
|
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
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
self.add_response_message(type="user", content=task_message, is_llm_message=True)
|
|
97
|
+
|
|
98
|
+
continuous_state: TaskRunContinuousState = {
|
|
99
|
+
"accumulated_content": "",
|
|
100
|
+
"auto_continue_count": 0,
|
|
101
|
+
"auto_continue": False if self.max_auto_run <= 1 else True
|
|
102
|
+
}
|
|
103
|
+
async for chunk in self._run_task_auto(continuous_state):
|
|
104
|
+
yield chunk
|
|
105
|
+
finally:
|
|
106
|
+
await self.tool_box.destroy_task_tool_box(self.task_id)
|
|
107
|
+
self.root_span_id = None
|
|
96
108
|
|
|
97
|
-
self.task_no += 1
|
|
98
|
-
self.task_run_id = f"{self.task_id}[{self.task_no}]"
|
|
99
109
|
|
|
100
|
-
|
|
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}]"
|
|
101
113
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
async for chunk in self._run_task_once(continuous_state):
|
|
109
|
-
yield chunk
|
|
110
|
-
else:
|
|
111
|
-
async for chunk in self._run_task_auto(max_auto_run):
|
|
112
|
-
yield chunk
|
|
113
|
-
finally:
|
|
114
|
-
await self.tool_box.destroy_task_tool_box(self.task_id)
|
|
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")
|
|
115
120
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
llm_messages.extend(cxt_llm_contents)
|
|
121
|
+
custom_tools = self.custom_tools or []
|
|
122
|
+
if isinstance(self.tool_box, XGAMcpToolBox):
|
|
123
|
+
await self.tool_box.load_mcp_tools_schema()
|
|
120
124
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
"role": "assistant",
|
|
125
|
-
"content": partial_content
|
|
126
|
-
}
|
|
127
|
-
llm_messages.append(temp_assistant_message)
|
|
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
128
|
|
|
129
|
-
|
|
130
|
-
response_processor = self._create_response_processer()
|
|
129
|
+
self.task_prompt = self.prompt_builder.build_task_prompt(self.model_name, general_tool_schemas, custom_tool_schemas)
|
|
131
130
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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}")
|
|
135
134
|
|
|
136
|
-
async def _run_task_auto(self, max_auto_run: int) -> AsyncGenerator[Dict[str, Any], None]:
|
|
137
|
-
continuous_state: TaskRunContinuousState = {
|
|
138
|
-
"accumulated_content": "",
|
|
139
|
-
"auto_continue_count": 0,
|
|
140
|
-
"auto_continue": True
|
|
141
|
-
}
|
|
142
135
|
|
|
136
|
+
async def _run_task_auto(self, continuous_state: TaskRunContinuousState) -> AsyncGenerator[Dict[str, Any], None]:
|
|
143
137
|
def update_continuous_state(_auto_continue_count, _auto_continue):
|
|
144
138
|
continuous_state["auto_continue_count"] = _auto_continue_count
|
|
145
139
|
continuous_state["auto_continue"] = _auto_continue
|
|
146
140
|
|
|
147
141
|
auto_continue_count = 0
|
|
148
142
|
auto_continue = True
|
|
149
|
-
while auto_continue and auto_continue_count < max_auto_run:
|
|
143
|
+
while auto_continue and auto_continue_count < self.max_auto_run:
|
|
150
144
|
auto_continue = False
|
|
151
145
|
|
|
152
146
|
try:
|
|
@@ -163,30 +157,52 @@ class XGATaskEngine:
|
|
|
163
157
|
elif status_type == 'finish':
|
|
164
158
|
finish_reason = content.get('finish_reason', None)
|
|
165
159
|
if finish_reason == 'completed':
|
|
166
|
-
logging.
|
|
160
|
+
logging.info(f"run_task_auto: Detected finish_reason='completed', TASK_COMPLETE Success !")
|
|
167
161
|
auto_continue = False
|
|
168
162
|
break
|
|
169
163
|
elif finish_reason == 'xml_tool_limit_reached':
|
|
170
|
-
logging.warning(f"run_task_auto: Detected finish_reason='xml_tool_limit_reached',
|
|
164
|
+
logging.warning(f"run_task_auto: Detected finish_reason='xml_tool_limit_reached', stop auto-continue")
|
|
171
165
|
auto_continue = False
|
|
172
166
|
break
|
|
173
167
|
elif finish_reason == 'stop' or finish_reason == 'length': # 'length' never occur
|
|
174
168
|
auto_continue = True
|
|
175
169
|
auto_continue_count += 1
|
|
176
170
|
update_continuous_state(auto_continue_count, auto_continue)
|
|
177
|
-
logging.info(f"run_task_auto: Detected finish_reason='{finish_reason}', auto-continuing ({auto_continue_count}/{max_auto_run})")
|
|
178
|
-
except StopAsyncIteration:
|
|
179
|
-
pass
|
|
171
|
+
logging.info(f"run_task_auto: Detected finish_reason='{finish_reason}', auto-continuing ({auto_continue_count}/{self.max_auto_run})")
|
|
180
172
|
except Exception as parse_error:
|
|
181
173
|
logging.error(f"run_task_auto: Error in parse chunk: {str(parse_error)}")
|
|
182
174
|
content = {"role": "system", "status_type": "error", "message": "Parse response chunk Error"}
|
|
183
|
-
|
|
184
|
-
|
|
175
|
+
handle_error(parse_error)
|
|
176
|
+
error_msg = self.add_response_message(type="status", content=content, is_llm_message=False)
|
|
177
|
+
yield format_for_yield(error_msg)
|
|
185
178
|
except Exception as run_error:
|
|
186
179
|
logging.error(f"run_task_auto: Call task_run_once error: {str(run_error)}")
|
|
187
180
|
content = {"role": "system", "status_type": "error", "message": "Call task_run_once error"}
|
|
188
|
-
|
|
189
|
-
|
|
181
|
+
handle_error(run_error)
|
|
182
|
+
error_msg = self.add_response_message(type="status", content=content, is_llm_message=False)
|
|
183
|
+
yield format_for_yield(error_msg)
|
|
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
|
+
|
|
190
206
|
|
|
191
207
|
def _parse_final_result(self, chunks: List[Dict[str, Any]]) -> XGATaskResult:
|
|
192
208
|
final_result: XGATaskResult = None
|
|
@@ -200,14 +216,11 @@ class XGATaskEngine:
|
|
|
200
216
|
if status_type == "error":
|
|
201
217
|
error = status_content.get('message', 'Unknown error')
|
|
202
218
|
final_result = XGATaskResult(type="error", content=error)
|
|
203
|
-
break
|
|
204
219
|
elif status_type == "finish":
|
|
205
220
|
finish_reason = status_content.get('finish_reason', None)
|
|
206
221
|
if finish_reason == 'xml_tool_limit_reached':
|
|
207
222
|
error = "Completed due to over task max_auto_run limit !"
|
|
208
223
|
final_result = XGATaskResult(type="error", content=error)
|
|
209
|
-
break
|
|
210
|
-
continue
|
|
211
224
|
elif chunk_type == "tool" and finish_reason in ['completed', 'stop']:
|
|
212
225
|
tool_content = json.loads(chunk.get('content', '{}'))
|
|
213
226
|
tool_execution = tool_content.get('tool_execution')
|
|
@@ -229,9 +242,10 @@ class XGATaskEngine:
|
|
|
229
242
|
result_content = f"Task execute '{tool_name}' {result_type}: {output}"
|
|
230
243
|
final_result = XGATaskResult(type=result_type, content=result_content)
|
|
231
244
|
elif chunk_type == "assistant" and finish_reason == 'stop':
|
|
232
|
-
assis_content = chunk.get('content',
|
|
245
|
+
assis_content = chunk.get('content', {})
|
|
233
246
|
result_content = assis_content.get("content", "LLM output is empty")
|
|
234
247
|
final_result = XGATaskResult(type="answer", content=result_content)
|
|
248
|
+
|
|
235
249
|
if final_result is not None:
|
|
236
250
|
break
|
|
237
251
|
except Exception as e:
|
|
@@ -241,74 +255,80 @@ class XGATaskEngine:
|
|
|
241
255
|
|
|
242
256
|
return final_result
|
|
243
257
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
258
|
+
|
|
259
|
+
def add_response_message(self, type: XGAResponseMsgType,
|
|
260
|
+
content: Union[Dict[str, Any], List[Any], str],
|
|
261
|
+
is_llm_message: bool,
|
|
262
|
+
metadata: Optional[Dict[str, Any]]=None)-> XGAResponseMessage:
|
|
263
|
+
metadata = metadata or {}
|
|
264
|
+
metadata["task_id"] = self.task_id
|
|
265
|
+
metadata["task_run_id"] = self.task_run_id
|
|
266
|
+
metadata["trace_id"] = self.trace_id
|
|
267
|
+
metadata["session_id"] = self.session_id
|
|
268
|
+
metadata["agent_id"] = self.agent_id
|
|
269
|
+
|
|
270
|
+
message = XGAResponseMessage(
|
|
249
271
|
message_id = f"xga_msg_{uuid4()}",
|
|
250
272
|
type = type,
|
|
251
|
-
content = content,
|
|
252
273
|
is_llm_message=is_llm_message,
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
agent_id = self.agent_id,
|
|
256
|
-
task_id = self.task_id,
|
|
257
|
-
task_run_id = self.task_run_id,
|
|
258
|
-
trace_id = self.trace_id
|
|
274
|
+
content = content,
|
|
275
|
+
metadata = metadata
|
|
259
276
|
)
|
|
260
277
|
self.task_response_msgs.append(message)
|
|
261
278
|
|
|
262
279
|
return message
|
|
263
280
|
|
|
264
|
-
def
|
|
281
|
+
def get_history_llm_messages (self) -> List[Dict[str, Any]]:
|
|
265
282
|
llm_messages = []
|
|
266
283
|
for message in self.task_response_msgs:
|
|
267
284
|
if message["is_llm_message"]:
|
|
268
285
|
llm_messages.append(message)
|
|
269
286
|
|
|
270
|
-
|
|
287
|
+
response_llm_contents = []
|
|
271
288
|
for llm_message in llm_messages:
|
|
272
289
|
content = llm_message["content"]
|
|
273
290
|
# @todo content List type
|
|
274
291
|
if isinstance(content, str):
|
|
275
292
|
try:
|
|
276
293
|
_content = json.loads(content)
|
|
277
|
-
|
|
294
|
+
response_llm_contents.append(_content)
|
|
278
295
|
except json.JSONDecodeError as e:
|
|
279
296
|
logging.error(f"get_context_llm_contents: Failed to decode json, content=:{content}")
|
|
280
297
|
handle_error(e)
|
|
281
298
|
else:
|
|
282
|
-
|
|
299
|
+
response_llm_contents.append(content)
|
|
300
|
+
|
|
301
|
+
return response_llm_contents
|
|
283
302
|
|
|
284
|
-
return cxt_llm_contents
|
|
285
303
|
|
|
286
304
|
def _create_response_processer(self) -> TaskResponseProcessor:
|
|
287
305
|
response_context = self._create_response_context()
|
|
288
306
|
is_stream = response_context.get("is_stream", False)
|
|
289
307
|
if is_stream:
|
|
290
|
-
from xgae.engine.responser.
|
|
308
|
+
from xgae.engine.responser.stream_responser import StreamTaskResponser
|
|
291
309
|
return StreamTaskResponser(response_context)
|
|
292
310
|
else:
|
|
293
|
-
from xgae.engine.responser.
|
|
311
|
+
from xgae.engine.responser.non_stream_responser import NonStreamTaskResponser
|
|
294
312
|
return NonStreamTaskResponser(response_context)
|
|
295
313
|
|
|
296
|
-
def _create_response_context(self) ->
|
|
297
|
-
response_context:
|
|
314
|
+
def _create_response_context(self) -> TaskResponserContext:
|
|
315
|
+
response_context: TaskResponserContext = {
|
|
298
316
|
"is_stream": self.is_stream,
|
|
299
317
|
"task_id": self.task_id,
|
|
300
318
|
"task_run_id": self.task_run_id,
|
|
301
319
|
"trace_id": self.trace_id,
|
|
320
|
+
"root_span_id": self.root_span_id,
|
|
302
321
|
"model_name": self.model_name,
|
|
303
322
|
"max_xml_tool_calls": 0,
|
|
304
|
-
"
|
|
323
|
+
"add_response_msg_func": self.add_response_message,
|
|
305
324
|
"tool_box": self.tool_box,
|
|
306
|
-
"tool_execution_strategy": "parallel"
|
|
325
|
+
"tool_execution_strategy": "parallel" if self.tool_exec_parallel else "sequential" ,#,
|
|
307
326
|
"xml_adding_strategy": "user_message",
|
|
308
327
|
}
|
|
309
328
|
return response_context
|
|
310
329
|
|
|
311
|
-
|
|
330
|
+
|
|
331
|
+
def _logging_reponse_chunk(self, chunk):
|
|
312
332
|
chunk_type = chunk.get('type')
|
|
313
333
|
prefix = ""
|
|
314
334
|
|
|
@@ -327,21 +347,21 @@ class XGATaskEngine:
|
|
|
327
347
|
|
|
328
348
|
if __name__ == "__main__":
|
|
329
349
|
import asyncio
|
|
330
|
-
from xgae.utils.
|
|
350
|
+
from xgae.utils.misc import read_file
|
|
351
|
+
|
|
331
352
|
async def main():
|
|
332
353
|
tool_box = XGAMcpToolBox(custom_mcp_server_file="mcpservers/custom_servers.json")
|
|
333
|
-
system_prompt = read_file("templates/
|
|
334
|
-
engine =
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
#final_result = await engine.run_task_with_final_answer(task_message={"role": "user", "content": "1+1"}, max_auto_run=2)
|
|
344
|
-
|
|
345
|
-
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"})
|
|
346
364
|
print("FINAL RESULT:", final_result)
|
|
365
|
+
|
|
366
|
+
|
|
347
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()
|
xgae/utils/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from .setup_env import setup_langfuse, setup_logging
|
|
4
|
+
|
|
5
|
+
setup_logging()
|
|
6
|
+
langfuse = setup_langfuse()
|
|
7
|
+
|
|
8
|
+
def handle_error(e: Exception) -> None:
|
|
9
|
+
import traceback
|
|
10
|
+
|
|
11
|
+
logging.error("An error occurred: %s", str(e))
|
|
12
|
+
logging.error("Traceback details:\n%s", traceback.format_exc())
|
|
13
|
+
raise (e) from e
|
xgae/utils/llm_client.py
CHANGED
|
@@ -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
|
-
|
|
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", [{}])
|
xgae/utils/{utils.py → misc.py}
RENAMED
|
@@ -1,17 +1,9 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import os
|
|
3
3
|
import sys
|
|
4
|
-
import datetime
|
|
5
4
|
|
|
6
5
|
from typing import Any, Dict
|
|
7
6
|
|
|
8
|
-
def handle_error(e: Exception) -> None:
|
|
9
|
-
import traceback
|
|
10
|
-
|
|
11
|
-
logging.error("An error occurred: %s", str(e))
|
|
12
|
-
logging.error("Traceback details:\n%s", traceback.format_exc())
|
|
13
|
-
raise (e) from e
|
|
14
|
-
|
|
15
7
|
def read_file(file_path: str) -> str:
|
|
16
8
|
if not os.path.exists(file_path):
|
|
17
9
|
logging.error(f"File '{file_path}' not found")
|