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.
- {xgae-0.1.19 → xgae-0.2.0}/CHANGELOG.md +15 -1
- {xgae-0.1.19 → xgae-0.2.0}/PKG-INFO +1 -1
- {xgae-0.1.19 → xgae-0.2.0}/pyproject.toml +1 -1
- xgae-0.2.0/src/examples/agent/langgraph/react/agent_base.py +32 -0
- {xgae-0.1.19 → xgae-0.2.0}/src/examples/agent/langgraph/react/react_agent.py +149 -71
- xgae-0.2.0/src/examples/agent/langgraph/react/result_eval_agent.py +125 -0
- {xgae-0.1.19 → xgae-0.2.0}/src/examples/agent/langgraph/react/run_react_agent.py +8 -6
- {xgae-0.1.19 → xgae-0.2.0}/src/xgae/engine/task_engine.py +6 -4
- {xgae-0.1.19 → xgae-0.2.0}/src/xgae/engine/task_langfuse.py +6 -4
- {xgae-0.1.19 → xgae-0.2.0}/src/xgae/utils/llm_client.py +27 -5
- {xgae-0.1.19 → xgae-0.2.0}/templates/agent_tool_prompt_template.txt +1 -0
- {xgae-0.1.19 → xgae-0.2.0}/templates/custom_tool_prompt_template.txt +11 -8
- xgae-0.2.0/templates/example/result_eval_template.txt +66 -0
- {xgae-0.1.19 → xgae-0.2.0}/templates/general_tool_prompt_template.txt +1 -0
- xgae-0.2.0/uv.lock +1463 -0
- xgae-0.1.19/src/examples/agent/langgraph/react/final_result_agent.py +0 -59
- xgae-0.1.19/templates/example/final_result_template.txt +0 -19
- xgae-0.1.19/uv.lock +0 -1463
- {xgae-0.1.19 → xgae-0.2.0}/.env +0 -0
- {xgae-0.1.19 → xgae-0.2.0}/.python-version +0 -0
- {xgae-0.1.19 → xgae-0.2.0}/README.md +0 -0
- {xgae-0.1.19 → xgae-0.2.0}/mcpservers/custom_servers.json +0 -0
- {xgae-0.1.19 → xgae-0.2.0}/mcpservers/xga_server.json +0 -0
- {xgae-0.1.19 → xgae-0.2.0}/mcpservers/xga_server_sse.json +0 -0
- {xgae-0.1.19 → xgae-0.2.0}/src/examples/engine/run_custom_and_agent_tools.py +0 -0
- {xgae-0.1.19 → xgae-0.2.0}/src/examples/engine/run_general_tools.py +0 -0
- {xgae-0.1.19 → xgae-0.2.0}/src/examples/engine/run_human_in_loop.py +0 -0
- {xgae-0.1.19 → xgae-0.2.0}/src/examples/engine/run_simple.py +0 -0
- {xgae-0.1.19 → xgae-0.2.0}/src/examples/tools/custom_fault_tools_app.py +0 -0
- {xgae-0.1.19 → xgae-0.2.0}/src/examples/tools/simu_a2a_tools_app.py +0 -0
- {xgae-0.1.19 → xgae-0.2.0}/src/xgae/__init__.py +0 -0
- {xgae-0.1.19 → xgae-0.2.0}/src/xgae/engine/engine_base.py +0 -0
- {xgae-0.1.19 → xgae-0.2.0}/src/xgae/engine/mcp_tool_box.py +0 -0
- {xgae-0.1.19 → xgae-0.2.0}/src/xgae/engine/prompt_builder.py +0 -0
- {xgae-0.1.19 → xgae-0.2.0}/src/xgae/engine/responser/non_stream_responser.py +0 -0
- {xgae-0.1.19 → xgae-0.2.0}/src/xgae/engine/responser/responser_base.py +0 -0
- {xgae-0.1.19 → xgae-0.2.0}/src/xgae/engine/responser/stream_responser.py +0 -0
- {xgae-0.1.19 → xgae-0.2.0}/src/xgae/engine_cli_app.py +0 -0
- {xgae-0.1.19 → xgae-0.2.0}/src/xgae/tools/without_general_tools_app.py +0 -0
- {xgae-0.1.19 → xgae-0.2.0}/src/xgae/utils/__init__.py +0 -0
- {xgae-0.1.19 → xgae-0.2.0}/src/xgae/utils/json_helpers.py +0 -0
- {xgae-0.1.19 → xgae-0.2.0}/src/xgae/utils/misc.py +0 -0
- {xgae-0.1.19 → xgae-0.2.0}/src/xgae/utils/setup_env.py +0 -0
- {xgae-0.1.19 → xgae-0.2.0}/src/xgae/utils/xml_tool_parser.py +0 -0
- {xgae-0.1.19 → xgae-0.2.0}/templates/example/fault_user_prompt.txt +0 -0
- {xgae-0.1.19 → xgae-0.2.0}/templates/gemini_system_prompt_template.txt +0 -0
- {xgae-0.1.19 → xgae-0.2.0}/templates/system_prompt_response_sample.txt +0 -0
- {xgae-0.1.19 → xgae-0.2.0}/templates/system_prompt_template.txt +0 -0
- {xgae-0.1.19 → xgae-0.2.0}/test/test_langfuse.py +0 -0
- {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
|
|
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
|
|
@@ -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,
|
|
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
|
|
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
|
-
|
|
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
|
-
'
|
|
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
|
|
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
|
-
|
|
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['
|
|
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"🔥
|
|
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
|
-
|
|
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'
|
|
174
|
-
'
|
|
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
|
-
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
190
|
-
'final_result' :
|
|
232
|
+
ask_input = interrupt({
|
|
233
|
+
'final_result' : task_result
|
|
191
234
|
})
|
|
192
|
-
logging.info(f"XReactAgent final_result_node: ASK_USER_ANSWER: {
|
|
235
|
+
logging.info(f"XReactAgent final_result_node: ASK_USER_ANSWER: {ask_input}")
|
|
193
236
|
next_node = "exec_task"
|
|
194
|
-
|
|
195
|
-
final_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
|
-
'
|
|
199
|
-
'next_node'
|
|
200
|
-
'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
|
|
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
|
-
#
|
|
13
|
-
|
|
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_{
|
|
22
|
-
'user_id':
|
|
23
|
-
'agent_id':
|
|
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.
|
|
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 = []
|