xgae 0.1.19__tar.gz → 0.2.0__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 (50) hide show
  1. {xgae-0.1.19 → xgae-0.2.0}/CHANGELOG.md +15 -1
  2. {xgae-0.1.19 → xgae-0.2.0}/PKG-INFO +1 -1
  3. {xgae-0.1.19 → xgae-0.2.0}/pyproject.toml +1 -1
  4. xgae-0.2.0/src/examples/agent/langgraph/react/agent_base.py +32 -0
  5. {xgae-0.1.19 → xgae-0.2.0}/src/examples/agent/langgraph/react/react_agent.py +149 -71
  6. xgae-0.2.0/src/examples/agent/langgraph/react/result_eval_agent.py +125 -0
  7. {xgae-0.1.19 → xgae-0.2.0}/src/examples/agent/langgraph/react/run_react_agent.py +8 -6
  8. {xgae-0.1.19 → xgae-0.2.0}/src/xgae/engine/task_engine.py +6 -4
  9. {xgae-0.1.19 → xgae-0.2.0}/src/xgae/engine/task_langfuse.py +6 -4
  10. {xgae-0.1.19 → xgae-0.2.0}/src/xgae/utils/llm_client.py +27 -5
  11. {xgae-0.1.19 → xgae-0.2.0}/templates/agent_tool_prompt_template.txt +1 -0
  12. {xgae-0.1.19 → xgae-0.2.0}/templates/custom_tool_prompt_template.txt +11 -8
  13. xgae-0.2.0/templates/example/result_eval_template.txt +66 -0
  14. {xgae-0.1.19 → xgae-0.2.0}/templates/general_tool_prompt_template.txt +1 -0
  15. xgae-0.2.0/uv.lock +1463 -0
  16. xgae-0.1.19/src/examples/agent/langgraph/react/final_result_agent.py +0 -59
  17. xgae-0.1.19/templates/example/final_result_template.txt +0 -19
  18. xgae-0.1.19/uv.lock +0 -1463
  19. {xgae-0.1.19 → xgae-0.2.0}/.env +0 -0
  20. {xgae-0.1.19 → xgae-0.2.0}/.python-version +0 -0
  21. {xgae-0.1.19 → xgae-0.2.0}/README.md +0 -0
  22. {xgae-0.1.19 → xgae-0.2.0}/mcpservers/custom_servers.json +0 -0
  23. {xgae-0.1.19 → xgae-0.2.0}/mcpservers/xga_server.json +0 -0
  24. {xgae-0.1.19 → xgae-0.2.0}/mcpservers/xga_server_sse.json +0 -0
  25. {xgae-0.1.19 → xgae-0.2.0}/src/examples/engine/run_custom_and_agent_tools.py +0 -0
  26. {xgae-0.1.19 → xgae-0.2.0}/src/examples/engine/run_general_tools.py +0 -0
  27. {xgae-0.1.19 → xgae-0.2.0}/src/examples/engine/run_human_in_loop.py +0 -0
  28. {xgae-0.1.19 → xgae-0.2.0}/src/examples/engine/run_simple.py +0 -0
  29. {xgae-0.1.19 → xgae-0.2.0}/src/examples/tools/custom_fault_tools_app.py +0 -0
  30. {xgae-0.1.19 → xgae-0.2.0}/src/examples/tools/simu_a2a_tools_app.py +0 -0
  31. {xgae-0.1.19 → xgae-0.2.0}/src/xgae/__init__.py +0 -0
  32. {xgae-0.1.19 → xgae-0.2.0}/src/xgae/engine/engine_base.py +0 -0
  33. {xgae-0.1.19 → xgae-0.2.0}/src/xgae/engine/mcp_tool_box.py +0 -0
  34. {xgae-0.1.19 → xgae-0.2.0}/src/xgae/engine/prompt_builder.py +0 -0
  35. {xgae-0.1.19 → xgae-0.2.0}/src/xgae/engine/responser/non_stream_responser.py +0 -0
  36. {xgae-0.1.19 → xgae-0.2.0}/src/xgae/engine/responser/responser_base.py +0 -0
  37. {xgae-0.1.19 → xgae-0.2.0}/src/xgae/engine/responser/stream_responser.py +0 -0
  38. {xgae-0.1.19 → xgae-0.2.0}/src/xgae/engine_cli_app.py +0 -0
  39. {xgae-0.1.19 → xgae-0.2.0}/src/xgae/tools/without_general_tools_app.py +0 -0
  40. {xgae-0.1.19 → xgae-0.2.0}/src/xgae/utils/__init__.py +0 -0
  41. {xgae-0.1.19 → xgae-0.2.0}/src/xgae/utils/json_helpers.py +0 -0
  42. {xgae-0.1.19 → xgae-0.2.0}/src/xgae/utils/misc.py +0 -0
  43. {xgae-0.1.19 → xgae-0.2.0}/src/xgae/utils/setup_env.py +0 -0
  44. {xgae-0.1.19 → xgae-0.2.0}/src/xgae/utils/xml_tool_parser.py +0 -0
  45. {xgae-0.1.19 → xgae-0.2.0}/templates/example/fault_user_prompt.txt +0 -0
  46. {xgae-0.1.19 → xgae-0.2.0}/templates/gemini_system_prompt_template.txt +0 -0
  47. {xgae-0.1.19 → xgae-0.2.0}/templates/system_prompt_response_sample.txt +0 -0
  48. {xgae-0.1.19 → xgae-0.2.0}/templates/system_prompt_template.txt +0 -0
  49. {xgae-0.1.19 → xgae-0.2.0}/test/test_langfuse.py +0 -0
  50. {xgae-0.1.19 → xgae-0.2.0}/test/test_litellm_langfuse.py +0 -0
