xgae 0.1.19__tar.gz → 0.1.20__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.
Files changed (49) hide show
  1. {xgae-0.1.19 → xgae-0.1.20}/CHANGELOG.md +5 -0
  2. {xgae-0.1.19 → xgae-0.1.20}/PKG-INFO +1 -1
  3. {xgae-0.1.19 → xgae-0.1.20}/pyproject.toml +1 -1
  4. xgae-0.1.20/src/examples/agent/langgraph/react/agent_base.py +31 -0
  5. xgae-0.1.20/src/examples/agent/langgraph/react/final_result_agent.py +119 -0
  6. {xgae-0.1.19 → xgae-0.1.20}/src/examples/agent/langgraph/react/react_agent.py +62 -54
  7. {xgae-0.1.19 → xgae-0.1.20}/src/examples/agent/langgraph/react/run_react_agent.py +8 -6
  8. {xgae-0.1.19 → xgae-0.1.20}/src/xgae/engine/task_engine.py +4 -3
  9. {xgae-0.1.19 → xgae-0.1.20}/src/xgae/engine/task_langfuse.py +6 -4
  10. {xgae-0.1.19 → xgae-0.1.20}/src/xgae/utils/llm_client.py +27 -5
  11. xgae-0.1.20/templates/example/final_result_template.txt +61 -0
  12. {xgae-0.1.19 → xgae-0.1.20}/uv.lock +1 -1
  13. xgae-0.1.19/src/examples/agent/langgraph/react/final_result_agent.py +0 -59
  14. xgae-0.1.19/templates/example/final_result_template.txt +0 -19
  15. {xgae-0.1.19 → xgae-0.1.20}/.env +0 -0
  16. {xgae-0.1.19 → xgae-0.1.20}/.python-version +0 -0
  17. {xgae-0.1.19 → xgae-0.1.20}/README.md +0 -0
  18. {xgae-0.1.19 → xgae-0.1.20}/mcpservers/custom_servers.json +0 -0
  19. {xgae-0.1.19 → xgae-0.1.20}/mcpservers/xga_server.json +0 -0
  20. {xgae-0.1.19 → xgae-0.1.20}/mcpservers/xga_server_sse.json +0 -0
  21. {xgae-0.1.19 → xgae-0.1.20}/src/examples/engine/run_custom_and_agent_tools.py +0 -0
  22. {xgae-0.1.19 → xgae-0.1.20}/src/examples/engine/run_general_tools.py +0 -0
  23. {xgae-0.1.19 → xgae-0.1.20}/src/examples/engine/run_human_in_loop.py +0 -0
  24. {xgae-0.1.19 → xgae-0.1.20}/src/examples/engine/run_simple.py +0 -0
  25. {xgae-0.1.19 → xgae-0.1.20}/src/examples/tools/custom_fault_tools_app.py +0 -0
  26. {xgae-0.1.19 → xgae-0.1.20}/src/examples/tools/simu_a2a_tools_app.py +0 -0
  27. {xgae-0.1.19 → xgae-0.1.20}/src/xgae/__init__.py +0 -0
  28. {xgae-0.1.19 → xgae-0.1.20}/src/xgae/engine/engine_base.py +0 -0
  29. {xgae-0.1.19 → xgae-0.1.20}/src/xgae/engine/mcp_tool_box.py +0 -0
  30. {xgae-0.1.19 → xgae-0.1.20}/src/xgae/engine/prompt_builder.py +0 -0
  31. {xgae-0.1.19 → xgae-0.1.20}/src/xgae/engine/responser/non_stream_responser.py +0 -0
  32. {xgae-0.1.19 → xgae-0.1.20}/src/xgae/engine/responser/responser_base.py +0 -0
  33. {xgae-0.1.19 → xgae-0.1.20}/src/xgae/engine/responser/stream_responser.py +0 -0
  34. {xgae-0.1.19 → xgae-0.1.20}/src/xgae/engine_cli_app.py +0 -0
  35. {xgae-0.1.19 → xgae-0.1.20}/src/xgae/tools/without_general_tools_app.py +0 -0
  36. {xgae-0.1.19 → xgae-0.1.20}/src/xgae/utils/__init__.py +0 -0
  37. {xgae-0.1.19 → xgae-0.1.20}/src/xgae/utils/json_helpers.py +0 -0
  38. {xgae-0.1.19 → xgae-0.1.20}/src/xgae/utils/misc.py +0 -0
  39. {xgae-0.1.19 → xgae-0.1.20}/src/xgae/utils/setup_env.py +0 -0
  40. {xgae-0.1.19 → xgae-0.1.20}/src/xgae/utils/xml_tool_parser.py +0 -0
  41. {xgae-0.1.19 → xgae-0.1.20}/templates/agent_tool_prompt_template.txt +0 -0
  42. {xgae-0.1.19 → xgae-0.1.20}/templates/custom_tool_prompt_template.txt +0 -0
  43. {xgae-0.1.19 → xgae-0.1.20}/templates/example/fault_user_prompt.txt +0 -0
  44. {xgae-0.1.19 → xgae-0.1.20}/templates/gemini_system_prompt_template.txt +0 -0
  45. {xgae-0.1.19 → xgae-0.1.20}/templates/general_tool_prompt_template.txt +0 -0
  46. {xgae-0.1.19 → xgae-0.1.20}/templates/system_prompt_response_sample.txt +0 -0
  47. {xgae-0.1.19 → xgae-0.1.20}/templates/system_prompt_template.txt +0 -0
  48. {xgae-0.1.19 → xgae-0.1.20}/test/test_langfuse.py +0 -0
  49. {xgae-0.1.19 → xgae-0.1.20}/test/test_litellm_langfuse.py +0 -0
