@aj-archipelago/cortex 1.4.2 → 1.4.3

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.
Files changed (86) hide show
  1. package/README.md +1 -0
  2. package/config.js +1 -1
  3. package/helper-apps/cortex-autogen2/.dockerignore +1 -0
  4. package/helper-apps/cortex-autogen2/Dockerfile +6 -10
  5. package/helper-apps/cortex-autogen2/Dockerfile.worker +2 -0
  6. package/helper-apps/cortex-autogen2/agents.py +203 -2
  7. package/helper-apps/cortex-autogen2/main.py +1 -1
  8. package/helper-apps/cortex-autogen2/pyproject.toml +12 -0
  9. package/helper-apps/cortex-autogen2/requirements.txt +14 -0
  10. package/helper-apps/cortex-autogen2/services/redis_publisher.py +1 -1
  11. package/helper-apps/cortex-autogen2/services/run_analyzer.py +1 -1
  12. package/helper-apps/cortex-autogen2/task_processor.py +431 -229
  13. package/helper-apps/cortex-autogen2/test_entity_fetcher.py +305 -0
  14. package/helper-apps/cortex-autogen2/tests/README.md +240 -0
  15. package/helper-apps/cortex-autogen2/tests/TEST_REPORT.md +342 -0
  16. package/helper-apps/cortex-autogen2/tests/__init__.py +8 -0
  17. package/helper-apps/cortex-autogen2/tests/analysis/__init__.py +1 -0
  18. package/helper-apps/cortex-autogen2/tests/analysis/improvement_suggester.py +224 -0
  19. package/helper-apps/cortex-autogen2/tests/analysis/trend_analyzer.py +211 -0
  20. package/helper-apps/cortex-autogen2/tests/cli/__init__.py +1 -0
  21. package/helper-apps/cortex-autogen2/tests/cli/run_tests.py +296 -0
  22. package/helper-apps/cortex-autogen2/tests/collectors/__init__.py +1 -0
  23. package/helper-apps/cortex-autogen2/tests/collectors/log_collector.py +252 -0
  24. package/helper-apps/cortex-autogen2/tests/collectors/progress_collector.py +182 -0
  25. package/helper-apps/cortex-autogen2/tests/conftest.py +15 -0
  26. package/helper-apps/cortex-autogen2/tests/database/__init__.py +1 -0
  27. package/helper-apps/cortex-autogen2/tests/database/repository.py +501 -0
  28. package/helper-apps/cortex-autogen2/tests/database/schema.sql +108 -0
  29. package/helper-apps/cortex-autogen2/tests/evaluators/__init__.py +1 -0
  30. package/helper-apps/cortex-autogen2/tests/evaluators/llm_scorer.py +294 -0
  31. package/helper-apps/cortex-autogen2/tests/evaluators/prompts.py +250 -0
  32. package/helper-apps/cortex-autogen2/tests/evaluators/wordcloud_validator.py +168 -0
  33. package/helper-apps/cortex-autogen2/tests/metrics/__init__.py +1 -0
  34. package/helper-apps/cortex-autogen2/tests/metrics/collector.py +155 -0
  35. package/helper-apps/cortex-autogen2/tests/orchestrator.py +576 -0
  36. package/helper-apps/cortex-autogen2/tests/test_cases.yaml +279 -0
  37. package/helper-apps/cortex-autogen2/tests/test_data.db +0 -0
  38. package/helper-apps/cortex-autogen2/tests/utils/__init__.py +3 -0
  39. package/helper-apps/cortex-autogen2/tests/utils/connectivity.py +112 -0
  40. package/helper-apps/cortex-autogen2/tools/azure_blob_tools.py +74 -24
  41. package/helper-apps/cortex-autogen2/tools/entity_api_registry.json +38 -0
  42. package/helper-apps/cortex-autogen2/tools/file_tools.py +1 -1
  43. package/helper-apps/cortex-autogen2/tools/search_tools.py +436 -238
  44. package/helper-apps/cortex-file-handler/package-lock.json +2 -2
  45. package/helper-apps/cortex-file-handler/package.json +1 -1
  46. package/helper-apps/cortex-file-handler/scripts/setup-test-containers.js +4 -5
  47. package/helper-apps/cortex-file-handler/src/blobHandler.js +36 -144
  48. package/helper-apps/cortex-file-handler/src/services/FileConversionService.js +5 -3
  49. package/helper-apps/cortex-file-handler/src/services/storage/AzureStorageProvider.js +34 -1
  50. package/helper-apps/cortex-file-handler/src/services/storage/GCSStorageProvider.js +22 -0
  51. package/helper-apps/cortex-file-handler/src/services/storage/LocalStorageProvider.js +28 -1
  52. package/helper-apps/cortex-file-handler/src/services/storage/StorageFactory.js +29 -4
  53. package/helper-apps/cortex-file-handler/src/services/storage/StorageProvider.js +11 -0
  54. package/helper-apps/cortex-file-handler/src/services/storage/StorageService.js +1 -1
  55. package/helper-apps/cortex-file-handler/tests/blobHandler.test.js +3 -2
  56. package/helper-apps/cortex-file-handler/tests/checkHashShortLived.test.js +8 -1
  57. package/helper-apps/cortex-file-handler/tests/containerConversionFlow.test.js +5 -2
  58. package/helper-apps/cortex-file-handler/tests/containerNameParsing.test.js +14 -7
  59. package/helper-apps/cortex-file-handler/tests/containerParameterFlow.test.js +5 -2
  60. package/helper-apps/cortex-file-handler/tests/storage/StorageFactory.test.js +31 -19
  61. package/package.json +1 -1
  62. package/server/modelExecutor.js +4 -0
  63. package/server/plugins/claude4VertexPlugin.js +540 -0
  64. package/server/plugins/openAiWhisperPlugin.js +43 -2
  65. package/tests/integration/rest/vendors/claude_streaming.test.js +121 -0
  66. package/tests/unit/plugins/claude4VertexPlugin.test.js +462 -0
  67. package/tests/unit/plugins/claude4VertexToolConversion.test.js +413 -0
  68. package/helper-apps/cortex-autogen/.funcignore +0 -8
  69. package/helper-apps/cortex-autogen/Dockerfile +0 -10
  70. package/helper-apps/cortex-autogen/OAI_CONFIG_LIST +0 -6
  71. package/helper-apps/cortex-autogen/agents.py +0 -493
  72. package/helper-apps/cortex-autogen/agents_extra.py +0 -14
  73. package/helper-apps/cortex-autogen/config.py +0 -18
  74. package/helper-apps/cortex-autogen/data_operations.py +0 -29
  75. package/helper-apps/cortex-autogen/function_app.py +0 -44
  76. package/helper-apps/cortex-autogen/host.json +0 -15
  77. package/helper-apps/cortex-autogen/main.py +0 -38
  78. package/helper-apps/cortex-autogen/prompts.py +0 -196
  79. package/helper-apps/cortex-autogen/prompts_extra.py +0 -5
  80. package/helper-apps/cortex-autogen/requirements.txt +0 -9
  81. package/helper-apps/cortex-autogen/search.py +0 -85
  82. package/helper-apps/cortex-autogen/test.sh +0 -40
  83. package/helper-apps/cortex-autogen/tools/sasfileuploader.py +0 -66
  84. package/helper-apps/cortex-autogen/utils.py +0 -88
  85. package/helper-apps/cortex-autogen2/DigiCertGlobalRootCA.crt.pem +0 -22
  86. package/helper-apps/cortex-autogen2/poetry.lock +0 -3652