@@ -1,7 +1,21 @@
1
1
  # Release Changelog
2
+ ## [0.2.0] - 2025-9-10
3
+ ### Added
4
+ - Agent Engine release 0.2
5
+ - Example: Langgraph ReactAgent release 0.2
6
+ ### Fixed
7
+ - Agent Engine: call mcp tool fail, call 'ask' tool again and again
8
+ - Example Langgraph ReactAgent: retry on 'ask', user_input is ask answer
9
+
10
+
11
+ ## [0.1.20] - 2025-9-9
12
+ ### Added
13
+ - Example: Langgraph ReactAgent add final_result_agent
14
+
15
+
2
16
  ## [0.1.19] - 2025-9-8
3
17
  ### Added
4
- - Example: Langgraph react agent release V1, full logic but no final result agent and tool select agent
18
+ - Example: Langgraph ReactAgent release V1, full logic but no final result agent and tool select agent
5
19
 
6
20
 
7
21
  # Release Changelog
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xgae
3
- Version: 0.1.19
3
+ Version: 0.2.0
4
4
  Summary: Extreme General Agent Engine
5
5
  Requires-Python: >=3.13
6
6
  Requires-Dist: colorlog==6.9.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "xgae"
3
- version = "0.1.19"
3
+ version = "0.2.0"
4
4
  description = "Extreme General Agent Engine"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -0,0 +1,32 @@
1
+ from typing import Any, Dict, List, TypedDict, Optional
2
+ from xgae.engine.engine_base import XGATaskResult
3
+
4
+ class EvaluateResult(TypedDict, total=False):
5
+ task_result: Dict[str, Any]
6
+ task_process: Dict[str, Any]
7
+ function_call: Dict[str, Any]
8
+
9
+ class AgentContext(TypedDict, total=False):
10
+ task_id: str
11
+ session_id: str
12
+ user_id: str
13
+ agent_id: str
14
+ thread_id: str
15
+
16
+
17
+ class TaskState(TypedDict, total=False):
18
+ """State definition for the agent orchestration graph"""
19
+ llm_messages: List[Dict[str, Any]]
20
+ user_inputs: List[str]
21
+ next_node: str
22
+ system_prompt: str
23
+ custom_tools: List[str]
24
+ general_tools: List[str]
25
+ task_result: XGATaskResult
26
+ final_result: XGATaskResult
27
+ eval_result: EvaluateResult
28
+ retry_count: int
29
+ task_no: int
30
+ agent_context: AgentContext
31
+
32
+
@@ -1,67 +1,50 @@
1
1
  import logging
2
2
  import os
3
3
 
4
- from typing import Any, Dict, List, Annotated, Sequence, TypedDict, Optional, AsyncGenerator
4
+ from typing import Any, Dict, List, Optional, AsyncGenerator
5
5
  from uuid import uuid4
6
6
 
7
7
  from langfuse.callback import CallbackHandler
8
8
  from langfuse import Langfuse
9
9
 
10
- from langchain_core.messages import BaseMessage, HumanMessage, AIMessage
11
10
 
12
11
  from langgraph.graph import END, START, StateGraph