@@ -1,4 +1,9 @@
1
1
  # Release Changelog
2
+ ## [0.1.20] - 2025-9-8
3
+ ### Added
4
+ - Example: Langgraph react agent add final_result_agent
5
+
6
+
2
7
  ## [0.1.19] - 2025-9-8
3
8
  ### Added
4
9
  - Example: Langgraph react agent release V1, full logic but no final result agent and tool select agent
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xgae
3
- Version: 0.1.19
3
+ Version: 0.1.20
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.1.20"
4
4
  description = "Extreme General Agent Engine"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -0,0 +1,31 @@
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_input: 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
+ iteration_count: int
29
+ agent_context: AgentContext
30
+
31
+
@@ -0,0 +1,119 @@
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 FinalResultAgent:
13
+ def __init__(self):
14
+ self.model_client = LLMClient()
15
+ self.prompt_template: str = read_file("templates/example/final_result_template.txt")
16
+
17
+
18
+ async def eval_result(self,
19
+ task_input: str,
20
+ task_result: XGATaskResult,
21
+ llm_messages: List[Dict[str, Any]],
22
+ trace_id: Optional[str] = None,
23
+ session_id: Optional[str] = None)-> Dict[str, Any]:
24
+ prompt = self._build_prompt(task_input, task_result, llm_messages)
25
+ messages = [{"role": "user", "content": prompt}]
26
+
27
+ langfuse_metadata = self._create_llm_langfuse_meta(trace_id, session_id)
28
+
29
+ response = await self.model_client.acompletion(messages, langfuse_metadata)
30
+ response_text = await self.model_client.get_response_result(response)
31
+
32
+ cleaned_text = re.sub(r'^\s*```json|```\s*$', '', response_text, flags=re.MULTILINE).strip()
33
+ eval_result = json.loads(cleaned_text)
34
+
35
+ result_score = eval_result.get('task_result', {}).get('score', -1)
36
+ process_score = eval_result.get('task_process', {}).get('score', -1)
37
+ function_score = eval_result.get('function_call', {}).get('score', -1)
38
+
39
+ logging.info(f"FINAL_RESULT_SCORE: task_result_score={result_score}, "
40
+ f"task_process_score={process_score}, function_call_score={function_score}")
41
+ return eval_result
42
+
43
+
44
+ def _build_prompt(self, task_input: str, task_result: XGATaskResult, llm_messages: List[Dict[str, Any]])-> str:
45
+ prompt = self.prompt_template.replace("{task_input}", task_input)
46
+ prompt = prompt.replace("{task_result}", str(task_result))
47
+ llm_process = ""
48
+ function_process = ""
49
+ llm_step = 1
50
+ function_step = 1
51
+ for llm_message in llm_messages:
52
+ content = llm_message.get('content', '')
53
+ if "tool_execution" in content:
54
+ function_process += f"{function_step}. \n"
55
+ tool_exec = json.loads(content)
56
+ func_call = tool_exec['tool_execution']
57
+ func_call.pop('xml_tag_name')
58
+ clear_content = json.dumps(func_call, indent=2)
59
+ function_process += clear_content
60
+ function_process += "\n"
61
+ function_step += 1
62
+ else:
63
+ llm_process += f"{llm_step}. \n"
64
+ llm_process += content
65
+ llm_process += "\n"
66
+ llm_step += 1
67
+
68
+ prompt = prompt.replace("{llm_process}", llm_process)
69
+ prompt = prompt.replace("{function_process}", function_process)
70
+
71
+ return prompt
72
+
73
+
74
+ def _create_llm_langfuse_meta(self, trace_id:str, session_id: str)-> LangfuseMetadata:
75
+ generation_name = "xga_agent_final_result_completion"
76
+
77
+ return LangfuseMetadata(
78
+ generation_name = generation_name,
79
+ existing_trace_id = trace_id,
80
+ session_id = session_id
81
+ )
82
+
83
+
84
+
85
+ if __name__ == "__main__":
86
+ import asyncio
87
+ from xgae.utils.setup_env import setup_logging
88
+ setup_logging()
89
+
90
+ async def main():
91
+ final_result_agent = FinalResultAgent()
92
+
93
+ user_input = "locate 10.2.3.4 fault and solution"
94
+ answer = ("Task Summary: The fault for IP 10.2.3.4 was identified as a Business Recharge Fault (Code: F01), "
95
+ "caused by a Phone Recharge Application Crash. The solution applied was to restart the application. "
96
+ "Key Deliverables: Fault diagnosis and resolution steps. Impact Achieved: Service restored.")
97
+ task_result:XGATaskResult = {'type': "answer", 'content': answer}
98
+ llm_messages: List[Dict[str, Any]] = [{
99
+ 'content':
100
+ """<function_calls>
101
+ <invoke name="get_alarm_type">
102
+ <parameter name="alarm_id">alm0123</parameter>
103
+ </invoke>
104
+ </function_calls>'""",
105
+ 'role': "assistant"
106
+ },{
107
+ 'content': """{"tool_execution": {
108
+ "function_name": "get_alarm_type",
109
+ "xml_tag_name": "get-alarm-type",
110
+ "arguments": {"alarm_id": "alm0123"},
111
+ "result": {"success": true, "output": "1", "error": null}}}""",
112
+ 'role': 'assistant'
113
+ }]
114
+ return await final_result_agent.eval_result(user_input, task_result, llm_messages)
115
+
116
+
117
+ final_result = asyncio.run(main())
118
+ final_result_json = json.dumps(final_result, ensure_ascii=False, indent=2)
119
+ print(f"FINAL_RESULT: {final_result_json} ")
@@ -1,61 +1,42 @@
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.final_result_agent import FinalResultAgent
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.final_result_agent = FinalResultAgent()
39
+
59
40
  async def _create_graph(self) -> StateGraph:
60
41
  try:
61
42
  graph_builder = StateGraph(TaskState)
@@ -105,13 +86,22 @@ class XGAReactAgent:
105
86
  return system_prompt
106
87
 
107
88
  async def _supervisor_node(self, state: TaskState) -> Dict[str, Any]:
108
- user_input = state.get("user_input", "")
89
+ user_input = state['user_input']
90
+ eval_result = state.get('eval_result', None)
91
+
109
92
  system_prompt = self._search_system_prompt(user_input)
110
93
 
111
94
  general_tools = [] if system_prompt else ["*"]
112
95
  custom_tools = ["*"] if system_prompt else []
113
96
 
114
- next_node = "select_tool" if system_prompt else "exec_task"
97
+ if eval_result and 'function_call' in eval_result and 'score' in eval_result['function_call']:
98
+ score = eval_result['function_call'].get('score', 1.0)
99
+ if score < self.QUALIFIED_RESULT_SCORE:
100
+ next_node = "select_tool"
101
+ else:
102
+ next_node = "end"
103
+ else:
104
+ next_node = "select_tool" if system_prompt else "exec_task"
115
105
 
116
106
  return {
117
107
  'system_prompt' : system_prompt,
@@ -141,13 +131,14 @@ class XGAReactAgent:
141
131
  is_system_prompt = True if system_prompt is not None else False
142
132
 
143
133
  trace_id = self.graph_langfuse.get_trace_id()
134
+ llm_messages = []
144
135
  try:
145
136
  logging.info(f"🔥 XGATaskEngine run_task: user_input={user_input}, general_tools={general_tools}, "
146
137
  f"custom_tools={custom_tools}, is_system_prompt={is_system_prompt}")
147
138
  if self.task_engine is None:
148
139
  self.task_engine = XGATaskEngine(
149
140
  task_id = state['agent_context']['task_id'],
150
- session_id=state['agent_context'].get('session_id', None),
141
+ session_id = state['agent_context'].get('session_id', None),
151
142
  user_id = state['agent_context'].get('user_id', None),
152
143
  agent_id = state['agent_context'].get('agent_id', None),
153
144
  tool_box = self.tool_box,
@@ -164,6 +155,7 @@ class XGAReactAgent:
164
155
  stream_writer({"engine_message": chunk})
165
156
 
166
157
  task_result = self.task_engine.parse_final_result(chunks)
158
+ llm_messages = self.task_engine.get_history_llm_messages()
167
159
  except Exception as e:
168
160
  logging.error(f"XReactAgent exec_task_node: Failed to execute task: {e}")
169
161
  task_result = XGATaskResult(type="error", content="Failed to execute task")
@@ -172,32 +164,42 @@ class XGAReactAgent:
172
164
  return {
173
165
  'task_result' : task_result,
174
166
  'iteration_count': iteration_count,
167
+ 'llm_messages' : llm_messages.copy()
175
168
  }
176
169
 
170
+
177
171
  async def _final_result_node(self, state: TaskState) -> Dict[str, Any]:
178
172
  user_input = state['user_input']
179
173
  iteration_count = state['iteration_count']
180
174
  task_result = state['task_result']
175
+ llm_messages = state['llm_messages']
176
+ agent_context = state['agent_context']
181
177
 
182
178
  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
179
+ final_result = task_result
180
+ eval_result = None
181
+ if task_result['type'] == "ask":
188
182
  logging.info(f"XReactAgent final_result_node: ASK_USER_QUESTION: {task_result['content']}")
189
183
  user_input = interrupt({
190
- 'final_result' : final_result
184
+ 'final_result' : task_result
191
185
  })
192
186
  logging.info(f"XReactAgent final_result_node: ASK_USER_ANSWER: {user_input}")
193
187
  next_node = "exec_task"
194
- else:
195
- final_result = task_result
188
+ final_result = None
189
+ elif iteration_count < self.MAX_TASK_RETRY:
190
+ trace_id = self.graph_langfuse.get_trace_id()
191
+ session_id = agent_context.get('session_id', None)
192
+ eval_result = await self.final_result_agent.eval_result(user_input, task_result, llm_messages, trace_id, session_id)
193
+ if "task_result" in eval_result and "score" in eval_result['task_result']:
194
+ score = eval_result['task_result'].get('score', 1.0)
195
+ if score < self.QUALIFIED_RESULT_SCORE:
196
+ next_node = "supervisor"
196
197
 
197
198
  return {
198
- 'user_input' : user_input,
199
- 'next_node' : next_node,
200
- 'final_result' : final_result,
199
+ 'user_input' : user_input,
200
+ 'next_node' : next_node,
201
+ 'final_result' : final_result,
202
+ 'eval_result' : eval_result
201
203
  }
202
204
 
203
205
 
@@ -266,13 +268,15 @@ class XGAReactAgent:
266
268
 
267
269
  except Exception as e:
268
270
  log_trace(e, f"XReactAgent generate: user_input={user_input}")
269
- yield XGATaskResult(type="error", content=f"React Agent error: {e}")
271
+ yield {'type': "error", 'content': f"React Agent generate error: {e}"}
270
272
 
271
273
 
272
- async def _prepare_graph_start(self, user_input, agent_context: AgentContext):
274
+ async def _prepare_graph_start(self, user_input, agent_context: AgentContext)->TaskState:
273
275
  if self.graph is None:
274
276
  self.graph = await self._create_graph()
275
277
 
278
+ self._clear_graph()
279
+
276
280
  agent_context = agent_context or {}
277
281
  task_id = agent_context.get("task_id", f"xga_task_{uuid4()}")
278
282
  agent_context["task_id"] = task_id
@@ -281,20 +285,14 @@ class XGAReactAgent:
281
285
  session_id = agent_context.get('session_id', task_id)
282
286
  agent_context['session_id'] = session_id
283
287
 
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
288
 
292
289
  langfuse_handler = self._get_langfuse_handler(agent_context)
293
290
  callbacks = None
294
291
  if langfuse_handler:
295
292
  callbacks = [langfuse_handler]
296
293
  self.graph_langfuse = langfuse_handler.langfuse
297
-
294
+ else:
295
+ self.graph_langfuse = Langfuse(enabled=False)
298
296
 
299
297
  self.graph_config = {
300
298
  'recursion_limit': 100,
@@ -304,6 +302,13 @@ class XGAReactAgent:
304
302
  'callbacks': callbacks
305
303
  }
306
304
 
305
+ graph_input = {
306
+ 'user_input' : user_input,
307
+ 'next_node' : None,
308
+ 'agent_context' : agent_context,
309
+ 'iteration_count' : 0
310
+ }
311
+
307
312
  return graph_input
308
313
 
309
314
 
@@ -324,4 +329,7 @@ class XGAReactAgent:
324
329
  )
325
330
  return langfuse_handler
326
331
 
327
-
332
+ def _clear_graph(self):
333
+ self.graph_config = None
334
+ self.graph_langfuse = None
335
+ self.task_engine: XGATaskEngine = None
@@ -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
@@ -28,7 +28,8 @@ class XGATaskEngine:
28
28
  tool_exec_parallel: Optional[bool] = None,
29
29
  llm_config: Optional[LLMConfig] = None,
30
30
  prompt_builder: Optional[XGAPromptBuilder] = None,
31
- tool_box: Optional[XGAToolBox] = None):
31
+ tool_box: Optional[XGAToolBox] = None
32
+ ):
32
33
  self.task_id = task_id if task_id else f"xga_task_{uuid4()}"
