xgae 0.1.1__py3-none-any.whl → 0.1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of xgae might be problematic. Click here for more details.
- xgae/engine/xga_base.py +5 -2
- xgae/engine/xga_engine.py +57 -27
- xgae/engine/xga_mcp_tool_box.py +41 -21
- xgae/engine/xga_prompt_builder.py +84 -25
- xgae/utils/llm_client.py +2 -5
- xgae/utils/setup_env.py +64 -76
- xgae/utils/utils.py +42 -0
- {xgae-0.1.1.dist-info → xgae-0.1.3.dist-info}/METADATA +1 -1
- xgae-0.1.3.dist-info/RECORD +14 -0
- xgae-0.1.1.dist-info/RECORD +0 -13
- {xgae-0.1.1.dist-info → xgae-0.1.3.dist-info}/WHEEL +0 -0
xgae/engine/xga_base.py
CHANGED
|
@@ -2,7 +2,9 @@ from typing import Union, Optional, Dict, List, Any, Literal
|
|
|
2
2
|
from dataclasses import dataclass
|
|
3
3
|
from abc import ABC, abstractmethod
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
class XGAError(Exception):
|
|
6
|
+
"""Custom exception for errors in the XGA system."""
|
|
7
|
+
pass
|
|
6
8
|
|
|
7
9
|
@dataclass
|
|
8
10
|
class XGAMessage:
|
|
@@ -20,7 +22,8 @@ class XGAToolSchema:
|
|
|
20
22
|
tool_name: str
|
|
21
23
|
server_name: str
|
|
22
24
|
description: str
|
|
23
|
-
input_schema:
|
|
25
|
+
input_schema: Dict[str, Any]
|
|
26
|
+
metadata: Optional[Dict[str, Any]]
|
|
24
27
|
|
|
25
28
|
|
|
26
29
|
@dataclass
|
xgae/engine/xga_engine.py
CHANGED
|
@@ -2,46 +2,74 @@
|
|
|
2
2
|
from typing import List, Any, Dict, Optional, AsyncGenerator
|
|
3
3
|
from uuid import uuid4
|
|
4
4
|
|
|
5
|
-
from xgae.engine.xga_base import XGAMessage,
|
|
5
|
+
from xgae.engine.xga_base import XGAMessage, XGAToolBox
|
|
6
6
|
from xgae.utils.llm_client import LLMClient
|
|
7
7
|
from xgae.utils.setup_env import langfuse
|
|
8
8
|
from xga_prompt_builder import XGAPromptBuilder
|
|
9
9
|
from xga_mcp_tool_box import XGAMcpToolBox
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
|
|
12
|
+
class XGATaskEngine():
|
|
12
13
|
def __init__(self,
|
|
13
14
|
session_id: Optional[str] = None,
|
|
14
|
-
|
|
15
|
+
task_id: Optional[str] = None,
|
|
15
16
|
agent_id: Optional[str] = None,
|
|
17
|
+
trace_id: Optional[str] = None,
|
|
18
|
+
system_prompt: Optional[str] = None,
|
|
16
19
|
llm_config: Optional[Dict[str, Any]] = None,
|
|
17
20
|
prompt_builder: Optional[XGAPromptBuilder] = None,
|
|
18
21
|
tool_box: Optional[XGAToolBox] = None):
|
|
19
22
|
self.session_id = session_id if session_id else f"xga_sid_{uuid4()}"
|
|
23
|
+
self.task_id = task_id if task_id else f"xga_task_{uuid4()}"
|
|
20
24
|
self.agent_id = agent_id
|
|
25
|
+
self.trace_id = trace_id if trace_id else langfuse.create_trace_id()
|
|
21
26
|
|
|
22
27
|
self.messages: List[XGAMessage] = []
|
|
23
28
|
self.llm_client = LLMClient(llm_config)
|
|
24
29
|
self.model_name = self.llm_client.model_name
|
|
25
30
|
self.is_stream = self.llm_client.is_stream
|
|
26
31
|
|
|
27
|
-
self.prompt_builder = prompt_builder or XGAPromptBuilder()
|
|
32
|
+
self.prompt_builder = prompt_builder or XGAPromptBuilder(system_prompt)
|
|
28
33
|
self.tool_box = tool_box or XGAMcpToolBox()
|
|
29
34
|
|
|
30
|
-
|
|
31
|
-
|
|
35
|
+
|
|
36
|
+
async def __async_init__(self, general_tools:List[str], custom_tools: List[str]) -> None:
|
|
37
|
+
await self.tool_box.load_mcp_tools_schema()
|
|
38
|
+
await self.tool_box.creat_task_tool_box(self.task_id, general_tools, custom_tools)
|
|
39
|
+
general_tool_schemas = self.tool_box.get_task_tool_schemas(self.task_id, "general_tool")
|
|
40
|
+
custom_tool_schemas = self.tool_box.get_task_tool_schemas(self.task_id, "custom_tool")
|
|
41
|
+
|
|
42
|
+
self.task_prompt = self.prompt_builder.build_task_prompt(self.model_name, general_tool_schemas, custom_tool_schemas)
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
async def create(cls,
|
|
46
|
+
session_id: Optional[str] = None,
|
|
47
|
+
task_id: Optional[str] = None,
|
|
48
|
+
agent_id: Optional[str] = None,
|
|
49
|
+
trace_id: Optional[str] = None,
|
|
50
|
+
system_prompt: Optional[str] = None,
|
|
51
|
+
general_tools: Optional[List[str]] = None,
|
|
52
|
+
custom_tools: Optional[List[str]] = None,
|
|
53
|
+
llm_config: Optional[Dict[str, Any]] = None,
|
|
54
|
+
prompt_builder: Optional[XGAPromptBuilder] = None,
|
|
55
|
+
tool_box: Optional[XGAToolBox] = None) -> 'XGATaskEngine':
|
|
56
|
+
engine: XGATaskEngine = cls(session_id=session_id,
|
|
57
|
+
task_id=task_id,
|
|
58
|
+
agent_id=agent_id,
|
|
59
|
+
trace_id=trace_id,
|
|
60
|
+
system_prompt=system_prompt,
|
|
61
|
+
llm_config=llm_config,
|
|
62
|
+
prompt_builder=prompt_builder,
|
|
63
|
+
tool_box=tool_box)
|
|
64
|
+
general_tools = general_tools or ["*"]
|
|
65
|
+
custom_tools = custom_tools or []
|
|
66
|
+
await engine.__async_init__(general_tools, custom_tools)
|
|
67
|
+
return engine
|
|
32
68
|
|
|
33
69
|
|
|
34
|
-
async def run_task(self,
|
|
35
|
-
task_messages: List[Dict[str, Any]],
|
|
36
|
-
task_id: Optional[str],
|
|
37
|
-
prompt_template: Optional[str] = None,
|
|
38
|
-
general_tools: Optional[List[str]] = ["*"],
|
|
39
|
-
custom_tools: Optional[List[str]] = []) -> AsyncGenerator:
|
|
70
|
+
async def run_task(self, task_messages: List[Dict[str, Any]]) -> AsyncGenerator:
|
|
40
71
|
try:
|
|
41
|
-
self.
|
|
42
|
-
await self.tool_box.creat_task_tool_box(self.task_id, general_tools, custom_tools)
|
|
43
|
-
system_prompt = await self._build_system_prompt(prompt_template, general_tools, custom_tools)
|
|
44
|
-
yield system_prompt
|
|
72
|
+
yield self.task_prompt
|
|
45
73
|
|
|
46
74
|
finally:
|
|
47
75
|
await self.tool_box.destroy_task_tool_box(self.task_id)
|
|
@@ -50,20 +78,22 @@ class XGAEngine():
|
|
|
50
78
|
def _run_task_once(self):
|
|
51
79
|
pass
|
|
52
80
|
|
|
53
|
-
async def _build_system_prompt(self, prompt_template: str, general_tools: List[str], custom_tools: List[str]) -> str:
|
|
54
|
-
self.task_tool_schemas: Dict[str, XGAToolSchema] = {}
|
|
55
|
-
system_prompt = self.prompt_builder.build_system_prompt(self.model_name, prompt_template)
|
|
56
|
-
|
|
57
|
-
tool_schemas = await self.tool_box.get_task_tool_schemas(self.task_id, "general_tool")
|
|
58
|
-
system_prompt = self.prompt_builder.build_general_tool_prompt(self.model_name, system_prompt, tool_schemas)
|
|
59
|
-
|
|
60
|
-
tool_schemas = await self.tool_box.get_task_tool_schemas(self.task_id, "general_tool")
|
|
61
|
-
system_prompt = self.prompt_builder.build_custom_tool_prompt(self.model_name, system_prompt, tool_schemas)
|
|
62
|
-
|
|
63
|
-
return system_prompt
|
|
64
81
|
|
|
65
82
|
def add_message(self, message: XGAMessage):
|
|
66
83
|
message.message_id = f"xga_msg_{uuid4()}"
|
|
67
84
|
message.session_id = self.session_id
|
|
68
85
|
message.agent_id = self.agent_id
|
|
69
86
|
self.messages.append(message)
|
|
87
|
+
|
|
88
|
+
if __name__ == "__main__":
|
|
89
|
+
import asyncio
|
|
90
|
+
|
|
91
|
+
async def main():
|
|
92
|
+
tool_box = XGAMcpToolBox(custom_mcp_server_file="mcpservers/custom_servers.json")
|
|
93
|
+
engine = await XGATaskEngine.create(tool_box=tool_box, custom_tools=["bomc_fault.*"])
|
|
94
|
+
# engine = await XGATaskEngine.create()
|
|
95
|
+
|
|
96
|
+
async for chunk in engine.run_task(task_messages=[{}]):
|
|
97
|
+
print(chunk)
|
|
98
|
+
|
|
99
|
+
asyncio.run(main())
|
xgae/engine/xga_mcp_tool_box.py
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import logging
|
|
3
3
|
import os
|
|
4
|
+
|
|
4
5
|
from typing import List, Any, Dict, Optional, Literal, override
|
|
5
6
|
|
|
6
7
|
from langchain_mcp_adapters.client import MultiServerMCPClient
|
|
7
8
|
from langchain_mcp_adapters.tools import load_mcp_tools
|
|
8
9
|
|
|
9
|
-
from xgae.engine.xga_base import XGAToolSchema, XGAToolBox, XGAToolResult
|
|
10
|
-
from xgae.utils.setup_env import
|
|
11
|
-
|
|
10
|
+
from xgae.engine.xga_base import XGAError, XGAToolSchema, XGAToolBox, XGAToolResult
|
|
11
|
+
from xgae.utils.setup_env import langfuse
|
|
12
12
|
|
|
13
13
|
class XGAMcpToolBox(XGAToolBox):
|
|
14
14
|
GENERAL_MCP_SERVER_NAME = "xga_general"
|
|
15
15
|
|
|
16
16
|
def __init__(self,
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
custom_mcp_server_file: Optional[str] = None,
|
|
18
|
+
custom_mcp_server_config: Optional[Dict[str, Any]] = None
|
|
19
|
+
):
|
|
19
20
|
general_mcp_server_config = self._load_mcp_servers_config("mcpservers/xga_server.json")
|
|
20
21
|
tool_box_mcp_server_config = general_mcp_server_config.get("mcpServers", {})
|
|
21
22
|
|
|
@@ -31,6 +32,8 @@ class XGAMcpToolBox(XGAToolBox):
|
|
|
31
32
|
self.mcp_tool_schemas: Dict[str, List[XGAToolSchema]] = {}
|
|
32
33
|
self.task_tool_schemas: Dict[str, Dict[str,XGAToolSchema]] = {}
|
|
33
34
|
|
|
35
|
+
self.is_loaded_tool_schemas = False
|
|
36
|
+
|
|
34
37
|
@override
|
|
35
38
|
async def creat_task_tool_box(self, task_id: str, general_tools: List[str], custom_tools: List[str]):
|
|
36
39
|
task_tool_schemas = {}
|
|
@@ -41,7 +44,7 @@ class XGAMcpToolBox(XGAToolBox):
|
|
|
41
44
|
for tool_schema in general_tool_schemas:
|
|
42
45
|
if tool_schema.tool_name in general_tools:
|
|
43
46
|
task_tool_schemas[tool_schema.tool_name] = tool_schema
|
|
44
|
-
task_tool_schemas.pop("end_task")
|
|
47
|
+
task_tool_schemas.pop("end_task", None)
|
|
45
48
|
|
|
46
49
|
for server_tool_name in custom_tools:
|
|
47
50
|
parts = server_tool_name.split(".")
|
|
@@ -122,20 +125,35 @@ class XGAMcpToolBox(XGAToolBox):
|
|
|
122
125
|
|
|
123
126
|
return result
|
|
124
127
|
|
|
128
|
+
|
|
125
129
|
async def load_mcp_tools_schema(self)-> None:
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
input_schema =
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
130
|
+
if not self.is_loaded_tool_schemas:
|
|
131
|
+
for server_name in self.mcp_server_names:
|
|
132
|
+
self.mcp_tool_schemas[server_name] = []
|
|
133
|
+
mcp_tools = await self._mcp_client.get_tools(server_name=server_name)
|
|
134
|
+
|
|
135
|
+
for tool in mcp_tools:
|
|
136
|
+
input_schema = tool.args_schema
|
|
137
|
+
if server_name == self.GENERAL_MCP_SERVER_NAME:
|
|
138
|
+
input_schema['properties'].pop("task_id", None)
|
|
139
|
+
if 'task_id' in input_schema['required']:
|
|
140
|
+
input_schema['required'].remove('task_id')
|
|
141
|
+
params_properties = input_schema.get("properties", {})
|
|
142
|
+
for param_properties in params_properties.values():
|
|
143
|
+
param_properties.pop("title", None)
|
|
144
|
+
|
|
145
|
+
metadata = tool.metadata or {}
|
|
146
|
+
tool_schema = XGAToolSchema(tool_name=tool.name,
|
|
147
|
+
server_name=server_name,
|
|
148
|
+
description=tool.description,
|
|
149
|
+
input_schema=input_schema,
|
|
150
|
+
metadata=metadata)
|
|
151
|
+
self.mcp_tool_schemas[server_name].append(tool_schema)
|
|
152
|
+
self.is_loaded_tool_schemas = True
|
|
153
|
+
|
|
154
|
+
async def reload_mcp_tools_schema(self) -> None:
|
|
155
|
+
self.is_loaded_tool_schemas = False
|
|
156
|
+
await self.load_mcp_tools_schema()
|
|
139
157
|
|
|
140
158
|
@staticmethod
|
|
141
159
|
def _load_mcp_servers_config(mcp_config_path: str) -> Dict[str, Any]:
|
|
@@ -164,10 +182,11 @@ class XGAMcpToolBox(XGAToolBox):
|
|
|
164
182
|
if __name__ == "__main__":
|
|
165
183
|
import asyncio
|
|
166
184
|
from dataclasses import asdict
|
|
185
|
+
|
|
167
186
|
async def main():
|
|
168
187
|
task_id = "task1"
|
|
169
|
-
mcp_tool_box = XGAMcpToolBox(custom_mcp_server_file="mcpservers/custom_servers.json")
|
|
170
|
-
|
|
188
|
+
#mcp_tool_box = XGAMcpToolBox(custom_mcp_server_file="mcpservers/custom_servers.json")
|
|
189
|
+
mcp_tool_box = XGAMcpToolBox()
|
|
171
190
|
await mcp_tool_box.load_mcp_tools_schema()
|
|
172
191
|
await mcp_tool_box.creat_task_tool_box(task_id=task_id, general_tools=["*"], custom_tools=["bomc_fault.*"])
|
|
173
192
|
tool_schemas = mcp_tool_box.get_task_tool_schemas(task_id, "general_tool")
|
|
@@ -189,4 +208,5 @@ if __name__ == "__main__":
|
|
|
189
208
|
print(f"call complete result: {result}")
|
|
190
209
|
|
|
191
210
|
await mcp_tool_box.destroy_task_tool_box(task_id)
|
|
211
|
+
|
|
192
212
|
asyncio.run(main())
|
|
@@ -1,38 +1,97 @@
|
|
|
1
|
+
import json
|
|
1
2
|
import datetime
|
|
2
|
-
|
|
3
3
|
from typing import Optional, List
|
|
4
4
|
|
|
5
|
-
from xga_base import XGAToolSchema
|
|
6
|
-
from xgae.utils.
|
|
5
|
+
from xga_base import XGAToolSchema, XGAError
|
|
6
|
+
from xgae.utils.utils import read_file, format_file_with_args
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class XGAPromptBuilder():
|
|
10
|
-
def __init__(self,
|
|
11
|
-
|
|
12
|
-
prompt_template_file: Optional[str] = None):
|
|
13
|
-
self.system_prompt_template = None
|
|
14
|
-
if prompt_template:
|
|
15
|
-
self.system_prompt_template = prompt_template
|
|
16
|
-
elif prompt_template_file:
|
|
17
|
-
self.system_prompt_template = read_file(prompt_template_file)
|
|
18
|
-
else:
|
|
19
|
-
_system_prompt_template = read_file("templates/system_prompt_template.txt")
|
|
20
|
-
self.system_prompt_template = _system_prompt_template.format(
|
|
21
|
-
current_date=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d'),
|
|
22
|
-
current_time=datetime.datetime.now(datetime.timezone.utc).strftime('%H:%M:%S'),
|
|
23
|
-
current_year=datetime.datetime.now(datetime.timezone.utc).strftime('%Y')
|
|
24
|
-
)
|
|
10
|
+
def __init__(self, system_prompt: Optional[str] = None):
|
|
11
|
+
self.system_prompt = system_prompt
|
|
25
12
|
|
|
13
|
+
def build_task_prompt(self, model_name: str, general_tool_schemas: List[XGAToolSchema], custom_tool_schemas: List[XGAToolSchema])-> str:
|
|
14
|
+
if self.system_prompt is None:
|
|
15
|
+
self.system_prompt = self._load_default_system_prompt(model_name)
|
|
26
16
|
|
|
27
|
-
|
|
28
|
-
system_prompt = prompt_template if prompt_template else self.system_prompt_template
|
|
17
|
+
task_prompt = self.system_prompt
|
|
29
18
|
|
|
30
|
-
|
|
19
|
+
tool_prompt = self.build_general_tool_prompt(general_tool_schemas)
|
|
20
|
+
task_prompt = task_prompt + "\n" + tool_prompt
|
|
21
|
+
|
|
22
|
+
tool_prompt = self.build_custom_tool_prompt(custom_tool_schemas)
|
|
23
|
+
task_prompt = task_prompt + "\n" + tool_prompt
|
|
24
|
+
|
|
25
|
+
return task_prompt
|
|
26
|
+
|
|
27
|
+
def build_general_tool_prompt(self, tool_schemas:List[XGAToolSchema])-> str:
|
|
28
|
+
tool_prompt = ""
|
|
29
|
+
tool_schemas = tool_schemas or []
|
|
30
|
+
if len(tool_schemas) > 0:
|
|
31
|
+
tool_prompt = read_file("templates/general_tool_prompt_template.txt")
|
|
32
|
+
example_prompt = ""
|
|
33
|
+
openai_schemas = []
|
|
34
|
+
for tool_schema in tool_schemas:
|
|
35
|
+
openai_schema = {}
|
|
36
|
+
openai_schema["type"] = "function"
|
|
37
|
+
openai_function = {}
|
|
38
|
+
openai_schema["function"] = openai_function
|
|
39
|
+
|
|
40
|
+
openai_function["name"] = tool_schema.tool_name
|
|
41
|
+
openai_function["description"] = tool_schema.description if tool_schema.description else 'No description available'
|
|
42
|
+
|
|
43
|
+
openai_parameters = {}
|
|
44
|
+
input_schema = tool_schema.input_schema
|
|
45
|
+
openai_function["parameters"] = openai_parameters
|
|
46
|
+
openai_parameters["type"] = input_schema["type"]
|
|
47
|
+
openai_parameters["properties"] = input_schema.get("properties", {})
|
|
48
|
+
openai_parameters["required"] = input_schema["required"]
|
|
49
|
+
|
|
50
|
+
openai_schemas.append(openai_schema)
|
|
51
|
+
|
|
52
|
+
metadata = tool_schema.metadata or {}
|
|
53
|
+
example = metadata.get("example", None)
|
|
54
|
+
if example:
|
|
55
|
+
example_prompt += f"\n{example}\n"
|
|
31
56
|
|
|
57
|
+
schema_prompt = json.dumps(openai_schemas, ensure_ascii=False, indent=2)
|
|
58
|
+
tool_prompt = tool_prompt.format(tool_schemas=schema_prompt, tool_examples=example_prompt)
|
|
59
|
+
return tool_prompt
|
|
32
60
|
|
|
33
|
-
|
|
34
|
-
|
|
61
|
+
|
|
62
|
+
def build_custom_tool_prompt(self, tool_schemas:List[XGAToolSchema])-> str:
|
|
63
|
+
tool_prompt = ""
|
|
64
|
+
tool_schemas = tool_schemas or []
|
|
65
|
+
if len(tool_schemas) > 0:
|
|
66
|
+
tool_prompt = read_file("templates/custom_tool_prompt_template.txt")
|
|
67
|
+
tool_info = ""
|
|
68
|
+
for tool_schema in tool_schemas:
|
|
69
|
+
description = tool_schema.description if tool_schema.description else 'No description available'
|
|
70
|
+
tool_info += f"- **{tool_schema.tool_name}**: {description}\n"
|
|
71
|
+
parameters = tool_schema.input_schema.get("properties", {})
|
|
72
|
+
tool_info += f" Parameters: {parameters}\n"
|
|
73
|
+
tool_prompt = tool_prompt.replace("{tool_schemas}", tool_info)
|
|
74
|
+
|
|
75
|
+
return tool_prompt
|
|
76
|
+
|
|
77
|
+
def _load_default_system_prompt(self, model_name) -> Optional[str]:
|
|
78
|
+
if "gemini-2.5-flash" in model_name.lower() and "gemini-2.5-pro" not in model_name.lower():
|
|
79
|
+
system_prompt_template = read_file("templates/gemini_system_prompt_template.txt")
|
|
80
|
+
else:
|
|
81
|
+
system_prompt_template = read_file("templates/system_prompt_template.txt")
|
|
82
|
+
|
|
83
|
+
system_prompt = format_file_with_args(system_prompt_template, {"datetime": datetime})
|
|
84
|
+
|
|
85
|
+
system_prompt = system_prompt.format(
|
|
86
|
+
current_date=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d'),
|
|
87
|
+
current_time=datetime.datetime.now(datetime.timezone.utc).strftime('%H:%M:%S'),
|
|
88
|
+
current_year=datetime.datetime.now(datetime.timezone.utc).strftime('%Y')
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
if "anthropic" not in model_name.lower():
|
|
92
|
+
sample_response = read_file("templates/system_prompt_response_sample.txt")
|
|
93
|
+
system_prompt = system_prompt + "\n\n <sample_assistant_response>" + sample_response + "</sample_assistant_response>"
|
|
94
|
+
|
|
95
|
+
return system_prompt
|
|
35
96
|
|
|
36
97
|
|
|
37
|
-
def build_custom_tool_prompt(self, model_name:str, prompt_template: str, tool_schemas:List[XGAToolSchema])-> str:
|
|
38
|
-
pass
|
xgae/utils/llm_client.py
CHANGED
|
@@ -9,8 +9,6 @@ from typing import Union, Dict, Any, Optional, List
|
|
|
9
9
|
from litellm.utils import ModelResponse, CustomStreamWrapper
|
|
10
10
|
from openai import OpenAIError
|
|
11
11
|
|
|
12
|
-
from setup_env import setup_xga_env
|
|
13
|
-
|
|
14
12
|
|
|
15
13
|
class LLMError(Exception):
|
|
16
14
|
"""Base exception for LLM-related errors."""
|
|
@@ -20,7 +18,7 @@ class LLMClient:
|
|
|
20
18
|
RATE_LIMIT_DELAY = 30
|
|
21
19
|
RETRY_DELAY = 0.1
|
|
22
20
|
|
|
23
|
-
def __init__(self, llm_config: Optional[Dict[str, Any]]=
|
|
21
|
+
def __init__(self, llm_config: Optional[Dict[str, Any]]=None) -> None:
|
|
24
22
|
"""
|
|
25
23
|
Arg: llm_config (Optional[Dict[str, Any]], optional)
|
|
26
24
|
model: Override default model to use, default set by .env LLM_MODEL
|
|
@@ -36,6 +34,7 @@ class LLMClient:
|
|
|
36
34
|
reasoning_effort: Optional level of reasoning effort, default is ‘low’
|
|
37
35
|
top_p: Optional Top-p sampling parameter, default is None
|
|
38
36
|
"""
|
|
37
|
+
llm_config = llm_config or {}
|
|
39
38
|
litellm.modify_params = True
|
|
40
39
|
litellm.drop_params = True
|
|
41
40
|
|
|
@@ -214,8 +213,6 @@ class LLMClient:
|
|
|
214
213
|
raise LLMError(f"LLM completion failed after {self.max_retries} attempts !")
|
|
215
214
|
|
|
216
215
|
if __name__ == "__main__":
|
|
217
|
-
setup_xga_env()
|
|
218
|
-
|
|
219
216
|
async def llm_completion():
|
|
220
217
|
llm_client = LLMClient({
|
|
221
218
|
"stream": False #default is True
|
xgae/utils/setup_env.py
CHANGED
|
@@ -1,107 +1,95 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import os
|
|
3
|
-
import traceback
|
|
4
3
|
|
|
5
4
|
from langfuse import Langfuse
|
|
6
5
|
|
|
6
|
+
_log_initialized = False
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
def setup_logging() -> None:
|
|
9
|
+
global _log_initialized
|
|
10
|
+
if not _log_initialized:
|
|
11
|
+
import colorlog
|
|
12
|
+
from dotenv import load_dotenv
|
|
13
|
+
load_dotenv()
|
|
11
14
|
|
|
12
|
-
|
|
15
|
+
env_log_level = os.getenv("LOG_LEVEL", "INFO")
|
|
16
|
+
env_log_file = os.getenv("LOG_FILE", "log/xga.log")
|
|
17
|
+
log_level = getattr(logging, env_log_level.upper(), logging.INFO)
|
|
13
18
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if env_public_key and env_secret_key:
|
|
20
|
-
langfuse = Langfuse(tracing_enabled=True,
|
|
21
|
-
public_key=env_public_key,
|
|
22
|
-
secret_key=env_secret_key,
|
|
23
|
-
host=env_host)
|
|
24
|
-
logging.info("utils.setup_langfuse: Langfuse initialized!")
|
|
25
|
-
else:
|
|
26
|
-
langfuse = Langfuse(tracing_enabled=False)
|
|
27
|
-
logging.warning("utils.setup_langfuse: Langfuse is disabled!")
|
|
19
|
+
log_dir = os.path.dirname(env_log_file)
|
|
20
|
+
if log_dir and not os.path.exists(log_dir):
|
|
21
|
+
os.makedirs(log_dir, exist_ok=True)
|
|
22
|
+
else:
|
|
23
|
+
os.remove(env_log_file)
|
|
28
24
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
log_level = getattr(logging, env_log_level.upper(), logging.INFO)
|
|
25
|
+
logger = logging.getLogger()
|
|
26
|
+
for handler in logger.handlers[:]:
|
|
27
|
+
logger.removeHandler(handler)
|
|
33
28
|
|
|
34
|
-
log_dir = os.path.dirname(env_log_file)
|
|
35
|
-
if log_dir and not os.path.exists(log_dir):
|
|
36
|
-
os.makedirs(log_dir, exist_ok=True)
|
|
37
|
-
else:
|
|
38
|
-
os.remove(env_log_file)
|
|
39
29
|
|
|
40
|
-
logger = logging.getLogger()
|
|
41
|
-
for handler in logger.handlers[:]:
|
|
42
|
-
logger.removeHandler(handler)
|
|
43
30
|
|
|
44
|
-
|
|
31
|
+
log_colors = {
|
|
32
|
+
'DEBUG': 'cyan',
|
|
33
|
+
'INFO': 'green',
|
|
34
|
+
'WARNING': 'yellow',
|
|
35
|
+
'ERROR': 'red',
|
|
36
|
+
'CRITICAL': 'red,bg_white'
|
|
37
|
+
}
|
|
45
38
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
'ERROR': 'red',
|
|
51
|
-
'CRITICAL': 'red,bg_white'
|
|
52
|
-
}
|
|
39
|
+
console_formatter = colorlog.ColoredFormatter('%(log_color)s%(asctime)s - %(levelname)-8s%(reset)s %(white)s%(message)s',
|
|
40
|
+
log_colors=log_colors,
|
|
41
|
+
datefmt='%Y-%m-%d %H:%M:%S'
|
|
42
|
+
)
|
|
53
43
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
44
|
+
file_formatter = logging.Formatter(
|
|
45
|
+
'%(asctime)s -%(levelname)-8s %(message)s',
|
|
46
|
+
datefmt='%Y-%m-%d %H:%M:%S'
|
|
47
|
+
)
|
|
58
48
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
datefmt='%Y-%m-%d %H:%M:%S'
|
|
62
|
-
)
|
|
49
|
+
console_handler = logging.StreamHandler()
|
|
50
|
+
console_handler.setFormatter(console_formatter)
|
|
63
51
|
|
|
64
|
-
|
|
65
|
-
|
|
52
|
+
file_handler = logging.FileHandler(env_log_file, encoding='utf-8')
|
|
53
|
+
file_handler.setFormatter(file_formatter)
|
|
66
54
|
|
|
67
|
-
|
|
68
|
-
|
|
55
|
+
logger.addHandler(console_handler)
|
|
56
|
+
logger.addHandler(file_handler)
|
|
69
57
|
|
|
70
|
-
|
|
71
|
-
logger.addHandler(file_handler)
|
|
58
|
+
logger.setLevel(log_level)
|
|
72
59
|
|
|
73
|
-
|
|
60
|
+
logging.info(f"Logger is initialized, log_level={env_log_level}, log_file={env_log_file}")
|
|
74
61
|
|
|
75
|
-
|
|
62
|
+
_log_initialized = True
|
|
76
63
|
|
|
64
|
+
setup_logging()
|
|
77
65
|
|
|
78
|
-
|
|
79
|
-
logging.error("An error occurred: %s", str(e))
|
|
80
|
-
logging.error("Traceback details:\n%s", traceback.format_exc())
|
|
81
|
-
raise (e) from e
|
|
66
|
+
_langfuse_initialized = False
|
|
82
67
|
|
|
83
|
-
def
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
68
|
+
def setup_langfuse() -> Langfuse:
|
|
69
|
+
global _langfuse_initialized
|
|
70
|
+
_langfuse = None
|
|
71
|
+
if not _langfuse_initialized:
|
|
72
|
+
env_public_key = os.getenv("LANGFUSE_PUBLIC_KEY")
|
|
73
|
+
env_secret_key = os.getenv("LANGFUSE_SECRET_KEY")
|
|
74
|
+
env_host = os.getenv("LANGFUSE_HOST", "https://cloud.langfuse.com")
|
|
75
|
+
if env_public_key and env_secret_key:
|
|
76
|
+
_langfuse = Langfuse(tracing_enabled=True,
|
|
77
|
+
public_key=env_public_key,
|
|
78
|
+
secret_key=env_secret_key,
|
|
79
|
+
host=env_host)
|
|
80
|
+
logging.info("Langfuse initialized Successfully by Key !")
|
|
81
|
+
else:
|
|
82
|
+
_langfuse = Langfuse(tracing_enabled=False)
|
|
83
|
+
logging.warning("Not set key, Langfuse is disabled!")
|
|
87
84
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
except Exception as e:
|
|
93
|
-
logging.error(f"Read file '{file_path}' failed")
|
|
94
|
-
handle_error(e)
|
|
85
|
+
_langfuse_initialized = True
|
|
86
|
+
return _langfuse
|
|
87
|
+
|
|
88
|
+
langfuse: Langfuse = Langfuse if _langfuse_initialized else setup_langfuse()
|
|
95
89
|
|
|
96
|
-
def setup_xga_env() -> None:
|
|
97
|
-
from dotenv import load_dotenv
|
|
98
|
-
load_dotenv()
|
|
99
|
-
setup_logging()
|
|
100
|
-
setup_langfuse()
|
|
101
90
|
|
|
102
91
|
if __name__ == "__main__":
|
|
103
92
|
try:
|
|
104
|
-
setup_xga_env()
|
|
105
93
|
trace_id = langfuse.create_trace_id()
|
|
106
94
|
print(f"trace_id={trace_id}")
|
|
107
95
|
except Exception as e:
|
xgae/utils/utils.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import datetime
|
|
5
|
+
|
|
6
|
+
from typing import Any, Dict
|
|
7
|
+
|
|
8
|
+
def handle_error(e: Exception) -> None:
|
|
9
|
+
import traceback
|
|
10
|
+
|
|
11
|
+
logging.error("An error occurred: %s", str(e))
|
|
12
|
+
logging.error("Traceback details:\n%s", traceback.format_exc())
|
|
13
|
+
raise (e) from e
|
|
14
|
+
|
|
15
|
+
def read_file(file_path: str) -> str:
|
|
16
|
+
if not os.path.exists(file_path):
|
|
17
|
+
logging.error(f"File '{file_path}' not found")
|
|
18
|
+
raise Exception(f"File '{file_path}' not found")
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
with open(file_path, "r", encoding="utf-8") as template_file:
|
|
22
|
+
content = template_file.read()
|
|
23
|
+
return content
|
|
24
|
+
except Exception as e:
|
|
25
|
+
logging.error(f"Read file '{file_path}' failed")
|
|
26
|
+
handle_error(e)
|
|
27
|
+
|
|
28
|
+
def format_file_with_args(file_content:str, args: Dict[str, Any])-> str:
|
|
29
|
+
from io import StringIO
|
|
30
|
+
|
|
31
|
+
formated = file_content
|
|
32
|
+
original_stdout = sys.stdout
|
|
33
|
+
buffer = StringIO()
|
|
34
|
+
sys.stdout = buffer
|
|
35
|
+
try:
|
|
36
|
+
code = f"print(f\"\"\"{file_content}\"\"\")"
|
|
37
|
+
exec(code, args)
|
|
38
|
+
formated = buffer.getvalue()
|
|
39
|
+
finally:
|
|
40
|
+
sys.stdout = original_stdout
|
|
41
|
+
|
|
42
|
+
return formated
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
xgae/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
xgae/engine/xga_base.py,sha256=b9FnBbCuO4lCAIBBhnedtRk49Dl3M6cBn5h_Wkzw4o0,1345
|
|
3
|
+
xgae/engine/xga_engine.py,sha256=kci7pvF8NwAYJ3Y6DS0JKLkPEQERZwcwRzjcAFQx_ec,4169
|
|
4
|
+
xgae/engine/xga_mcp_tool_box.py,sha256=coyQy7tj2YTBpOc0JKmVBJO8ItO4Z6s1pdbucHd85qg,9762
|
|
5
|
+
xgae/engine/xga_prompt_builder.py,sha256=RuTvQCNufqxDwVvSOPXR0qxAc42cG7NuIaUy9amu66A,4351
|
|
6
|
+
xgae/engine/responser/xga_non_stream_responser.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
xgae/engine/responser/xga_responser_utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
xgae/engine/responser/xga_stream_reponser.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
xgae/utils/llm_client.py,sha256=UMMK84psLsx36-Nn6Q8X1hl9wd-OdaS9ZhxbRjwNCr0,12149
|
|
10
|
+
xgae/utils/setup_env.py,sha256=vY2wnq5KMXQ2dx2wAPeTpudp7b-fvgzenkwHpgd8vuI,2951
|
|
11
|
+
xgae/utils/utils.py,sha256=cCYmWjKFksZ8BRD1YYnaM_jTLVHAg1ibEdjsczEUO6k,1134
|
|
12
|
+
xgae-0.1.3.dist-info/METADATA,sha256=t4sr4k9pbVAcicIRjoqt327eWtSC0uAeo2pDB5LPjs0,309
|
|
13
|
+
xgae-0.1.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
14
|
+
xgae-0.1.3.dist-info/RECORD,,
|
xgae-0.1.1.dist-info/RECORD
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
xgae/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
xgae/engine/xga_base.py,sha256=ZuOVD5mLnfi8rTnDspY8qfEgwefVTTYgf6eY3B_001s,1213
|
|
3
|
-
xgae/engine/xga_engine.py,sha256=3U0F3_ISu5K1CGYhn0RZsIaZkm4OQIrMTIf3N22M7bE,3013
|
|
4
|
-
xgae/engine/xga_mcp_tool_box.py,sha256=3L48c-Wl8QghbudUsZdYemOzEHoOGF0QC-sAh0H1PVI,9047
|
|
5
|
-
xgae/engine/xga_prompt_builder.py,sha256=wPCB6g0QNpKDuvWs5Ix_Z8-OoCFjxqApodb-g-qZrbM,1503
|
|
6
|
-
xgae/engine/responser/xga_non_stream_responser.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
-
xgae/engine/responser/xga_responser_utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
xgae/engine/responser/xga_stream_reponser.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
-
xgae/utils/llm_client.py,sha256=bsdWLTXRDDpiuEn72a5BuT4FYomE1LjY-thQHbTQ_Fg,12167
|
|
10
|
-
xgae/utils/setup_env.py,sha256=nJHllWuM9kLs_mJg_-j3Lhj8FHtyFjK4CIoDY_SHOxk,3253
|
|
11
|
-
xgae-0.1.1.dist-info/METADATA,sha256=_j2ZiXzJZa-sw_fk7P0sHkKiyYus_ncxoL9YoZEv4iQ,309
|
|
12
|
-
xgae-0.1.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
13
|
-
xgae-0.1.1.dist-info/RECORD,,
|
|
File without changes
|