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.
- {xgae-0.1.19 → xgae-0.1.20}/CHANGELOG.md +5 -0
- {xgae-0.1.19 → xgae-0.1.20}/PKG-INFO +1 -1
- {xgae-0.1.19 → xgae-0.1.20}/pyproject.toml +1 -1
- xgae-0.1.20/src/examples/agent/langgraph/react/agent_base.py +31 -0
- xgae-0.1.20/src/examples/agent/langgraph/react/final_result_agent.py +119 -0
- {xgae-0.1.19 → xgae-0.1.20}/src/examples/agent/langgraph/react/react_agent.py +62 -54
- {xgae-0.1.19 → xgae-0.1.20}/src/examples/agent/langgraph/react/run_react_agent.py +8 -6
- {xgae-0.1.19 → xgae-0.1.20}/src/xgae/engine/task_engine.py +4 -3
- {xgae-0.1.19 → xgae-0.1.20}/src/xgae/engine/task_langfuse.py +6 -4
- {xgae-0.1.19 → xgae-0.1.20}/src/xgae/utils/llm_client.py +27 -5
- xgae-0.1.20/templates/example/final_result_template.txt +61 -0
- {xgae-0.1.19 → xgae-0.1.20}/uv.lock +1 -1
- 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 → xgae-0.1.20}/.env +0 -0
- {xgae-0.1.19 → xgae-0.1.20}/.python-version +0 -0
- {xgae-0.1.19 → xgae-0.1.20}/README.md +0 -0
- {xgae-0.1.19 → xgae-0.1.20}/mcpservers/custom_servers.json +0 -0
- {xgae-0.1.19 → xgae-0.1.20}/mcpservers/xga_server.json +0 -0
- {xgae-0.1.19 → xgae-0.1.20}/mcpservers/xga_server_sse.json +0 -0
- {xgae-0.1.19 → xgae-0.1.20}/src/examples/engine/run_custom_and_agent_tools.py +0 -0
- {xgae-0.1.19 → xgae-0.1.20}/src/examples/engine/run_general_tools.py +0 -0
- {xgae-0.1.19 → xgae-0.1.20}/src/examples/engine/run_human_in_loop.py +0 -0
- {xgae-0.1.19 → xgae-0.1.20}/src/examples/engine/run_simple.py +0 -0
- {xgae-0.1.19 → xgae-0.1.20}/src/examples/tools/custom_fault_tools_app.py +0 -0
- {xgae-0.1.19 → xgae-0.1.20}/src/examples/tools/simu_a2a_tools_app.py +0 -0
- {xgae-0.1.19 → xgae-0.1.20}/src/xgae/__init__.py +0 -0
- {xgae-0.1.19 → xgae-0.1.20}/src/xgae/engine/engine_base.py +0 -0
- {xgae-0.1.19 → xgae-0.1.20}/src/xgae/engine/mcp_tool_box.py +0 -0
- {xgae-0.1.19 → xgae-0.1.20}/src/xgae/engine/prompt_builder.py +0 -0
- {xgae-0.1.19 → xgae-0.1.20}/src/xgae/engine/responser/non_stream_responser.py +0 -0
- {xgae-0.1.19 → xgae-0.1.20}/src/xgae/engine/responser/responser_base.py +0 -0
- {xgae-0.1.19 → xgae-0.1.20}/src/xgae/engine/responser/stream_responser.py +0 -0
- {xgae-0.1.19 → xgae-0.1.20}/src/xgae/engine_cli_app.py +0 -0
- {xgae-0.1.19 → xgae-0.1.20}/src/xgae/tools/without_general_tools_app.py +0 -0
- {xgae-0.1.19 → xgae-0.1.20}/src/xgae/utils/__init__.py +0 -0
- {xgae-0.1.19 → xgae-0.1.20}/src/xgae/utils/json_helpers.py +0 -0
- {xgae-0.1.19 → xgae-0.1.20}/src/xgae/utils/misc.py +0 -0
- {xgae-0.1.19 → xgae-0.1.20}/src/xgae/utils/setup_env.py +0 -0
- {xgae-0.1.19 → xgae-0.1.20}/src/xgae/utils/xml_tool_parser.py +0 -0
- {xgae-0.1.19 → xgae-0.1.20}/templates/agent_tool_prompt_template.txt +0 -0
- {xgae-0.1.19 → xgae-0.1.20}/templates/custom_tool_prompt_template.txt +0 -0
- {xgae-0.1.19 → xgae-0.1.20}/templates/example/fault_user_prompt.txt +0 -0
- {xgae-0.1.19 → xgae-0.1.20}/templates/gemini_system_prompt_template.txt +0 -0
- {xgae-0.1.19 → xgae-0.1.20}/templates/general_tool_prompt_template.txt +0 -0
- {xgae-0.1.19 → xgae-0.1.20}/templates/system_prompt_response_sample.txt +0 -0
- {xgae-0.1.19 → xgae-0.1.20}/templates/system_prompt_template.txt +0 -0
- {xgae-0.1.19 → xgae-0.1.20}/test/test_langfuse.py +0 -0
- {xgae-0.1.19 → xgae-0.1.20}/test/test_litellm_langfuse.py +0 -0
|
@@ -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,
|
|
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.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
|
|
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
|
-
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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' :
|
|
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
|
-
|
|
195
|
-
|
|
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'
|
|
199
|
-
'next_node'
|
|
200
|
-
'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
|
|
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
|
-
#
|
|
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
|
|
@@ -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.
|
|
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(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
+
|
|
@@ -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
|
-
}
|
{xgae-0.1.19 → xgae-0.1.20}/.env
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|