33
34
  self.session_id = session_id
34
35
  self.user_id = user_id
@@ -213,7 +214,7 @@ class XGATaskEngine:
213
214
  auto_count = continuous_state.get("auto_continue_count")
214
215
  langfuse_metadata = self.task_langfuse.create_llm_langfuse_meta(auto_count)
215
216
 
216
- llm_response = await self.llm_client.create_completion(llm_messages, langfuse_metadata)
217
+ llm_response = await self.llm_client.acompletion(llm_messages, langfuse_metadata)
217
218
  response_processor = self._create_response_processer()
218
219
 
219
220
  async for chunk in response_processor.process_response(llm_response, llm_messages, continuous_state):
@@ -315,7 +316,7 @@ class XGATaskEngine:
315
316
  def get_history_llm_messages (self) -> List[Dict[str, Any]]:
316
317
  llm_messages = []
317
318
  for message in self.task_response_msgs:
318
- if message['is_llm_message']:
319
+ if message['is_llm_message'] and message['type'] != "assistant_chunk":
319
320
  llm_messages.append(message)
320
321
 
321
322
  response_llm_contents = []
@@ -51,10 +51,12 @@ class XGATaskLangFuse:
51
51
  'agent_id' : self.agent_id
52
52
  }
53
53
 
54
- self.root_span = trace.span(id=self.task_run_id,
55
- name=f"{root_span_name}[{self.task_no}]",
56
- input=task_input,
57
- metadata=metadata)
54
+ self.root_span = trace.span(
55
+ id = self.task_run_id,
56
+ name = f"{root_span_name}[{self.task_no}]",
57
+ input = task_input,
58
+ metadata = metadata
59
+ )
58
60
  self.root_span_name = root_span_name
