arterm-sedov commited on
Commit
2ac0203
·
1 Parent(s): 14ac741

New tools: get records, get record url

Browse files
misc_files/run_list_attributes.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ from tools.templates_tools.tool_list_attributes import list_attributes
3
+
4
+
5
+ if __name__ == "__main__":
6
+ params = {
7
+ "application_system_name": "Управлениеавтопарком",
8
+ "template_system_name": "Заявкинаавтомобили",
9
+ }
10
+ res = list_attributes.invoke(params)
11
+ print(json.dumps(res, ensure_ascii=False, indent=2))
12
+
13
+
misc_files/run_list_template_records.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ from tools.templates_tools.tool_list_records import list_template_records
3
+
4
+
5
+ def run_case(application_system_name: str, template_system_name: str, attributes, filters):
6
+ params = {
7
+ "application_system_name": application_system_name,
8
+ "template_system_name": template_system_name,
9
+ "attributes": attributes,
10
+ "filters": filters,
11
+ "limit": 10,
12
+ "offset": 0,
13
+ "sort_by": None,
14
+ "sort_desc": False,
15
+ }
16
+ res = list_template_records.invoke(params)
17
+ print(json.dumps(res, ensure_ascii=False, indent=2))
18
+
19
+
20
+ if __name__ == "__main__":
21
+ # Target case from chat
22
+ print("\n=== Case 1: attributes=[], filters={} ===")
23
+ run_case("Управлениеавтопарком", "Заявкинаавтомобили", attributes=[], filters={})
24
+
25
+ print("\n=== Case 2: attributes=None, filters=None ===")
26
+ run_case("Управлениеавтопарком", "Заявкинаавтомобили", attributes=None, filters=None)
27
+
28
+
tools/__init__.py CHANGED
@@ -38,7 +38,7 @@ from . import tools
38
 
39
  # Import key functions from subpackages for convenience
40
  from .applications_tools import list_applications, list_templates