@@ -0,0 +1,252 @@
1
+ """
2
+ Docker log collector for test orchestration.
3
+
4
+ Streams Docker container logs and parses them into structured format.
5
+ """
6
+
7
+ import asyncio
8
+ import re
9
+ import logging
10
+ from datetime import datetime
11
+ from typing import List, Dict, Optional
12
+ from collections import Counter
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class LogCollector:
18
+ """Streams and parses Docker container logs."""
19
+
20
+ def __init__(self, container_name: str = "cortex-autogen-function"):
21
+ """
22
+ Initialize the log collector.
23
+
24
+ Args:
25
+ container_name: Name of the Docker container to collect logs from
26
+ """
27
+ self.container_name = container_name
28
+ self.logs: List[Dict] = []
29
+ self.is_collecting = False
30
+ self.process: Optional[asyncio.subprocess.Process] = None
31
+
32
+ async def start_collecting(
33
+ self,
34
+ request_id: Optional[str] = None,
35
+ timeout: int = 300,
36
+ filter_levels: Optional[List[str]] = None
37
+ ) -> List[Dict]:
38
+ """
39
+ Start collecting Docker logs.
40
+
41
+ Args:
42
+ request_id: Optional request ID to filter logs for
43
+ timeout: Maximum time to collect in seconds
44
+ filter_levels: Optional list of log levels to collect (e.g., ['ERROR', 'WARNING'])
45
+
46
+ Returns:
47
+ List of parsed log entries
48
+ """
49
+ self.logs = []
50
+ self.is_collecting = True
51
+
52
+ try:
53
+ # Start docker logs process
54
+ self.process = await asyncio.create_subprocess_exec(
55
+ 'docker', 'logs', '-f', '--tail=0', self.container_name,
56
+ stdout=asyncio.subprocess.PIPE,
57
+ stderr=asyncio.subprocess.PIPE
58
+ )
59
+
60
+ logger.info(f"📝 Log collector started for container: {self.container_name}")
61
+ if request_id:
62
+ logger.info(f" Filtering for request ID: {request_id}")
63
+ if filter_levels:
64
+ logger.info(f" Filtering levels: {', '.join(filter_levels)}")
65
+
66
+ # Read logs from both stdout and stderr
67
+ async def read_stream(stream, stream_name):
68
+ while self.is_collecting:
69
+ line = await stream.readline()
70
+ if not line:
71
+ break
72
+
73
+ try:
74
+ line_str = line.decode('utf-8').strip()
75
+ if not line_str:
76
+ continue
77
+
78
+ # Parse the log line
79
+ log_entry = self._parse_log_line(line_str)
80
+
81
+ if log_entry:
82
+ # Apply filters
83
+ if request_id and request_id not in line_str:
84
+ continue
85
+
86
+ if filter_levels and log_entry.get('level') not in filter_levels:
87
+ continue
88
+
89
+ self.logs.append(log_entry)
90
+
91
+ except Exception as e:
92
+ logger.debug(f"Error parsing log line: {e}")
93
+ continue
94
+
95
+ # Collect logs with timeout
96
+ try:
97
+ await asyncio.wait_for(
98
+ asyncio.gather(
99
+ read_stream(self.process.stdout, 'stdout'),
100
+ read_stream(self.process.stderr, 'stderr')
101
+ ),
102
+ timeout=timeout
103
+ )
104
+ except asyncio.TimeoutError:
105
+ logger.info(f"⏱️ Log collection timeout after {timeout}s")
106
+
107
+ except Exception as e:
108
+ logger.error(f"❌ Log collection error: {e}", exc_info=True)
109
+ finally:
110
+ await self.stop_collecting()
111
+
112
+ logger.info(f"📊 Log collection completed: {len(self.logs)} log entries collected")
113
+ return self.logs
114
+
115
+ async def stop_collecting(self):
116
+ """Stop collecting logs and cleanup."""
117
+ self.is_collecting = False
118
+
119
+ if self.process:
120
+ try:
121
+ self.process.kill()
122
+ await self.process.wait()
123
+ except Exception as e:
124
+ logger.debug(f"Error stopping log collection process: {e}")
125
+ finally:
126
+ self.process = None
127
+
128
+ logger.info("🛑 Log collection stopped")
129
+
130
+ def _parse_log_line(self, line: str) -> Optional[Dict]:
131
+ """
132
+ Parse a log line into structured format.
133
+
134
+ Supports multiple log formats:
135
+ - Standard format: "2024-10-25 12:34:56 - INFO - [agent_name] Message"
136
+ - Python format: "2024-10-25 12:34:56,123 - module - INFO - Message"
137
+ - Simple format: "INFO: Message"
138
+
139
+ Args:
140
+ line: Log line string
141
+
142
+ Returns:
143
+ Parsed log entry dict or None if parsing fails
144
+ """
145
+ # Try standard format first: "YYYY-MM-DD HH:MM:SS - LEVEL - [agent] Message"
146
+ pattern1 = r'(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})[,\s-]+([A-Z]+)\s*-?\s*\[?([^\]]*)\]?\s*[-:]?\s*(.*)'
147
+ match = re.search(pattern1, line)
148
+
149
+ if match:
150
+ timestamp_str = match.group(1)
151
+ level = match.group(2).strip()
152
+ agent = match.group(3).strip() if match.group(3) else None
153
+ message = match.group(4).strip()
154
+
155
+ try:
156
+ timestamp = datetime.strptime(timestamp_str, '%Y-%m-%d %H:%M:%S')
157
+ except:
158
+ timestamp = datetime.now()
159
+
160
+ return {
161
+ 'timestamp': timestamp.isoformat(),
162
+ 'level': level,
163
+ 'agent': agent if agent else None,
164
+ 'message': message,
165
+ 'raw': line
166
+ }
167
+
168
+ # Try simple level format: "LEVEL: Message" or "LEVEL - Message"
169
+ pattern2 = r'^([A-Z]+)[\s:-]+(.+)$'
170
+ match = re.search(pattern2, line)
171
+
172
+ if match:
173
+ return {
174
+ 'timestamp': datetime.now().isoformat(),
175
+ 'level': match.group(1).strip(),
176
+ 'agent': None,
177
+ 'message': match.group(2).strip(),
178
+ 'raw': line
179
+ }
180
+
181
+ # If no pattern matches, store as unparsed
182
+ return {
183
+ 'timestamp': datetime.now().isoformat(),
184
+ 'level': 'UNKNOWN',
185
+ 'agent': None,
186
+ 'message': line,
187
+ 'raw': line
188
+ }
189
+
190
+ def get_logs(
191
+ self,
192
+ level: Optional[str] = None,
193
+ agent: Optional[str] = None
194
+ ) -> List[Dict]:
195
+ """
196
+ Get collected logs with optional filtering.
197
+
198
+ Args:
199
+ level: Filter by log level
200
+ agent: Filter by agent name
201
+
202
+ Returns:
203
+ Filtered list of log entries
204
+ """
205
+ filtered = self.logs
206
+
207
+ if level:
208
+ filtered = [log for log in filtered if log.get('level') == level]
209
+
210
+ if agent:
211
+ filtered = [log for log in filtered if log.get('agent') == agent]
212
+
213
+ return filtered
214
+
215
+ def get_summary(self) -> Dict:
216
+ """
217
+ Get a summary of collected logs.
218
+
219
+ Returns:
220
+ Dictionary with log statistics
221
+ """
222
+ if not self.logs:
223
+ return {
224
+ 'total_logs': 0,
225
+ 'by_level': {},
226
+ 'by_agent': {},
227
+ 'errors': 0,
228
+ 'warnings': 0
229
+ }
230
+
231
+ level_counts = Counter(log.get('level', 'UNKNOWN') for log in self.logs)
232
+ agent_counts = Counter(log.get('agent', 'unknown') for log in self.logs if log.get('agent'))
233
+
234
+ return {
235
+ 'total_logs': len(self.logs),
236
+ 'by_level': dict(level_counts),
237
+ 'by_agent': dict(agent_counts),
238
+ 'errors': level_counts.get('ERROR', 0),
239
+ 'warnings': level_counts.get('WARNING', 0) + level_counts.get('WARN', 0),
240
+ 'first_log': self.logs[0]['timestamp'] if self.logs else None,
241
+ 'last_log': self.logs[-1]['timestamp'] if self.logs else None
242
+ }
243
+
244
+ def get_errors(self) -> List[Dict]:
245
+ """Get all ERROR level logs."""
246
+ return self.get_logs(level='ERROR')
247
+
248
+ def get_warnings(self) -> List[Dict]:
249
+ """Get all WARNING level logs."""
250
+ warnings = self.get_logs(level='WARNING')
251
+ warnings.extend(self.get_logs(level='WARN'))
252
+ return warnings
@@ -0,0 +1,182 @@
1
+ """
2
+ Progress update collector for test orchestration.
3
+
4
+ Subscribes to Redis pub/sub channel and collects progress updates
5
+ during test execution.
6
+ """
7
+
8
+ import redis
9
+ import json
10
+ import asyncio
11
+ import logging
12
+ from datetime import datetime
13
+ from typing import List, Dict, Optional
14
+ from collections import defaultdict
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class ProgressCollector:
20
+ """Collects progress updates from Redis pub/sub channel."""
21
+
22
+ def __init__(self, redis_url: str, channel: str):
23
+ """
24
+ Initialize the progress collector.
25
+
26
+ Args:
27
+ redis_url: Redis connection string (e.g., "redis://localhost:6379")
28
+ channel: Redis channel name to subscribe to
29
+ """
30
+ self.redis_url = redis_url
31
+ self.channel = channel
32
+ self.updates: List[Dict] = []
33
+ self.is_collecting = False
34
+ self.final_result = None
35
+
36
+ async def start_collecting(
37
+ self,
38
+ request_id: str,
39
+ timeout: int = 300,
40
+ stop_on_final: bool = True
41
+ ) -> List[Dict]:
42
+ """
43
+ Start collecting progress updates for a specific request.
44
+
45
+ Args:
46
+ request_id: The request ID to filter updates for
47
+ timeout: Maximum time to collect in seconds
48
+ stop_on_final: Stop collecting when final update (progress=1.0 or data field) is received
49
+
50
+ Returns:
51
+ List of progress updates collected
52
+ """
53
+ self.updates = []
54
+ self.is_collecting = True
55
+ self.final_result = None
56
+
57
+ try:
58
+ # Create Redis client in executor to avoid blocking
59
+ redis_client = redis.from_url(self.redis_url)
60
+ pubsub = redis_client.pubsub()
61
+ pubsub.subscribe(self.channel)
62
+
63
+ logger.info(f"📡 Progress collector started for request {request_id}")
64
+ logger.info(f" Subscribed to channel: {self.channel}")
65
+ logger.info(f" Timeout: {timeout}s")
66
+
67
+ start_time = datetime.now()
68
+ message_count = 0
69
+
70
+ # Listen for messages with timeout
71
+ for message in pubsub.listen():
72
+ if not self.is_collecting:
73
+ break
74
+
75
+ if message['type'] == 'message':
76
+ try:
77
+ data = json.loads(message['data'])
78
+
79
+ # Only collect updates for our request
80
+ if data.get('requestId') == request_id:
81
+ message_count += 1
82
+
83
+ update = {
84
+ 'timestamp': datetime.now().isoformat(),
85
+ 'progress': data.get('progress', 0.0),
86
+ 'info': data.get('info', ''),
87
+ 'data': data.get('data')
88
+ }
89
+
90
+ self.updates.append(update)
91
+
92
+ # Log progress update
93
+ progress_pct = int(update['progress'] * 100)
94
+ logger.info(f" Progress: {progress_pct}% - {update['info']}")
95
+
96
+ # Check if this is the final update
97
+ if stop_on_final:
98
+ if update['data'] is not None:
99
+ self.final_result = update['data']
100
+ logger.info(f"✅ Final result received (with data field)")
101
+ break
102
+ elif update['progress'] >= 1.0:
103
+ logger.info(f"✅ Final progress reached (100%)")
104
+ # Wait a bit more to catch any late final result
105
+ await asyncio.sleep(2)
106
+ break
107
+
108
+ except json.JSONDecodeError as e:
109
+ logger.warning(f"Failed to parse message: {e}")
110
+ continue
111
+ except Exception as e:
112
+ logger.error(f"Error processing message: {e}")
113
+ continue
114
+
115
+ # Check timeout
116
+ elapsed = (datetime.now() - start_time).total_seconds()
117
+ if elapsed > timeout:
118
+ logger.warning(f"⏱️ Progress collection timeout after {timeout}s")
119
+ break
120
+
121
+ # Cleanup
122
+ pubsub.unsubscribe()
123
+ pubsub.close()
124
+ redis_client.close()
125
+
126
+ logger.info(f"📊 Progress collection completed: {message_count} updates collected")
127
+
128
+ except redis.ConnectionError as e:
129
+ logger.error(f"❌ Redis connection error: {e}")
130
+ except Exception as e:
131
+ logger.error(f"❌ Progress collection error: {e}", exc_info=True)
132
+ finally:
133
+ self.is_collecting = False
134
+
135
+ return self.updates
136
+
137
+ def stop_collecting(self):
138
+ """Stop collecting progress updates."""
139
+ self.is_collecting = False
140
+ logger.info("🛑 Progress collection stopped manually")
141
+
142
+ def get_updates(self) -> List[Dict]:
143
+ """Get all collected updates."""
144
+ return self.updates
145
+
146
+ def get_final_result(self) -> Optional[Dict]:
147
+ """Get the final result data if received."""
148
+ return self.final_result
149
+
150
+ def get_summary(self) -> Dict:
151
+ """
152
+ Get a summary of collected progress updates.
153
+
154
+ Returns:
155
+ Dictionary with statistics about the updates
156
+ """
157
+ if not self.updates:
158
+ return {
159
+ 'total_updates': 0,
160
+ 'duration_seconds': 0,
161
+ 'avg_interval_seconds': 0,
162
+ 'final_progress': 0
163
+ }
164
+
165
+ timestamps = [datetime.fromisoformat(u['timestamp']) for u in self.updates]
166
+ intervals = []
167
+
168
+ for i in range(1, len(timestamps)):
169
+ interval = (timestamps[i] - timestamps[i-1]).total_seconds()
170
+ intervals.append(interval)
171
+
172
+ duration = (timestamps[-1] - timestamps[0]).total_seconds() if len(timestamps) > 1 else 0
173
+
174
+ return {
175
+ 'total_updates': len(self.updates),
176
+ 'duration_seconds': duration,
177
+ 'avg_interval_seconds': sum(intervals) / len(intervals) if intervals else 0,
178
+ 'min_interval_seconds': min(intervals) if intervals else 0,
179
+ 'max_interval_seconds': max(intervals) if intervals else 0,
180
+ 'final_progress': self.updates[-1]['progress'] if self.updates else 0,
181
+ 'has_final_result': self.final_result is not None
182
+ }
@@ -0,0 +1,15 @@
1
+ """
2
+ Pytest configuration file.
3
+
4
+ This file can be used for pytest-based testing in the future.
5
+ For now, use the CLI runner: python tests/cli/run_tests.py
6
+ """
7
+
8
+ import pytest
9
+ import logging
10
+
11
+ # Configure logging for tests
12
+ logging.basicConfig(
13
+ level=logging.INFO,
14
+ format='%(asctime)s - %(levelname)s - %(message)s'
15
+ )
@@ -0,0 +1 @@
1
+ """Database layer for test results storage."""