59
61
 
60
62
  logging.info(f"{root_span_name} TASK_INPUT: {task_input}")
@@ -226,7 +226,10 @@ class LLMClient:
226
226
  logging.debug(f"LLMClient: Waiting {delay} seconds before retry llm completion...")
227
227
  await asyncio.sleep(delay)
228
228
 
229
- async def create_completion(self, messages: List[Dict[str, Any]], langfuse_metadata: Optional[LangfuseMetadata]=None) -> Union[ModelResponse, CustomStreamWrapper]:
229
+ async def acompletion(self,
230
+ messages: List[Dict[str, Any]],
231
+ langfuse_metadata: Optional[LangfuseMetadata]=None
232
+ ) -> Union[ModelResponse, CustomStreamWrapper]:
230
233
  complete_params = self._prepare_complete_params(messages)
231
234
  if LLMClient.langfuse_enabled and langfuse_metadata:
232
235
  complete_params["metadata"] = langfuse_metadata
@@ -234,19 +237,38 @@ class LLMClient:
234
237
  last_error = None
235
238
  for attempt in range(self.max_retries):
236
239
  try:
237
- logging.info(f"*** LLMClient create_completion: LLM '{self.model_name}' completion attempt {attempt + 1}/{self.max_retries}")
240
+ logging.info(f"*** LLMClient acompletion: LLM '{self.model_name}' completion attempt {attempt + 1}/{self.max_retries}")
238
241
  response = await litellm.acompletion(**complete_params)