13
12
  from langgraph.types import interrupt, Command
14
13
  from langgraph.checkpoint.memory import MemorySaver
15
- from langgraph.graph.message import add_messages
16
14
  from langgraph.config import get_stream_writer
17
15
 
18
16
  from xgae.utils.misc import read_file
19
17
  from xgae.utils import log_trace
20
18
 
21
- from xgae.engine.engine_base import XGATaskResult, XGAResponseMessage
19
+ from xgae.engine.engine_base import XGATaskResult
22
20
  from xgae.engine.mcp_tool_box import XGAMcpToolBox
23
21
  from xgae.engine.task_engine import XGATaskEngine
24
22
 
25
-
26
- class AgentContext(TypedDict, total=False):
27
- task_id: str
28
- session_id: str
29
- user_id: str
30
- agent_id: str
31
- thread_id: str
32
-
33
-
34
- class TaskState(TypedDict, total=False):
35
- """State definition for the agent orchestration graph"""
36
- messages: Annotated[Sequence[BaseMessage], add_messages] # for message persistent
37
- user_input: str
38
- next_node: str
39
- system_prompt: str
40
- custom_tools: List[str]
41
- general_tools: List[str]
42
- task_result: XGATaskResult
43
- final_result: XGATaskResult
44
- iteration_count: int
45
- agent_context: AgentContext
46
-
23
+ from examples.agent.langgraph.react.agent_base import AgentContext, TaskState, EvaluateResult
24
+ from examples.agent.langgraph.react.result_eval_agent import TaskResultEvalAgent
47
25
 
48
26
  class XGAReactAgent:
49
27
  MAX_TASK_RETRY = 2
28
+ QUALIFIED_RESULT_SCORE = 0.7
50
29
 
51
30
  def __init__(self):
52
- self.tool_box = XGAMcpToolBox(custom_mcp_server_file="mcpservers/custom_servers.json")
53
31
  self.graph = None
54
- self.graph_config = None
55
- self.graph_langfuse = Langfuse(enabled=False)
56
32
 
33
+ self.graph_config = None
34
+ self.graph_langfuse = None
57
35
  self.task_engine: XGATaskEngine = None
58
36
 
37
+ self.tool_box = XGAMcpToolBox(custom_mcp_server_file="mcpservers/custom_servers.json")
38
+ self.result_eval_agent = TaskResultEvalAgent()
39
+
40
+
59
41
  async def _create_graph(self) -> StateGraph:
60
42
  try:
61
43
  graph_builder = StateGraph(TaskState)
62
44
 
63
45
  # Add nodes
64
46
  graph_builder.add_node('supervisor', self._supervisor_node)
47
+ graph_builder.add_node('prompt_optimize', self._prompt_optimize_node)
65
48
  graph_builder.add_node('select_tool', self._select_tool_node)
66
49
  graph_builder.add_node('exec_task', self._exec_task_node)
67
50
  graph_builder.add_node('final_result', self._final_result_node)
@@ -72,12 +55,14 @@ class XGAReactAgent:
72
55
  'supervisor',
73
56
  self._next_condition,
74
57
  {
75
- 'select_tool': 'select_tool',
76
- 'exec_task': 'exec_task',
77
- 'end': END
58
+ 'select_tool' : 'select_tool',
59
+ 'exec_task' : 'exec_task',
60
+ 'prompt_optimize' : 'prompt_optimize',
61
+ 'end' : END
78
62
  }
79
63
  )
80
64
 
65
+ graph_builder.add_edge('prompt_optimize', 'select_tool')
81
66
  graph_builder.add_edge('select_tool', 'exec_task')
82
67
  graph_builder.add_edge('exec_task', 'final_result')
83
68
 
@@ -86,8 +71,8 @@ class XGAReactAgent:
86
71
  self._next_condition,
87
72
  {
88
73
  'supervisor': 'supervisor',
89
- 'exec_task': 'exec_task',
90
- 'end': END
74
+ 'exec_task' : 'exec_task',
75
+ 'end' : END
91
76
  }
92
77
  )
93
78
 
@@ -99,55 +84,105 @@ class XGAReactAgent:
99
84
  logging.error("Failed to create XGARectAgent Graph: %s", str(e))
100
85
  raise
101
86
 
87
+
102
88
  def _search_system_prompt(self, user_input: str) -> str:
103
89
  # You should search RAG use user_input, fetch COT or Prompt for your business
