xgae 0.1.2__py3-none-any.whl → 0.1.4__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/responser/xga_non_stream_responser.py +213 -0
- xgae/engine/responser/xga_responser_base.py +751 -0
- xgae/engine/responser/xga_stream_responser.py +787 -0
- xgae/engine/xga_base.py +23 -9
- xgae/engine/xga_engine.py +228 -55
- xgae/engine/xga_mcp_tool_box.py +8 -3
- xgae/engine/xga_prompt_builder.py +27 -45
- xgae/utils/json_helpers.py +174 -0
- xgae/utils/llm_client.py +17 -6
- xgae/utils/setup_env.py +1 -31
- xgae/utils/utils.py +42 -0
- xgae/utils/xml_tool_parser.py +236 -0
- {xgae-0.1.2.dist-info → xgae-0.1.4.dist-info}/METADATA +1 -1
- xgae-0.1.4.dist-info/RECORD +16 -0
- xgae/engine/responser/xga_responser_utils.py +0 -0
- xgae/engine/responser/xga_stream_reponser.py +0 -0
- xgae-0.1.2.dist-info/RECORD +0 -13
- {xgae-0.1.2.dist-info → xgae-0.1.4.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""
|
|
2
|
+
JSON helper utilities for handling both legacy (string) and new (dict/list) formats.
|
|
3
|
+
|
|
4
|
+
These utilities help with the transition from storing JSON as strings to storing
|
|
5
|
+
them as proper JSONB objects in the database.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from typing import Any, Union, Dict, List
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def ensure_dict(value: Union[str, Dict[str, Any], None], default: Dict[str, Any] = None) -> Dict[str, Any]:
|
|
13
|
+
"""
|
|
14
|
+
Ensure a value is a dictionary.
|
|
15
|
+
|
|
16
|
+
Handles:
|
|
17
|
+
- None -> returns default or {}
|
|
18
|
+
- Dict -> returns as-is
|
|
19
|
+
- JSON string -> parses and returns dict
|
|
20
|
+
- Other -> returns default or {}
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
value: The value to ensure is a dict
|
|
24
|
+
default: Default value if conversion fails
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
A dictionary
|
|
28
|
+
"""
|
|
29
|
+
if default is None:
|
|
30
|
+
default = {}
|
|
31
|
+
|
|
32
|
+
if value is None:
|
|
33
|
+
return default
|
|
34
|
+
|
|
35
|
+
if isinstance(value, dict):
|
|
36
|
+
return value
|
|
37
|
+
|
|
38
|
+
if isinstance(value, str):
|
|
39
|
+
try:
|
|
40
|
+
parsed = json.loads(value)
|
|
41
|
+
if isinstance(parsed, dict):
|
|
42
|
+
return parsed
|
|
43
|
+
return default
|
|
44
|
+
except (json.JSONDecodeError, TypeError):
|
|
45
|
+
return default
|
|
46
|
+
|
|
47
|
+
return default
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def ensure_list(value: Union[str, List[Any], None], default: List[Any] = None) -> List[Any]:
|
|
51
|
+
"""
|
|
52
|
+
Ensure a value is a list.
|
|
53
|
+
|
|
54
|
+
Handles:
|
|
55
|
+
- None -> returns default or []
|
|
56
|
+
- List -> returns as-is
|
|
57
|
+
- JSON string -> parses and returns list
|
|
58
|
+
- Other -> returns default or []
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
value: The value to ensure is a list
|
|
62
|
+
default: Default value if conversion fails
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
A list
|
|
66
|
+
"""
|
|
67
|
+
if default is None:
|
|
68
|
+
default = []
|
|
69
|
+
|
|
70
|
+
if value is None:
|
|
71
|
+
return default
|
|
72
|
+
|
|
73
|
+
if isinstance(value, list):
|
|
74
|
+
return value
|
|
75
|
+
|
|
76
|
+
if isinstance(value, str):
|
|
77
|
+
try:
|
|
78
|
+
parsed = json.loads(value)
|
|
79
|
+
if isinstance(parsed, list):
|
|
80
|
+
return parsed
|
|
81
|
+
return default
|
|
82
|
+
except (json.JSONDecodeError, TypeError):
|
|
83
|
+
return default
|
|
84
|
+
|
|
85
|
+
return default
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def safe_json_parse(value: Union[str, Dict, List, Any], default: Any = None) -> Any:
|
|
89
|
+
"""
|
|
90
|
+
Safely parse a value that might be JSON string or already parsed.
|
|
91
|
+
|
|
92
|
+
This handles the transition period where some data might be stored as
|
|
93
|
+
JSON strings (old format) and some as proper objects (new format).
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
value: The value to parse
|
|
97
|
+
default: Default value if parsing fails
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Parsed value or default
|
|
101
|
+
"""
|
|
102
|
+
if value is None:
|
|
103
|
+
return default
|
|
104
|
+
|
|
105
|
+
# If it's already a dict or list, return as-is
|
|
106
|
+
if isinstance(value, (dict, list)):
|
|
107
|
+
return value
|
|
108
|
+
|
|
109
|
+
# If it's a string, try to parse it
|
|
110
|
+
if isinstance(value, str):
|
|
111
|
+
try:
|
|
112
|
+
return json.loads(value)
|
|
113
|
+
except (json.JSONDecodeError, TypeError):
|
|
114
|
+
# If it's not valid JSON, return the string itself
|
|
115
|
+
return value
|
|
116
|
+
|
|
117
|
+
# For any other type, return as-is
|
|
118
|
+
return value
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def to_json_string(value: Any) -> str:
|
|
122
|
+
"""
|
|
123
|
+
Convert a value to a JSON string if needed.
|
|
124
|
+
|
|
125
|
+
This is used for backwards compatibility when yielding data that
|
|
126
|
+
expects JSON strings.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
value: The value to convert
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
JSON string representation
|
|
133
|
+
"""
|
|
134
|
+
if isinstance(value, str):
|
|
135
|
+
# If it's already a string, check if it's valid JSON
|
|
136
|
+
try:
|
|
137
|
+
json.loads(value)
|
|
138
|
+
return value # It's already a JSON string
|
|
139
|
+
except (json.JSONDecodeError, TypeError):
|
|
140
|
+
# It's a plain string, encode it as JSON
|
|
141
|
+
return json.dumps(value)
|
|
142
|
+
|
|
143
|
+
# For all other types, convert to JSON
|
|
144
|
+
return json.dumps(value)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def format_for_yield(message_object: Dict[str, Any]) -> Dict[str, Any]:
|
|
148
|
+
"""
|
|
149
|
+
Format a message object for yielding, ensuring content and metadata are JSON strings.
|
|
150
|
+
|
|
151
|
+
This maintains backward compatibility with clients expecting JSON strings
|
|
152
|
+
while the database now stores proper objects.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
message_object: The message object from the database
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
Message object with content and metadata as JSON strings
|
|
159
|
+
"""
|
|
160
|
+
if not message_object:
|
|
161
|
+
return message_object
|
|
162
|
+
|
|
163
|
+
# Create a copy to avoid modifying the original
|
|
164
|
+
formatted = message_object.copy()
|
|
165
|
+
|
|
166
|
+
# Ensure content is a JSON string
|
|
167
|
+
if 'content' in formatted and not isinstance(formatted['content'], str):
|
|
168
|
+
formatted['content'] = json.dumps(formatted['content'])
|
|
169
|
+
|
|
170
|
+
# Ensure metadata is a JSON string
|
|
171
|
+
if 'metadata' in formatted and not isinstance(formatted['metadata'], str):
|
|
172
|
+
formatted['metadata'] = json.dumps(formatted['metadata'])
|
|
173
|
+
|
|
174
|
+
return formatted
|
xgae/utils/llm_client.py
CHANGED
|
@@ -4,11 +4,24 @@ import logging
|
|
|
4
4
|
import os
|
|
5
5
|
import litellm
|
|
6
6
|
|
|
7
|
-
from typing import Union, Dict, Any, Optional, List
|
|
7
|
+
from typing import Union, Dict, Any, Optional, List, TypedDict
|
|
8
8
|
|
|
9
9
|
from litellm.utils import ModelResponse, CustomStreamWrapper
|
|
10
10
|
from openai import OpenAIError
|
|
11
11
|
|
|
12
|
+
class LLMConfig(TypedDict, total=False):
|
|
13
|
+
model: str
|
|
14
|
+
model_name: str
|
|
15
|
+
model_id: str
|
|
16
|
+
api_key: str
|
|
17
|
+
api_base: str
|
|
18
|
+
temperature: float
|
|
19
|
+
max_tokens: int
|
|
20
|
+
stream: bool
|
|
21
|
+
enable_thinking: bool
|
|
22
|
+
reasoning_effort: str
|
|
23
|
+
response_format: str
|
|
24
|
+
top_p: int
|
|
12
25
|
|
|
13
26
|
class LLMError(Exception):
|
|
14
27
|
"""Base exception for LLM-related errors."""
|
|
@@ -18,7 +31,7 @@ class LLMClient:
|
|
|
18
31
|
RATE_LIMIT_DELAY = 30
|
|
19
32
|
RETRY_DELAY = 0.1
|
|
20
33
|
|
|
21
|
-
def __init__(self, llm_config:
|
|
34
|
+
def __init__(self, llm_config: LLMConfig=None) -> None:
|
|
22
35
|
"""
|
|
23
36
|
Arg: llm_config (Optional[Dict[str, Any]], optional)
|
|
24
37
|
model: Override default model to use, default set by .env LLM_MODEL
|
|
@@ -34,7 +47,7 @@ class LLMClient:
|
|
|
34
47
|
reasoning_effort: Optional level of reasoning effort, default is ‘low’
|
|
35
48
|
top_p: Optional Top-p sampling parameter, default is None
|
|
36
49
|
"""
|
|
37
|
-
llm_config = llm_config or
|
|
50
|
+
llm_config = llm_config or LLMConfig()
|
|
38
51
|
litellm.modify_params = True
|
|
39
52
|
litellm.drop_params = True
|
|
40
53
|
|
|
@@ -214,9 +227,7 @@ class LLMClient:
|
|
|
214
227
|
|
|
215
228
|
if __name__ == "__main__":
|
|
216
229
|
async def llm_completion():
|
|
217
|
-
llm_client = LLMClient(
|
|
218
|
-
"stream": False #default is True
|
|
219
|
-
})
|
|
230
|
+
llm_client = LLMClient(LLMConfig(stream=False))
|
|
220
231
|
messages = [{"role": "user", "content": "今天是2025年8月15日,北京本周每天温度"}]
|
|
221
232
|
response = await llm_client.create_completion(messages)
|
|
222
233
|
if llm_client.is_stream:
|
xgae/utils/setup_env.py
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import os
|
|
3
|
-
import traceback
|
|
4
3
|
|
|
5
4
|
from langfuse import Langfuse
|
|
6
5
|
|
|
7
|
-
|
|
8
6
|
_log_initialized = False
|
|
9
7
|
|
|
10
8
|
def setup_logging() -> None:
|
|
@@ -65,17 +63,6 @@ def setup_logging() -> None:
|
|
|
65
63
|
|
|
66
64
|
setup_logging()
|
|
67
65
|
|
|
68
|
-
|
|
69
|
-
class XGAError(Exception):
|
|
70
|
-
"""Custom exception for errors in the XGA system."""
|
|
71
|
-
pass
|
|
72
|
-
|
|
73
|
-
def handle_error(e: Exception) -> None:
|
|
74
|
-
logging.error("An error occurred: %s", str(e))
|
|
75
|
-
logging.error("Traceback details:\n%s", traceback.format_exc())
|
|
76
|
-
raise (e) from e
|
|
77
|
-
|
|
78
|
-
|
|
79
66
|
_langfuse_initialized = False
|
|
80
67
|
|
|
81
68
|
def setup_langfuse() -> Langfuse:
|
|
@@ -101,23 +88,6 @@ def setup_langfuse() -> Langfuse:
|
|
|
101
88
|
langfuse: Langfuse = Langfuse if _langfuse_initialized else setup_langfuse()
|
|
102
89
|
|
|
103
90
|
|
|
104
|
-
def read_file(file_path: str) -> str:
|
|
105
|
-
if not os.path.exists(file_path):
|
|
106
|
-
logging.error(f"File '{file_path}' not found")
|
|
107
|
-
raise XGAError(f"File '{file_path}' not found")
|
|
108
|
-
|
|
109
|
-
try:
|
|
110
|
-
with open(file_path, "r", encoding="utf-8") as template_file:
|
|
111
|
-
content = template_file.read()
|
|
112
|
-
return content
|
|
113
|
-
except Exception as e:
|
|
114
|
-
logging.error(f"Read file '{file_path}' failed")
|
|
115
|
-
handle_error(e)
|
|
116
|
-
|
|
117
|
-
|
|
118
91
|
if __name__ == "__main__":
|
|
119
|
-
try:
|
|
120
92
|
trace_id = langfuse.create_trace_id()
|
|
121
|
-
|
|
122
|
-
except Exception as e:
|
|
123
|
-
handle_error(e)
|
|
93
|
+
logging.warning(f"trace_id={trace_id}")
|
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,236 @@
|
|
|
1
|
+
"""
|
|
2
|
+
XML Tool Call Parser Module
|
|
3
|
+
|
|
4
|
+
This module provides a reliable XML tool call parsing system that supports
|
|
5
|
+
the XML format with structured function_calls blocks.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import re
|
|
9
|
+
import xml.etree.ElementTree as ET
|
|
10
|
+
from typing import List, Dict, Any, Optional, Tuple
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
import json
|
|
13
|
+
import logging
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class XMLToolCall:
|
|
20
|
+
"""Represents a parsed XML tool call."""
|
|
21
|
+
function_name: str
|
|
22
|
+
parameters: Dict[str, Any]
|
|
23
|
+
raw_xml: str
|
|
24
|
+
parsing_details: Dict[str, Any]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class XMLToolParser:
|
|
28
|
+
"""
|
|
29
|
+
Parser for XML tool calls format:
|
|
30
|
+
|
|
31
|
+
<function_calls>
|
|
32
|
+
<invoke name="function_name">
|
|
33
|
+
<parameter name="param_name">param_value</parameter>
|
|
34
|
+
...
|
|
35
|
+
</invoke>
|
|
36
|
+
</function_calls>
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
# Regex patterns for extracting XML blocks
|
|
40
|
+
FUNCTION_CALLS_PATTERN = re.compile(
|
|
41
|
+
r'<function_calls>(.*?)</function_calls>',
|
|
42
|
+
re.DOTALL | re.IGNORECASE
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
INVOKE_PATTERN = re.compile(
|
|
46
|
+
r'<invoke\s+name=["\']([^"\']+)["\']>(.*?)</invoke>',
|
|
47
|
+
re.DOTALL | re.IGNORECASE
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
PARAMETER_PATTERN = re.compile(
|
|
51
|
+
r'<parameter\s+name=["\']([^"\']+)["\']>(.*?)</parameter>',
|
|
52
|
+
re.DOTALL | re.IGNORECASE
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
def __init__(self):
|
|
56
|
+
"""Initialize the XML tool parser."""
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
def parse_content(self, content: str) -> List[XMLToolCall]:
|
|
60
|
+
"""
|
|
61
|
+
Parse XML tool calls from content.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
content: The text content potentially containing XML tool calls
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
List of parsed XMLToolCall objects
|
|
68
|
+
"""
|
|
69
|
+
tool_calls = []
|
|
70
|
+
|
|
71
|
+
# Find function_calls blocks
|
|
72
|
+
function_calls_matches = self.FUNCTION_CALLS_PATTERN.findall(content)
|
|
73
|
+
|
|
74
|
+
for fc_content in function_calls_matches:
|
|
75
|
+
# Find all invoke blocks within this function_calls block
|
|
76
|
+
invoke_matches = self.INVOKE_PATTERN.findall(fc_content)
|
|
77
|
+
|
|
78
|
+
for function_name, invoke_content in invoke_matches:
|
|
79
|
+
try:
|
|
80
|
+
tool_call = self._parse_invoke_block(
|
|
81
|
+
function_name,
|
|
82
|
+
invoke_content,
|
|
83
|
+
fc_content
|
|
84
|
+
)
|
|
85
|
+
if tool_call:
|
|
86
|
+
tool_calls.append(tool_call)
|
|
87
|
+
except Exception as e:
|
|
88
|
+
logger.error(f"Error parsing invoke block for {function_name}: {e}")
|
|
89
|
+
|
|
90
|
+
return tool_calls
|
|
91
|
+
|
|
92
|
+
def _parse_invoke_block(
|
|
93
|
+
self,
|
|
94
|
+
function_name: str,
|
|
95
|
+
invoke_content: str,
|
|
96
|
+
full_block: str
|
|
97
|
+
) -> Optional[XMLToolCall]:
|
|
98
|
+
"""Parse a single invoke block into an XMLToolCall."""
|
|
99
|
+
parameters = {}
|
|
100
|
+
parsing_details = {
|
|
101
|
+
"function_name": function_name,
|
|
102
|
+
"raw_parameters": {}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
# Extract all parameters
|
|
106
|
+
param_matches = self.PARAMETER_PATTERN.findall(invoke_content)
|
|
107
|
+
|
|
108
|
+
for param_name, param_value in param_matches:
|
|
109
|
+
# Clean up the parameter value
|
|
110
|
+
param_value = param_value.strip()
|
|
111
|
+
|
|
112
|
+
# Try to parse as JSON if it looks like JSON
|
|
113
|
+
parsed_value = self._parse_parameter_value(param_value)
|
|
114
|
+
|
|
115
|
+
parameters[param_name] = parsed_value
|
|
116
|
+
parsing_details["raw_parameters"][param_name] = param_value
|
|
117
|
+
|
|
118
|
+
# Extract the raw XML for this specific invoke
|
|
119
|
+
invoke_pattern = re.compile(
|
|
120
|
+
rf'<invoke\s+name=["\']{re.escape(function_name)}["\']>.*?</invoke>',
|
|
121
|
+
re.DOTALL | re.IGNORECASE
|
|
122
|
+
)
|
|
123
|
+
raw_xml_match = invoke_pattern.search(full_block)
|
|
124
|
+
raw_xml = raw_xml_match.group(0) if raw_xml_match else f"<invoke name=\"{function_name}\">...</invoke>"
|
|
125
|
+
|
|
126
|
+
return XMLToolCall(
|
|
127
|
+
function_name=function_name,
|
|
128
|
+
parameters=parameters,
|
|
129
|
+
raw_xml=raw_xml,
|
|
130
|
+
parsing_details=parsing_details
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
def _parse_parameter_value(self, value: str) -> Any:
|
|
134
|
+
"""
|
|
135
|
+
Parse a parameter value, attempting to convert to appropriate type.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
value: The string value to parse
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
Parsed value (could be dict, list, bool, int, float, or str)
|
|
142
|
+
"""
|
|
143
|
+
value = value.strip()
|
|
144
|
+
|
|
145
|
+
# Try to parse as JSON first
|
|
146
|
+
if value.startswith(('{', '[')):
|
|
147
|
+
try:
|
|
148
|
+
return json.loads(value)
|
|
149
|
+
except json.JSONDecodeError:
|
|
150
|
+
pass
|
|
151
|
+
|
|
152
|
+
# Try to parse as boolean
|
|
153
|
+
if value.lower() in ('true', 'false'):
|
|
154
|
+
return value.lower() == 'true'
|
|
155
|
+
|
|
156
|
+
# Try to parse as number
|
|
157
|
+
try:
|
|
158
|
+
if '.' in value:
|
|
159
|
+
return float(value)
|
|
160
|
+
else:
|
|
161
|
+
return int(value)
|
|
162
|
+
except ValueError:
|
|
163
|
+
pass
|
|
164
|
+
|
|
165
|
+
# Return as string
|
|
166
|
+
return value
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def format_tool_call(self, function_name: str, parameters: Dict[str, Any]) -> str:
|
|
170
|
+
"""
|
|
171
|
+
Format a tool call in the XML format.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
function_name: Name of the function to call
|
|
175
|
+
parameters: Dictionary of parameters
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
Formatted XML string
|
|
179
|
+
"""
|
|
180
|
+
lines = ['<function_calls>', '<invoke name="{}">'.format(function_name)]
|
|
181
|
+
|
|
182
|
+
for param_name, param_value in parameters.items():
|
|
183
|
+
# Convert value to string representation
|
|
184
|
+
if isinstance(param_value, (dict, list)):
|
|
185
|
+
value_str = json.dumps(param_value)
|
|
186
|
+
elif isinstance(param_value, bool):
|
|
187
|
+
value_str = str(param_value).lower()
|
|
188
|
+
else:
|
|
189
|
+
value_str = str(param_value)
|
|
190
|
+
|
|
191
|
+
lines.append('<parameter name="{}">{}</parameter>'.format(
|
|
192
|
+
param_name, value_str
|
|
193
|
+
))
|
|
194
|
+
|
|
195
|
+
lines.extend(['</invoke>', '</function_calls>'])
|
|
196
|
+
return '\n'.join(lines)
|
|
197
|
+
|
|
198
|
+
def validate_tool_call(self, tool_call: XMLToolCall, expected_params: Optional[Dict[str, type]] = None) -> Tuple[bool, Optional[str]]:
|
|
199
|
+
"""
|
|
200
|
+
Validate a tool call against expected parameters.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
tool_call: The XMLToolCall to validate
|
|
204
|
+
expected_params: Optional dict of parameter names to expected types
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
Tuple of (is_valid, error_message)
|
|
208
|
+
"""
|
|
209
|
+
if not tool_call.function_name:
|
|
210
|
+
return False, "Function name is required"
|
|
211
|
+
|
|
212
|
+
if expected_params:
|
|
213
|
+
for param_name, expected_type in expected_params.items():
|
|
214
|
+
if param_name not in tool_call.parameters:
|
|
215
|
+
return False, f"Missing required parameter: {param_name}"
|
|
216
|
+
|
|
217
|
+
param_value = tool_call.parameters[param_name]
|
|
218
|
+
if not isinstance(param_value, expected_type):
|
|
219
|
+
return False, f"Parameter {param_name} should be of type {expected_type.__name__}"
|
|
220
|
+
|
|
221
|
+
return True, None
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
# Convenience function for quick parsing
|
|
225
|
+
def parse_xml_tool_calls(content: str) -> List[XMLToolCall]:
|
|
226
|
+
"""
|
|
227
|
+
Parse XML tool calls from content.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
content: The text content potentially containing XML tool calls
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
List of parsed XMLToolCall objects
|
|
234
|
+
"""
|
|
235
|
+
parser = XMLToolParser()
|
|
236
|
+
return parser.parse_content(content)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
xgae/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
xgae/engine/xga_base.py,sha256=yM8YdThOVHo_TFrLUiu33hZzU2H9knQLs3JfD6hOx24,1669
|
|
3
|
+
xgae/engine/xga_engine.py,sha256=7XRRC6KKz3a2EvH7P9k8szbCCm2O6MxIL674GzUkwHI,12940
|
|
4
|
+
xgae/engine/xga_mcp_tool_box.py,sha256=dvToorrw8FJq4NrzUz494czI6QhT3bvmJQlt5oFgVBA,9994
|
|
5
|
+
xgae/engine/xga_prompt_builder.py,sha256=RuTvQCNufqxDwVvSOPXR0qxAc42cG7NuIaUy9amu66A,4351
|
|
6
|
+
xgae/engine/responser/xga_non_stream_responser.py,sha256=HlSN025jIsl-JY_n6fEdqJQkqc1UqCrgFr6K23uXF3E,12704
|
|
7
|
+
xgae/engine/responser/xga_responser_base.py,sha256=IBJPQELMxZSpnz8YlSCgvPNSHEEUp8_vglotVnHoSeY,36808
|
|
8
|
+
xgae/engine/responser/xga_stream_responser.py,sha256=FESVliTzHFy8BkTudi_Ftcty6QFpJWx7kYRubSuLqsg,50370
|
|
9
|
+
xgae/utils/json_helpers.py,sha256=K1ja6GJCatrAheW9bEWAYSQbDI42__boBCZgtsv1gtk,4865
|
|
10
|
+
xgae/utils/llm_client.py,sha256=mgzn8heUyRm92HTLEYGdfsGEpFtD-xLFr39P98_JP0s,12402
|
|
11
|
+
xgae/utils/setup_env.py,sha256=-Ehv7_E9udHc8AjP66Y78E4X7_G6gpuNJkioCh5fn4A,2902
|
|
12
|
+
xgae/utils/utils.py,sha256=cCYmWjKFksZ8BRD1YYnaM_jTLVHAg1ibEdjsczEUO6k,1134
|
|
13
|
+
xgae/utils/xml_tool_parser.py,sha256=7Ei7X8zSgVct0fFCSmxDtknCLtdrUIwL9hy_0qSNlvs,7546
|
|
14
|
+
xgae-0.1.4.dist-info/METADATA,sha256=R_nN1j5mESZfEQIiLFBQufsOc_Hbd6190GBLgo7-k-o,309
|
|
15
|
+
xgae-0.1.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
16
|
+
xgae-0.1.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
xgae-0.1.2.dist-info/RECORD
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
xgae/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
xgae/engine/xga_base.py,sha256=vOiYWoBze7VrmndYnaNhMzoB1zCT80WtIlOWdp2IrUc,1253
|
|
3
|
-
xgae/engine/xga_engine.py,sha256=AgklNAimyErvn74xUu6aardZvgjqbIR99gCojEmZfO0,4267
|
|
4
|
-
xgae/engine/xga_mcp_tool_box.py,sha256=jRYvZ8eY72KRU0leD-o15fe_E8wjWJtxQZbsIqGVjPU,9753
|
|
5
|
-
xgae/engine/xga_prompt_builder.py,sha256=Q3nrmYAofRkFQFdfSWkP00FZKjWMUQs2cvMs7bvyQgQ,4819
|
|
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=qZmVfqaYHtjq5t-zwRt_G3i-QqGPxSb4D2FnFHbhdcY,3689
|
|
11
|
-
xgae-0.1.2.dist-info/METADATA,sha256=iBwWKlpbp7z0ZQgydnieP_2NBf0Z7H0D6AtavbIN3Zc,309
|
|
12
|
-
xgae-0.1.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
13
|
-
xgae-0.1.2.dist-info/RECORD,,
|
|
File without changes
|