239
242
  return response
240
243
  except (litellm.exceptions.RateLimitError, OpenAIError, json.JSONDecodeError) as e:
241
244
  last_error = e
242
245
  await self._handle_llm_error(e, attempt)
243
246
  except Exception as e:
244
- logging.error(f"LLMClient create_completion: Unexpected error during LLM completion: {str(e)}", exc_info=True)
247
+ logging.error(f"LLMClient acompletion: Unexpected error during LLM completion: {str(e)}", exc_info=True)
245
248
  raise LLMError(f"LLMClient create completion failed: {e}")
246
249
 
247
- logging.error(f"LLMClient create_completion: LLM completion failed after {self.max_retries} attempts: {last_error}", exc_info=True)
250
+ logging.error(f"LLMClient acompletion: LLM completion failed after {self.max_retries} attempts: {last_error}", exc_info=True)
248
251
  raise LLMError(f"LLMClient create completion failed after {self.max_retries} attempts !")
249
252
 
253
+
254
+ async def get_response_result(self, response: Union[ModelResponse, CustomStreamWrapper]) -> str:
255
+ response_text: str = ""
256
+
257
+ if self.is_stream:
258
+ async for chunk in response:
259
+ choices = chunk.get("choices", [{}])
260
+ if not choices:
261
+ continue
262
+ delta = choices[0].get("delta", {})
263
+ content = delta.get("content", "")
264
+ if content:
265
+ response_text += content
266
+ else:
267
+ response_text = response.choices[0].message.content
268
+
269
+ return response_text
270
+
271
+
250
272
  if __name__ == "__main__":
251
273
  from xgae.utils.setup_env import setup_logging
252
274
 
@@ -267,7 +289,7 @@ if __name__ == "__main__":
267
289
  session_id="session_0",
268
290
  )
269
291
 
270
- response = await llm_client.create_completion(messages, meta)
292
+ response = await llm_client.acompletion(messages, meta)
271
293
  if llm_client.is_stream:
272
294
  async for chunk in response:
273
295
  choices = chunk.get("choices", [{}])