104
90
  system_prompt = None if "fault" not in user_input else read_file("templates/example/fault_user_prompt.txt")
105
91
  return system_prompt
106
92
 
93
+
107
94
  async def _supervisor_node(self, state: TaskState) -> Dict[str, Any]:
108
- user_input = state.get("user_input", "")
95
+ user_input = state['user_inputs'][0]
96
+ eval_result = state.get('eval_result', None)
97
+
109
98
  system_prompt = self._search_system_prompt(user_input)
99
+ is_system_prompt = True if system_prompt is not None else False
110
100
 
111
101
  general_tools = [] if system_prompt else ["*"]
112
102
  custom_tools = ["*"] if system_prompt else []
113
103
 
114
- next_node = "select_tool" if system_prompt else "exec_task"
104
+ task_plan_score = None
105
+ if eval_result and 'task_plan' in eval_result and 'score' in eval_result['task_plan']:
106
+ task_plan_score = eval_result['task_plan'].get('score', 1.0)
107
+
108
+ function_call_score = None
109
+ if eval_result and 'function_call' in eval_result and 'score' in eval_result['function_call']:
110
+ function_call_score = eval_result['function_call'].get('score', 1.0)
111
+
112
+ super_state = {}
113
+ if task_plan_score and task_plan_score < self.QUALIFIED_RESULT_SCORE:
114
+ next_node = "prompt_optimize"
115
+ super_state = self._prepare_task_retry(state)
116
+ logging.warning(f"****** ReactAgent TASK_RETRY: task_plan_score={task_plan_score} < {self.QUALIFIED_RESULT_SCORE} , "
117
+ f"Start Optimize Prompt ...")
118
+ elif function_call_score and function_call_score < self.QUALIFIED_RESULT_SCORE:
119
+ next_node = "select_tool"
120
+ super_state = self._prepare_task_retry(state)
121
+ logging.warning(f"****** ReactAgent TASK_RETRY: function_call_score={function_call_score} < {self.QUALIFIED_RESULT_SCORE} , "
122
+ f"Select Tool Again ...")
123
+ elif eval_result is not None: # retry condition is not satisfied, end task
124
+ next_node = "end"
125
+ else:
126
+ next_node = "select_tool" if is_system_prompt else "exec_task"
127
+
128
+ logging.info(f"ReactAgent supervisor_node: is_system_prompt={is_system_prompt}, next_node={next_node}")
129
+
130
+ super_state['next_node'] = next_node
131
+ super_state['system_prompt'] = system_prompt
132
+ super_state['custom_tools'] = custom_tools
133
+ super_state['general_tools'] = general_tools
134
+
135
+ return super_state
115
136
 
137
+
138
+ async def _prompt_optimize_node(self, state: TaskState) -> Dict[str, Any]:
139
+ system_prompt = state['system_prompt']
140
+ logging.info("ReactAgent prompt_optimize_node: optimize system prompt")
141
+ # @todo optimize system prompt in future
116
142
  return {
117
143
  'system_prompt' : system_prompt,
118
- 'next_node' : next_node,
119
- 'general_tools' : general_tools,
120
- 'custom_tools' : custom_tools,
121
144
  }
122
145
 
146
+
123
147
  def _select_custom_tools(self, system_prompt: str) -> list[str]:
148
+ # @todo select mcp tool based on system prompt in future
124
149
  custom_tools = ["*"] if system_prompt else []
125
150
  return custom_tools
126
151
 
152
+
127
153
  async def _select_tool_node(self, state: TaskState) -> Dict[str, Any]:
128
154
  system_prompt = state.get('system_prompt',None)
129
155
  general_tools = []
156
+
157
+ logging.info("ReactAgent select_tool_node: select tool based on system_prompt")
130
158
  custom_tools = self._select_custom_tools(system_prompt)
131
159
  return {
132
160
  'general_tools' : general_tools,
133
161
  'custom_tools' : custom_tools,
134
162
  }
135
163
 
164
+
136
165
  async def _exec_task_node(self, state: TaskState) -> Dict[str, Any]:
137
- user_input = state['user_input']
166
+ user_input = state['user_inputs'][0]
138
167
  system_prompt = state.get('system_prompt',None)
139
168
  general_tools = state.get('general_tools',[])
140
169
  custom_tools = state.get('custom_tools',[])
170
+ retry_count = state.get('retry_count', 0)
171
+ task_no = state.get('task_no', 0)
141
172
  is_system_prompt = True if system_prompt is not None else False