41
- from .applications_tools import get_platform_entity_url
42
  from .attributes_tools import (
43
  # General operations
44
  delete_attribute, archive_or_unarchive_attribute, get_attribute,
@@ -102,6 +102,7 @@ __all__ = [
102
  'list_templates',
103
  'list_attributes',
104
  'get_platform_entity_url',
 
105
 
106
  # General operations
107
  'delete_attribute',
 
38
 
39
  # Import key functions from subpackages for convenience
40
  from .applications_tools import list_applications, list_templates
41
+ from .applications_tools import get_platform_entity_url, get_record_url
42
  from .attributes_tools import (
43
  # General operations
44
  delete_attribute, archive_or_unarchive_attribute, get_attribute,
 
102
  'list_templates',
103
  'list_attributes',
104
  'get_platform_entity_url',
105
+ 'get_record_url',
106
 
107
  # General operations
108
  'delete_attribute',
tools/applications_tools/__init__.py CHANGED
@@ -13,9 +13,11 @@ Available Tools:
13
  from .tool_list_applications import list_applications
14
  from .tool_list_templates import list_templates
15
  from .tool_platform_entity_url import get_platform_entity_url
 
16
 
17
  __all__ = [
18
  'list_applications',
19
  'list_templates',
20
- 'get_platform_entity_url'
 
21
  ]
 
13
  from .tool_list_applications import list_applications
14
  from .tool_list_templates import list_templates
15
  from .tool_platform_entity_url import get_platform_entity_url
16
+ from .tool_record_url import get_record_url
17
 
18
  __all__ = [
19
  'list_applications',
20
  'list_templates',
21
+ 'get_platform_entity_url',
22
+ 'get_record_url'
23
  ]
tools/applications_tools/tool_record_url.py ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, Dict
2
+
3
+ from pydantic import BaseModel, Field, field_validator
4
+
5
+ from ..tool_utils import tool, requests_ # type: ignore
6
+
7
+
8
+ class GetRecordUrlSchema(BaseModel):
9
+ """
10
+ Schema for generating a direct URL to a specific record by its ID.
11
+ """
12
+
13
+ record_id: str = Field(description="Record ID to generate URL for")
14
+
15
+ @field_validator("record_id", mode="before")
16
+ @classmethod
17
+ def non_empty_str(cls, v: Any) -> Any:
18
+ if isinstance(v, str) and v.strip() == "":
19
+ raise ValueError("Value must be a non-empty string")
20
+ return v
21
+
22
+
23
+ @tool("get_record_url", return_direct=False, args_schema=GetRecordUrlSchema)
24
+ def get_record_url(record_id: str) -> Dict[str, Any]:
25
+ """
26
+ Get the URL for a record by its ID.
27
+
28
+ Returns:
29
+ dict: {
30
+ "success": bool,
31
+ "status_code": int,
32
+ "record_url": str|None,
33
+ "error": str|None,
34
+ }
35
+ """
36
+
37
+ try:
38
+ cfg = requests_._load_server_config()
39
+ base_url = cfg.base_url.rstrip("/")
40
+
41
+ if not base_url:
42
+ return {
43
+ "success": False,
44
+ "status_code": 500,
45
+ "record_url": None,
46
+ "error": "Base URL not found in server configuration",
47
+ }
48
+
49
+ record_url = f"{base_url}/#Resolver/{record_id}"
50
+
51
+ return {
52
+ "success": True,
53
+ "status_code": 200,
54
+ "record_url": record_url,
55
+ "error": None,
56
+ }
57
+
58
+ except Exception as e:
59
+ return {
60
+ "success": False,
61
+ "status_code": 500,
62
+ "record_url": None,
63
+ "error": f"Error generating record URL: {str(e)}",
64
+ }
65
+
66
+
67
+ if __name__ == "__main__":
68
+ # Local manual test example
69
+ results = get_record_url.invoke({
70
+ "record_id": "1"
71
+ })
72
+ print(results)
73
+
tools/templates_tools/__init__.py CHANGED
@@ -1,18 +1,17 @@
1
- """
2
- Templates Tools Package
3
- ======================
4
-
5
- This package contains tools for managing Comindware Platform templates and their attributes.
6
 
7
- Available Tools:
8
- - list_attributes: List all attributes in a specific template
9
  """
10
 
11
  # Import all tool functions
12
- from .tool_list_attributes import list_attributes
13
- from .tools_record_template import edit_or_create_record_template
 
14
 
15
  __all__ = [
16
- 'list_attributes',
17
- 'edit_or_create_record_template'
 
18
  ]
 
1
+ # ruff: noqa: N999
2
+ """Templates Tools Package.
 
 
 
3
 
4
+ This package contains tools for managing Comindware Platform templates and records and listing
5
+ template attributes.
6
  """
7
 
8
  # Import all tool functions
9
+ from tools.templates_tools.tool_list_attributes import list_attributes
10
+ from tools.templates_tools.tool_list_records import list_template_records
11
+ from tools.templates_tools.tools_record_template import edit_or_create_record_template
12
 
13
  __all__ = [
14
+ "edit_or_create_record_template",
15
+ "list_attributes",
16
+ "list_template_records",
17
  ]
tools/templates_tools/tool_list_records.py ADDED
@@ -0,0 +1,214 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from contextlib import suppress
2
+ from typing import Any
3
+
4
+ from langchain_core.tools import tool
5
+ from pydantic import BaseModel, Field, field_validator
6
+
7
+ from tools import requests_
8
+ from tools.models import AttributeResult
9
+ from tools.tool_utils import execute_list_operation, remove_values
10
+
11
+ RECORDS_ENDPOINT_PREFIX = "webapi/Records"
12
+
13
+
14
+ class ListTemplateRecordsSchema(BaseModel):
15
+ application_system_name: str = Field(
16
+ description=(
17
+ "System name of the application with the template. "
18
+ "RU: Системное имя приложения"
19
+ )
20
+ )
21
+ template_system_name: str = Field(
22
+ description=(
23
+ "System name of the template to fetch records from. "
24
+ "RU: Системное имя шаблона"
25
+ )
26
+ )
27
+ attributes: list[str] | None = Field(
28
+ default=None,
29
+ description=(
30
+ "List of attribute system names to return. If omitted, returns all "
31
+ "template attributes."
32
+ ),
33
+ )
34
+ filters: dict[str, Any] | None = Field(
35
+ default=None,
36
+ description=(
37
+ "Optional mapping attribute->value to filter dataset. "
38
+ "Keep filters narrow; prefer pagination over large scans. "
39
+ ),
40
+ )
41
+ limit: int = Field(
42
+ default=100,
43
+ ge=1,
44
+ le=100,
45
+ description=(
46
+ "Number of records to fetch (page size). Default 100. Max 100 per request. "
47
+ ),
48
+ )
49
+ offset: int = Field(
50
+ default=0,
51
+ ge=0,
52
+ description=(
53
+ "Position to start fetching from (use for paging). Example: first "
54
+ "page offset=0, second page offset=10."
55
+ ),
56
+ )
57
+ sort_by: str | None = Field(
58
+ default="creationDate",
59
+ description="Attribute system name to sort by.",
60
+ )
61
+ sort_desc: bool = Field(
62
+ default=False,
63
+ description=(
64
+ "Sort in descending order if True, ascending if False. "
65
+ ),
66
+ )
67
+
68
+ @field_validator("application_system_name", "template_system_name", mode="before")
69
+ @classmethod
70
+ def non_empty_str(cls, v: Any) -> Any:
71
+ if isinstance(v, str) and v.strip() == "":
72
+ msg = "must be a non-empty string"
73
+ raise ValueError(msg)
74
+ return v
75
+
76
+
77
+ @tool(
78
+ "list_template_records",
79
+ return_direct=False,
80
+ args_schema=ListTemplateRecordsSchema,
81
+ )
82
+ def list_template_records(
83
+ application_system_name: str,
84
+ template_system_name: str,
85
+ *,
86
+ attributes: list[str] | None = None,
87
+ filters: dict[str, Any] | None = None,
88
+ limit: int = 100,
89
+ offset: int = 0,
90
+ sort_by: str | None = "creationDate",
91
+ sort_desc: bool = False,
92
+ ) -> dict[str, Any]:
93
+ r"""
94
+ List records for a given template with filtering, attribute projection,
95
+ sorting, and pagination.
96
+
97
+ - Before calling this tool fetch the template attribute model using the
98
+ `list_attributes` tool and use it to determine available attribute properties.
99
+ - Filtering: exact match by keys in `filters`; list values are supported
100
+ (contains/all-of).
101
+ - Attributes: when a non-empty `attributes` list is provided, only those
102
+ attributes are returned.
103
+ - Pagination/sorting: hard limit 100 per call (page via `offset`).
104
+ - After each iteration talk to the user about the results instead of
105
+ fetching too many records at once.
106
+
107
+ Returns:
108
+ dict: {
109
+ "success": bool - True if record list was fetched successfully
110
+ "status_code": int - HTTP response status code
111
+ "data": list|None - List of attribute and their values if successful
112
+ "error": str|None - Error message if operation failed
113
+ }
114
+ """
115
+
116
+ # Build Template@solution.template path parameter
117
+ template_global_alias_str = (
118
+ f"Template@{application_system_name}.{template_system_name}"
119
+ )
120
+ endpoint = f"{RECORDS_ENDPOINT_PREFIX}/{template_global_alias_str}"
121
+
122
+ result = requests_._get_request(endpoint) # noqa: SLF001
123
+
124
+ # If the API call failed or structure is unexpected, normalize via common adapter
125
+ if not result.get("success", False):
126
+ return execute_list_operation(
127
+ response_data=result,
128
+ result_model=AttributeResult,
129
+ )
130
+
131
+ raw = result.get("raw_response")
132
+ if not isinstance(raw, dict) or "response" not in raw:
133
+ return execute_list_operation(
134
+ response_data=result,
135
+ result_model=AttributeResult,
136
+ )
137
+
138
+ # Normalize response to a list of records
139
+ response_payload = raw.get("response")
140
+ if isinstance(response_payload, dict):
141
+ data = list(response_payload.values())
142
+ elif isinstance(response_payload, list):
143
+ data = response_payload
144
+ else:
145
+ data = []
146
+
147
+ # Client-side filtering (exact match semantics with light list support)
148
+ if isinstance(filters, dict) and filters:
149
+ def _matches(rec: dict[str, Any]) -> bool:
150
+ for key, expected in filters.items():
151
+ actual = (rec or {}).get(key, None)
152
+ # List-aware comparison
153
+ if isinstance(actual, list):
154
+ if isinstance(expected, list):
155
+ # Require all expected items to be present
156
+ if not set(expected).issubset(set(actual)):
157
+ return False
158
+ elif expected not in actual:
159
+ return False
160
+ elif actual != expected:
161
+ return False
162
+ return True
163
+
164
+ data = [r for r in data if isinstance(r, dict) and _matches(r)]
165
+
166
+ # Client-side attribute projection (None or empty list => return all fields)
167
+ if attributes not in (None, []) and isinstance(attributes, list):
168
+ attr_set = set(attributes)
169
+ projected: list[dict[str, Any]] = []
170
+ for rec in data:
171
+ if not isinstance(rec, dict):
172
+ continue
173
+ projected.append({k: v for k, v in rec.items() if k in attr_set})
174
+ data = projected
175
+
176
+ # Optional client-side sorting
177
+ if sort_by:
178
+ with suppress(Exception):
179
+ data = sorted(
180
+ data,
181
+ key=lambda r: (r or {}).get(sort_by),
182
+ reverse=bool(sort_desc),
183
+ )
184
+
185
+ # Client-side pagination — enforce hard ceiling of 100 per request
186
+ start = max(0, int(offset))
187
+ page_size = max(1, min(int(limit), 100))
188
+ end = max(start, start + page_size)
189
+ paged = data[start:end]
190
+
191
+ adapted = dict(result)
192
+ adapted["raw_response"] = {"response": paged}
193
+
194
+ return execute_list_operation(
195
+ response_data=adapted,
196
+ result_model=AttributeResult,
197
+ )
198
+
199
+
200
+ if __name__ == "__main__":
201
+ results = list_template_records.invoke({
202
+ "application_system_name": "AItestAndApi",
203
+ "template_system_name": "Test",
204
+ "attributes": [],
205
+ "filters": {},
206
+ "limit": 10,
207
+ "offset": 0,
208
+ "sort_by": None,
209
+ "sort_desc": False,
210
+ })
211
+ print(results)
212
+
213
+
214
+
tools/tools.py CHANGED
@@ -47,6 +47,7 @@ from langchain_core.tools import tool
47
  # Expose Comindware Platform tools from all directories
48
  # Templates tools
49
  from .templates_tools.tool_list_attributes import list_attributes
 
50
 
51
  # Applications tools
52
  from .applications_tools.tool_list_templates import list_templates
 
47
  # Expose Comindware Platform tools from all directories
48
  # Templates tools
49
  from .templates_tools.tool_list_attributes import list_attributes
50
+ from .templates_tools.tool_list_records import list_template_records
51
 
52
  # Applications tools
53
  from .applications_tools.tool_list_templates import list_templates