@@ -0,0 +1,61 @@
1
+ # Scoring Criteria
2
+ Grading is based on task requirements and task answers. Key scoring elements include:
3
+ 1. The most important scoring element: whether the task answer satisfies the user’s question.
4
+ 2.Final answer requirements:
5
+ - Provide accurate and complete information
6
+ - Contain no factual errors
7
+ - Address all parts of the question
8
+ - Maintain logical consistency
9
+ 3. When scoring, points should be deducted for:
10
+ - Factual errors or inaccurate information
11
+ - Incomplete or partial answers
12
+ - Misleading or unclear statements
13
+ - Logical inconsistencies
14
+ - Missing key information
15
+
16
+
17
+ # Evaluation Dimensions
18
+ 1. Task Result Score: Task outcome evaluation
19
+ - assesses the degree of match between task requirements and task results
20
+ - if task result 'type' is 'error', evaluation score is 0, evaluation reason is empty
21
+ 2.Task Process Score : Task process evaluation
22
+ - assesses whether task planning is reasonable
23
+ - Whether task steps can yield answers to the user’s question
24
+ - Whether task steps can be executed
25
+ - Whether task steps can properly match and call tools
26
+ 3. Function Call Score: Function Call evaluation
27
+ - assesses whether function calls are successful and whether parameter transmission is reasonable
28
+ - if no function call, evaluation score is 1.00, evaluation reason is empty
29
+
30
+
31
+ # Output Format: JSON
32
+ {
33
+ "task_result": { # Task Result Score
34
+ "score": 0.62, # value: 0 ~ 1.00 , using two decimal places
35
+ "reasons": "Evaluation and reasons for deduction regarding task results"
36
+ },
37
+ "task_process": { # Task Process Score
38
+ "score": 0.53, # value: 0 ~ 1.00 , using two decimal places
39
+ "reasons": "Evaluation and reasons for deduction regarding the task process"
40
+ },
41
+ "function_call": { # Function Call Score
42
+ "score": 0.41, # value: 0 ~ 1.00 , using two decimal places
43
+ "reasons": "Evaluation and reasons for deduction regarding tool execution"
44
+ }
45
+ }
46
+
47
+
48
+ # Task Requirements
49
+ {task_input}
50
+
51
+
52
+ # Task Result
53
+ {task_result}
54
+
55
+
56
+ # LLM Procedure
57
+ {llm_process}
58
+
59
+ # Function Call Procedure
60
+ {function_process}
61
+
@@ -1332,7 +1332,7 @@ wheels = [
1332
1332
 
1333
1333
  [[package]]
1334
1334
  name = "xgae"
1335
- version = "0.1.19"
1335
+ version = "0.1.20"
1336
1336
  source = { editable = "." }
1337
1337
  dependencies = [
1338
1338
  { name = "colorlog" },
@@ -1,59 +0,0 @@
1
- import json
2
- import logging
3
- import re
4
- from typing import Any, Dict, List
5
-
6
- from xgae.utils.misc import read_file
7
- from xgae.utils.llm_client import LLMClient, LangfuseMetadata
8
-
9
- class FinalResultAgent:
10
- def __init__(self):
11
- self.model_client = LLMClient()
12
- self.prompt_template: str = read_file("templates/example/final_result_template.txt")
13
-
14
-
15
- async def final_result(self, user_request: str, task_results: str, langfuse_metadata:LangfuseMetadata=None)-> Dict[str, Any]:
16
- prompt = self.prompt_template.replace("{user_request}", user_request)
17
- prompt = prompt.replace("{task_results}", task_results)
18
-
19
- messages = [{"role": "user", "content": prompt}]
20
-
21
- response_text: str = ""
22
- response = await self.model_client.create_completion(
23
- messages,
24
- langfuse_metadata
25
- )
26
- if self.model_client.is_stream:
27
- async for chunk in response:
28
- choices = chunk.get("choices", [{}])
29
- if not choices:
30
- continue
31
- delta = choices[0].get("delta", {})
32
- content = delta.get("content", "")
33
- if content:
34
- response_text += content
35
- else:
36
- response_text = response.choices[0].message.content
37
-
38
- cleaned_text = re.sub(r'^\s*```json|```\s*$', '', response_text, flags=re.MULTILINE).strip()
39
- final_result = json.loads(cleaned_text)
40
- return final_result
41
-
42
-
43
- if __name__ == "__main__":
44
- import asyncio
45
- from xgae.utils.setup_env import setup_logging
46
- setup_logging()
47
-
48
- async def main():
49
- final_result_agent = FinalResultAgent()
50
-
51
- user_input = "locate 10.2.3.4 fault and solution"
52
- answer = ("Task Summary: The fault for IP 10.2.3.4 was identified as a Business Recharge Fault (Code: F01), "
53
- "caused by a Phone Recharge Application Crash. The solution applied was to restart the application. "
54
- "Key Deliverables: Fault diagnosis and resolution steps. Impact Achieved: Service restored.")
55
- return await final_result_agent.final_result(user_input, answer)
56
-
57
-
58
- final_result = asyncio.run(main())
59
- print(f"FINAL_RESULT: {final_result} ")
@@ -1,19 +0,0 @@
1
- #Objective:
2
- Compare the user's requirements with the task execution results and extract the final result for user request format.
3
-
4
- #User Input Requirements:
5
- {user_request}
6
-
7
- #Task Execution Results:
8
- {task_results}
9
-
10
- #Final Result Principles:
11
- 1. Extract the final result solely based on the task execution results; no additional information from other sources should be used.
12
- 2. If task agent is 'mcp_creation_agent', task result is 'output' field when no 'error'
13
- 3. If only part of the requirements are met, include the partially met content in the final result.
14
-
15
- #Final Result Output Format: JSON
16
- {
17
- "final_result_type": 0, // Value explanation: -1: Not met, no final result; 0: All requirements met; 1: Partially met
18
- "formatted_result": "Final result" // If not met at all, provide an empty string
19
- }
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