142
173
 
143
174
  trace_id = self.graph_langfuse.get_trace_id()
175
+ llm_messages = []
144
176
  try:
145
- logging.info(f"🔥 XGATaskEngine run_task: user_input={user_input}, general_tools={general_tools}, "
177
+ logging.info(f"🔥 ReactAgent exec_task_node: user_input={user_input}, general_tools={general_tools}, "
146
178
  f"custom_tools={custom_tools}, is_system_prompt={is_system_prompt}")
179
+
180
+ # if langgraph resume , must use same task engine
147
181
  if self.task_engine is None:
148
182
  self.task_engine = XGATaskEngine(
149
183
  task_id = state['agent_context']['task_id'],
150
- session_id=state['agent_context'].get('session_id', None),
184
+ task_no = task_no,
185
+ session_id = state['agent_context'].get('session_id', None),
151
186
  user_id = state['agent_context'].get('user_id', None),
152
187
  agent_id = state['agent_context'].get('agent_id', None),
153
188
  tool_box = self.tool_box,
@@ -155,6 +190,7 @@ class XGAReactAgent:
155
190
  custom_tools = custom_tools,
156
191
  system_prompt = system_prompt
157
192
  )
193
+ retry_count += 1
158
194
 
159
195
  chunks = []
160
196
  stream_writer = get_stream_writer()
@@ -164,40 +200,60 @@ class XGAReactAgent:
164
200
  stream_writer({"engine_message": chunk})
165
201
 
166
202
  task_result = self.task_engine.parse_final_result(chunks)
203
+ llm_messages = self.task_engine.get_history_llm_messages()
204
+ task_no += 1 # a task use unique task_no, no matter retry n times
167
205
  except Exception as e:
168
206
  logging.error(f"XReactAgent exec_task_node: Failed to execute task: {e}")
169
207
  task_result = XGATaskResult(type="error", content="Failed to execute task")
170
208
 
171
- iteration_count = state.get('iteration_count', 0) + 1
172
209
  return {
173
- 'task_result' : task_result,
174
- 'iteration_count': iteration_count,
210
+ 'task_result' : task_result,
211
+ 'retry_count' : retry_count,
212
+ 'llm_messages' : llm_messages.copy(),
213
+ 'task_no' : task_no,
175
214
  }
176
215
 
216
+
177
217
  async def _final_result_node(self, state: TaskState) -> Dict[str, Any]:
178
- user_input = state['user_input']
179
- iteration_count = state['iteration_count']
218
+ user_inputs = state['user_inputs']
180
219
  task_result = state['task_result']
220
+ llm_messages = state['llm_messages']
221
+ agent_context = state['agent_context']
222
+ system_prompt = state.get('system_prompt', None)
223
+ retry_count = state['retry_count']
224
+
225
+ is_system_prompt = True if system_prompt is not None else False
181
226
 
182
227
  next_node = "end"
183
- if task_result['type'] == "error" and iteration_count < self.MAX_TASK_RETRY:
184
- next_node = "supervisor"
185
- final_result = None
186
- elif task_result['type'] == "ask":
187
- final_result = task_result
228
+ final_result = task_result
229
+ eval_result = None
230
+ if task_result['type'] == "ask":
188
231
  logging.info(f"XReactAgent final_result_node: ASK_USER_QUESTION: {task_result['content']}")
189
- user_input = interrupt({
190
- 'final_result' : final_result
232
+ ask_input = interrupt({
233
+ 'final_result' : task_result
191
234
  })
192
- logging.info(f"XReactAgent final_result_node: ASK_USER_ANSWER: {user_input}")
235
+ logging.info(f"XReactAgent final_result_node: ASK_USER_ANSWER: {ask_input}")
193
236
  next_node = "exec_task"
194
- else:
195
- final_result = task_result
196
-
237
+ user_inputs.insert(0, ask_input)
238
+ final_result = None
239
+ elif is_system_prompt and retry_count < self.MAX_TASK_RETRY:
240
+ trace_id = self.graph_langfuse.get_trace_id()
241
+ session_id = agent_context.get('session_id', None)
242
+ task_input = ", ".join(reversed(user_inputs))
243
+ eval_result = await self.result_eval_agent.eval_result(task_input, system_prompt, task_result,
244
+ llm_messages, trace_id, session_id)
245
+ if 'task_result' in eval_result and 'score' in eval_result['task_result']:
246
+ score = eval_result['task_result'].get('score', 1.0)
247
+ if score < self.QUALIFIED_RESULT_SCORE:
248
+ next_node = "supervisor"
249
+
250
+ logging.info(f"ReactAgent final_result_node: next_node={next_node}")
251
+
197
252
  return {
198
- 'user_input' : user_input,
199
- 'next_node' : next_node,
200
- 'final_result' : final_result,
253
+ 'user_inputs' : user_inputs,
254
+ 'next_node' : next_node,
255
+ 'final_result' : final_result,
256
+ 'eval_result' : eval_result
201
257
  }
202
258
 
203
259
 
@@ -266,13 +322,15 @@ class XGAReactAgent:
266
322
 
267
323
  except Exception as e:
268
324
  log_trace(e, f"XReactAgent generate: user_input={user_input}")
269
- yield XGATaskResult(type="error", content=f"React Agent error: {e}")
325
+ yield {'type': "error", 'content': f"React Agent generate error: {e}"}
270
326
 
271
327
 
272
- async def _prepare_graph_start(self, user_input, agent_context: AgentContext):
328
+ async def _prepare_graph_start(self, user_input, agent_context: AgentContext)->TaskState:
273
329
  if self.graph is None:
274
330
  self.graph = await self._create_graph()
275
331
 
332
+ self._clear_graph()
333
+
276
334
  agent_context = agent_context or {}
277
335
  task_id = agent_context.get("task_id", f"xga_task_{uuid4()}")
278
336
  agent_context["task_id"] = task_id
@@ -281,20 +339,14 @@ class XGAReactAgent:
281
339
  session_id = agent_context.get('session_id', task_id)
282
340
  agent_context['session_id'] = session_id
283
341
 
284
- graph_input = {
285
- 'messages' : [HumanMessage(content=f"information for: {user_input}")],
286
- 'user_input' : user_input,
287
- 'next_node' : None,
288
- 'agent_context' : agent_context,
289
- 'iteration_count' : 0
290
- }
291
342
 
292
343
  langfuse_handler = self._get_langfuse_handler(agent_context)
293
344
  callbacks = None
294
345
  if langfuse_handler:
295
346
  callbacks = [langfuse_handler]
296
347
  self.graph_langfuse = langfuse_handler.langfuse
297
-
348
+ else:
349
+ self.graph_langfuse = Langfuse(enabled=False)
298
350
 
299
351
  self.graph_config = {
300
352
  'recursion_limit': 100,
@@ -304,6 +356,14 @@ class XGAReactAgent:
304
356
  'callbacks': callbacks
305
357
  }
306
358
 
359
+ graph_input = {
360
+ 'user_inputs' : [user_input],
361
+ 'next_node' : None,
362
+ 'agent_context' : agent_context,
363
+ 'retry_count' : 0,
364
+ 'task_no' : 0
365
+ }
366
+
307
367
  return graph_input
308
368
 
309
369
 
@@ -325,3 +385,21 @@ class XGAReactAgent:
325
385
  return langfuse_handler
326
386
 
327
387
 
388
+ def _clear_graph(self):
389
+ self.graph_config = None
390
+ self.graph_langfuse = None
391
+ self.task_engine: XGATaskEngine = None
392
+
393
+
394
+ def _prepare_task_retry(self, state: TaskState)-> Dict[str, Any]:
395
+ self.task_engine = None
396
+ user_inputs = state['user_inputs']
397
+ task_input = ", ".join(reversed(user_inputs))
398
+
399
+ return {
400
+ 'user_inputs' : [task_input],
401
+ 'llm_messages' : [],
402
+ 'task_result' : None,
403
+ 'final_result' : None,
404
+ 'eval_result' : None,
405
+ }
@@ -0,0 +1,125 @@
1
+ import json
2
+ import logging
3
+ import re
4
+
5
+ from typing import Any, Dict, List, Optional
6
+
7
+ from xgae.engine.engine_base import XGATaskResult
8
+ from xgae.utils.misc import read_file
9
+ from xgae.utils.llm_client import LLMClient, LangfuseMetadata
10
+
11
+
12
+ class TaskResultEvalAgent:
13
+ def __init__(self):
14
+ self.model_client = LLMClient()
15
+ self.prompt_template: str = read_file("templates/example/result_eval_template.txt")
16
+
17
+
18
+ async def eval_result(self,
19
+ task_input: str,
20
+ task_plan: str,
21
+ task_result: XGATaskResult,
22
+ llm_messages: List[Dict[str, Any]],
23
+ trace_id: Optional[str] = None,
24
+ session_id: Optional[str] = None)-> Dict[str, Any]:
25
+ prompt = self._build_prompt(task_input, task_plan, task_result, llm_messages)
26
+ messages = [{"role": "user", "content": prompt}]
27
+
28
+ langfuse_metadata = self._create_llm_langfuse_meta(trace_id, session_id)
29
+
30
+ response = await self.model_client.acompletion(messages, langfuse_metadata)
31
+ response_text = await self.model_client.get_response_result(response)
32
+
33
+ cleaned_text = re.sub(r'^\s*```json|```\s*$', '', response_text, flags=re.MULTILINE).strip()
34
+ eval_result = json.loads(cleaned_text)
35
+
36
+ result_score = eval_result.get('task_result', {}).get('score', -1)
37
+ plan_score = eval_result.get('task_plan', {}).get('score', -1)
38
+ function_score = eval_result.get('function_call', {}).get('score', -1)
39
+
40
+ logging.info(f"FINAL_RESULT_SCORE: task_result_score={result_score}, "
41
+ f"task_plan_score={plan_score}, function_call_score={function_score}")
42
+ return eval_result
43
+
44
+
45
+ def _build_prompt(self, task_input: str, task_plan: str, task_result: XGATaskResult, llm_messages: List[Dict[str, Any]])-> str:
46
+ prompt = self.prompt_template.replace("{task_input}", task_input)
47
+ prompt = prompt.replace("{task_result}", str(task_result))
48
+ llm_process = ""
49
+ function_process = ""
50
+ llm_step = 1
51
+ function_step = 1
52
+ for llm_message in llm_messages:
53
+ content = llm_message.get('content', '')
54
+ if "tool_execution" in content:
55
+ function_process += f"{function_step}. \n"
56
+ tool_exec = json.loads(content)
57
+ func_call = tool_exec['tool_execution']
58
+ func_call.pop('xml_tag_name')
59
+ clear_content = json.dumps(func_call, indent=2)
60
+ function_process += clear_content
61
+ function_process += "\n"
62
+ function_step += 1
63
+ else:
64
+ llm_process += f"{llm_step}. \n"
65
+ llm_process += content
66
+ llm_process += "\n"
67
+ llm_step += 1
68
+
69
+ prompt = prompt.replace("{task_plan}", task_plan)
70
+ prompt = prompt.replace("{llm_process}", llm_process)
71
+ prompt = prompt.replace("{function_process}", function_process)
72
+
73
+ return prompt
74
+
75
+
76
+ def _create_llm_langfuse_meta(self, trace_id:str, session_id: str)-> LangfuseMetadata:
77
+ generation_name = "xga_agent_final_result_completion"
78
+
79
+ return LangfuseMetadata(
80
+ generation_name = generation_name,
81
+ existing_trace_id = trace_id,
82
+ session_id = session_id
83
+ )
84
+
85
+
86
+
87
+ if __name__ == "__main__":
88
+ import asyncio
89
+ from xgae.utils.setup_env import setup_logging
90
+ setup_logging()
91
+
92
+ async def main():
93
+ final_result_agent = TaskResultEvalAgent()
94
+
95
+ task_plan = read_file("templates/example/fault_user_prompt.txt")
96
+ user_input = "locate 10.2.3.4 fault and solution"
97
+
98
+ answer = ("Task Summary: The fault for IP 10.2.3.4 was identified as a Business Recharge Fault (Code: F01), "
99
+ "caused by a Phone Recharge Application Crash. The solution applied was to restart the application. "
100
+ "Key Deliverables: Fault diagnosis and resolution steps. Impact Achieved: Service restored.")
101
+ task_result:XGATaskResult = {'type': "answer", 'content': answer}
102
+
103
+ llm_messages: List[Dict[str, Any]] = [{
104
+ 'content':
105
+ """<function_calls>
106
+ <invoke name="get_alarm_type">
107
+ <parameter name="alarm_id">alm0123</parameter>
108
+ </invoke>
109
+ </function_calls>'""",
110
+ 'role': "assistant"
111
+ },{
112
+ 'content': """{"tool_execution": {
113
+ "function_name": "get_alarm_type",
114
+ "xml_tag_name": "get-alarm-type",
115
+ "arguments": {"alarm_id": "alm0123"},
116
+ "result": {"success": true, "output": "1", "error": null}}}""",
117
+ 'role': 'assistant'
118
+ }]
119
+
120
+ return await final_result_agent.eval_result(user_input, task_plan, task_result, llm_messages)
121
+
122
+
123
+ final_result = asyncio.run(main())
124
+ final_result_json = json.dumps(final_result, ensure_ascii=False, indent=2)
125
+ print(f"FINAL_RESULT: {final_result_json} ")
@@ -1,5 +1,7 @@
1
1
  import asyncio
2
2
 
3
+ from uuid import uuid4
4
+
3
5
  from xgae.utils.setup_env import setup_logging
4
6
 
5
7
  from examples.agent.langgraph.react.react_agent import XGAReactAgent, AgentContext
@@ -9,18 +11,18 @@ async def main():
9
11
  is_stream = True # two mode agent experience
10
12
  task_no = 0
11
13
  user_inputs = [
12
- # "5+5", # For no tool call
13
- # "locate 10.2.3.4 fault and solution", # For custom tool
14
- "locate fault and solution", # For human append input
14
+ #"5+5", # For no tool call
15
+ "locate 10.2.3.4 fault and solution", # For custom tool
16
+ #"locate fault and solution", # For human append input
15
17
  ]
16
18
 
17
19
  for user_input in user_inputs:
18
20
  agent = XGAReactAgent()
19
21
  task_no += 1
20
22
  context: AgentContext = {
21
- 'task_id': f"agent_task_{task_no}",
22
- 'user_id': f"agent_user_{task_no}",
23
- 'agent_id': f"agent_{task_no}",
23
+ 'task_id': f"agent_task_{uuid4()}", # can be set with request_id, must be unique
24
+ 'user_id': "agent_user_1",
25
+ 'agent_id': "agent_1",
24
26
  }
25
27
 
26
28
  is_resume = False
@@ -18,6 +18,7 @@ from xgae.engine.responser.responser_base import TaskResponserContext, TaskRespo
18
18
  class XGATaskEngine:
19
19
  def __init__(self,
20
20
  task_id: Optional[str] = None,
21
+ task_no: Optional[int] = None,
21
22
  session_id: Optional[str] = None,
22
23
  user_id: Optional[str] = None,
23
24
  agent_id: Optional[str] = None,
@@ -28,7 +29,8 @@ class XGATaskEngine:
28
29
  tool_exec_parallel: Optional[bool] = None,
29
30
  llm_config: Optional[LLMConfig] = None,
30
31
  prompt_builder: Optional[XGAPromptBuilder] = None,
31
- tool_box: Optional[XGAToolBox] = None):
32
+ tool_box: Optional[XGAToolBox] = None
33
+ ):
32
34
  self.task_id = task_id if task_id else f"xga_task_{uuid4()}"
33
35
  self.session_id = session_id
34
36
  self.user_id = user_id
@@ -50,7 +52,7 @@ class XGATaskEngine:
50
52
  self.use_assistant_chunk_msg = to_bool(os.getenv('USE_ASSISTANT_CHUNK_MSG', False))
51
53
  self.tool_exec_parallel = True if tool_exec_parallel is None else tool_exec_parallel
52
54
 
53
- self.task_no = -1
55
+ self.task_no = (task_no - 1) if task_no else -1
54
56
  self.task_run_id :str = None
55
57
  self.task_prompt :str = None
56
58
  self.task_langfuse: XGATaskLangFuse = None
@@ -213,7 +215,7 @@ class XGATaskEngine:
213
215
  auto_count = continuous_state.get("auto_continue_count")
214
216
  langfuse_metadata = self.task_langfuse.create_llm_langfuse_meta(auto_count)
215
217
 
216
- llm_response = await self.llm_client.create_completion(llm_messages, langfuse_metadata)
218
+ llm_response = await self.llm_client.acompletion(llm_messages, langfuse_metadata)
217
219
  response_processor = self._create_response_processer()
218
220
 
219
221
  async for chunk in response_processor.process_response(llm_response, llm_messages, continuous_state):
@@ -315,7 +317,7 @@ class XGATaskEngine:
315
317
  def get_history_llm_messages (self) -> List[Dict[str, Any]]:
316
318
  llm_messages = []
317
319
  for message in self.task_response_msgs:
318
- if message['is_llm_message']:
320
+ if message['is_llm_message'] and message['type'] != "assistant_chunk":
319
321
  llm_messages.append(message)
320
322
 
321
323
  response_llm_contents = []