Skip to content

Commit 2b0d082

Browse files
authored
🔨 use arm host for unitest runner and update monitor auto disable
2 parents 07de4fd + 3755b54 commit 2b0d082

File tree

9 files changed

+154
-96
lines changed

9 files changed

+154
-96
lines changed

.github/workflows/auto-unit-test.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ on:
1010
runner_label_json:
1111
description: 'runner array in json format (e.g. ["ubuntu-latest"] or ["self-hosted"])'
1212
required: false
13-
default: '["ubuntu-latest"]'
13+
default: '["ubuntu-24.04-arm"]'
1414
pull_request:
1515
branches: [develop]
1616
paths:
@@ -28,7 +28,7 @@ on:
2828

2929
jobs:
3030
test:
31-
runs-on: ${{ fromJson(github.event.inputs.runner_label_json || '["ubuntu-latest"]') }}
31+
runs-on: ${{ fromJson(github.event.inputs.runner_label_json || '["ubuntu-24.04-arm"]') }}
3232
steps:
3333
- name: Checkout code
3434
uses: actions/checkout@v4
@@ -45,7 +45,7 @@ jobs:
4545
run: |
4646
cd backend
4747
uv sync --extra data-process --extra test
48-
uv pip install -e ../sdk
48+
uv pip install -e "../sdk[dev]"
4949
cd ..
5050
5151
- name: Run all tests and collect coverage

backend/apps/data_process_app.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
TaskRequest,
1313
)
1414
from data_process.tasks import process_and_forward, process_sync
15-
from data_process.utils import get_task_info
1615
from services.data_process_service import get_data_process_service
1716

1817
logger = logging.getLogger("data_process.app")

backend/database/client.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44
from typing import Any, BinaryIO, Dict, List, Optional, Tuple
55

66
import boto3
7-
import psycopg2.extras
8-
from psycopg2 import extensions
7+
import psycopg2
98
from botocore.client import Config
109
from botocore.exceptions import ClientError
1110
from sqlalchemy import create_engine

sdk/nexent/monitor/monitoring.py

Lines changed: 56 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,29 @@
66
integration with OpenTelemetry, Jaeger, Prometheus, and Grafana.
77
88
This module uses a singleton pattern for consistent monitoring across the SDK.
9+
When OpenTelemetry dependencies are not available, the module gracefully degrades
10+
and disables monitoring functionality without breaking the application.
11+
12+
Installation:
13+
- Basic: pip install nexent
14+
- With monitoring: pip install nexent[performance]
915
"""
1016

11-
from opentelemetry.trace.status import Status, StatusCode
12-
from opentelemetry.exporter.prometheus import PrometheusMetricReader
13-
from opentelemetry.sdk.metrics import MeterProvider
14-
from opentelemetry.sdk.trace.export import BatchSpanProcessor
15-
from opentelemetry.sdk.trace import TracerProvider
16-
from opentelemetry.instrumentation.requests import RequestsInstrumentor
17-
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
18-
from opentelemetry.exporter.jaeger.thrift import JaegerExporter
19-
from opentelemetry import trace, metrics
20-
from opentelemetry.sdk.resources import Resource
17+
# Optional OpenTelemetry imports - gracefully handle missing dependencies
18+
try:
19+
from opentelemetry.trace.status import Status, StatusCode
20+
from opentelemetry.exporter.prometheus import PrometheusMetricReader
21+
from opentelemetry.sdk.metrics import MeterProvider
22+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
23+
from opentelemetry.sdk.trace import TracerProvider
24+
from opentelemetry.instrumentation.requests import RequestsInstrumentor
25+
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
26+
from opentelemetry.exporter.jaeger.thrift import JaegerExporter
27+
from opentelemetry import trace, metrics
28+
from opentelemetry.sdk.resources import Resource
29+
OPENTELEMETRY_AVAILABLE = True
30+
except ImportError:
31+
OPENTELEMETRY_AVAILABLE = False
2132
import logging
2233
import time
2334
import functools
@@ -27,11 +38,13 @@
2738

2839
logger = logging.getLogger(__name__)
2940

30-
# Import OpenTelemetry dependencies (assumed to be available in SDK)
31-
3241
F = TypeVar('F', bound=Callable[..., Any])
3342

3443

44+
def is_opentelemetry_available() -> bool:
45+
"""Check if OpenTelemetry dependencies are available."""
46+
return OPENTELEMETRY_AVAILABLE
47+
3548
@dataclass
3649
class MonitoringConfig:
3750
"""Configuration for monitoring system."""
@@ -42,6 +55,15 @@ class MonitoringConfig:
4255
telemetry_sample_rate: float = 1.0
4356
llm_slow_request_threshold_seconds: float = 5.0
4457
llm_slow_token_rate_threshold: float = 10.0
58+
59+
def __post_init__(self):
60+
"""Validate configuration and adjust based on OpenTelemetry availability."""
61+
if self.enable_telemetry and not OPENTELEMETRY_AVAILABLE:
62+
logger.warning(
63+
"OpenTelemetry dependencies not available. Disabling telemetry. "
64+
"Install with: pip install nexent[performance]"
65+
)
66+
self.enable_telemetry = False
4567

4668

4769
class MonitoringManager:
@@ -90,6 +112,13 @@ def _init_telemetry(self) -> None:
90112
logger.info("Telemetry is disabled by configuration")
91113
return
92114

115+
if not OPENTELEMETRY_AVAILABLE:
116+
logger.warning(
117+
"OpenTelemetry dependencies not available. Telemetry initialization skipped. "
118+
"Install with: pip install nexent[performance]"
119+
)
120+
return
121+
93122
try:
94123
# Setup tracing with proper service name resource
95124
resource = Resource.create({
@@ -164,7 +193,9 @@ def _init_telemetry(self) -> None:
164193
@property
165194
def is_enabled(self) -> bool:
166195
"""Check if monitoring is enabled."""
167-
return self._config is not None and self._config.enable_telemetry
196+
return (self._config is not None and
197+
self._config.enable_telemetry and
198+
OPENTELEMETRY_AVAILABLE)
168199

169200
@property
170201
def tracer(self):
@@ -174,11 +205,16 @@ def tracer(self):
174205
def setup_fastapi_app(self, app) -> bool:
175206
"""Setup monitoring for a FastAPI application."""
176207
try:
177-
if self.is_enabled and app:
208+
if self.is_enabled and app and OPENTELEMETRY_AVAILABLE:
178209
FastAPIInstrumentor.instrument_app(app)
179210
logger.info(
180211
"FastAPI application monitoring initialized successfully")
181212
return True
213+
elif not OPENTELEMETRY_AVAILABLE:
214+
logger.warning(
215+
"OpenTelemetry not available. FastAPI monitoring skipped. "
216+
"Install with: pip install nexent[performance]"
217+
)
182218
return False
183219
except Exception as e:
184220
logger.error(f"Failed to initialize FastAPI monitoring: {e}")
@@ -187,9 +223,7 @@ def setup_fastapi_app(self, app) -> bool:
187223
@contextmanager
188224
def trace_llm_request(self, operation_name: str, model_name: str, **attributes: Any) -> Iterator[Optional[Any]]:
189225
"""Context manager for tracing LLM requests with comprehensive metrics."""
190-
if not self.is_enabled or not self._tracer:
191-
logger.warning(
192-
f"⚠️ trace_llm_request returning None: is_enabled={self.is_enabled}, has_tracer={self._tracer is not None}")
226+
if not self.is_enabled or not OPENTELEMETRY_AVAILABLE or not self._tracer:
193227
yield None
194228
return
195229

@@ -218,13 +252,13 @@ def trace_llm_request(self, operation_name: str, model_name: str, **attributes:
218252

219253
def get_current_span(self) -> Optional[Any]:
220254
"""Get the current active span."""
221-
if not self.is_enabled:
255+
if not self.is_enabled or not OPENTELEMETRY_AVAILABLE:
222256
return None
223257
return trace.get_current_span()
224258

225259
def add_span_event(self, name: str, attributes: Optional[Dict[str, Any]] = None) -> None:
226260
"""Add an event to the current span."""
227-
if not self.is_enabled:
261+
if not self.is_enabled or not OPENTELEMETRY_AVAILABLE:
228262
return
229263

230264
span = trace.get_current_span()
@@ -233,7 +267,7 @@ def add_span_event(self, name: str, attributes: Optional[Dict[str, Any]] = None)
233267

234268
def set_span_attributes(self, **attributes: Any) -> None:
235269
"""Set attributes on the current span."""
236-
if not self.is_enabled:
270+
if not self.is_enabled or not OPENTELEMETRY_AVAILABLE:
237271
return
238272

239273
span = trace.get_current_span()
@@ -246,7 +280,7 @@ def create_token_tracker(self, model_name: str, span: Optional[Any] = None) -> '
246280

247281
def record_llm_metrics(self, metric_type: str, value: float, attributes: Dict[str, Any]) -> None:
248282
"""Record LLM-specific metrics."""
249-
if not self.is_enabled:
283+
if not self.is_enabled or not OPENTELEMETRY_AVAILABLE:
250284
return
251285

252286
if metric_type == "ttft" and self._llm_ttft_duration:
@@ -499,4 +533,5 @@ async def my_function():
499533
'MonitoringManager',
500534
'LLMTokenTracker',
501535
'get_monitoring_manager',
536+
'is_opentelemetry_available',
502537
]

sdk/nexent/vector_database/__init__.py

Whitespace-only changes.

test/backend/app/test_agent_app.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import atexit
2-
from unittest.mock import patch, Mock
2+
from unittest.mock import patch, Mock, MagicMock
33
import os
44
import sys
55
import types
@@ -14,19 +14,22 @@
1414
backend_dir = os.path.abspath(os.path.join(current_dir, "../../../backend"))
1515
sys.path.insert(0, backend_dir)
1616

17-
from apps.agent_app import router
17+
# Mock boto3 before importing backend modules
18+
boto3_mock = MagicMock()
19+
sys.modules['boto3'] = boto3_mock
20+
21+
# Import target endpoints with all external dependencies patched
22+
with patch('backend.database.client.MinioClient') as minio_mock, \
23+
patch('elasticsearch.Elasticsearch', return_value=MagicMock()) as es_mock:
24+
minio_mock.return_value = MagicMock()
25+
26+
from apps.agent_app import router
1827

1928
# Apply patches before importing any app modules (similar to test_base_app.py)
2029

2130
patches = [
22-
# Mock boto3 client
23-
patch('boto3.client', return_value=Mock()),
24-
# Mock boto3 resource
25-
patch('boto3.resource', return_value=Mock()),
2631
# Mock database sessions
27-
patch('backend.database.client.get_db_session', return_value=Mock()),
28-
# Mock Elasticsearch to prevent connection errors
29-
patch('elasticsearch.Elasticsearch', return_value=Mock())
32+
patch('backend.database.client.get_db_session', return_value=Mock())
3033
]
3134

3235
for p in patches:

test/backend/services/test_agent_service.py

Lines changed: 32 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,11 @@
1-
import backend.services.agent_service as agent_service
2-
from backend.services.agent_service import update_agent_info_impl
3-
from backend.services.agent_service import get_creating_sub_agent_info_impl
4-
from backend.services.agent_service import list_all_agent_info_impl
5-
from backend.services.agent_service import get_agent_info_impl
6-
from backend.services.agent_service import get_creating_sub_agent_id_service
7-
from backend.services.agent_service import get_enable_tool_id_by_agent_id
8-
from backend.services.agent_service import (
9-
get_agent_call_relationship_impl,
10-
delete_agent_impl,
11-
export_agent_impl,
12-
export_agent_by_agent_id,
13-
import_agent_by_agent_id,
14-
insert_related_agent_impl,
15-
load_default_agents_json_file,
16-
clear_agent_memory,
17-
import_agent_impl,
18-
get_agent_id_by_name,
19-
save_messages,
20-
prepare_agent_run,
21-
run_agent_stream,
22-
stop_agent_tasks,
23-
)
24-
from fastapi import Request
25-
from consts.model import ExportAndImportAgentInfo, ExportAndImportDataFormat, MCPInfo, AgentRequest
261
import sys
272
import asyncio
283
import json
294
from unittest.mock import patch, MagicMock, mock_open, call, Mock, AsyncMock
305

316
import pytest
327
from fastapi.responses import StreamingResponse
8+
from fastapi import Request
339

3410
# Import the actual ToolConfig model for testing before any mocking
3511
from nexent.core.agents.agent_model import ToolConfig
@@ -38,17 +14,38 @@
3814
boto3_mock = MagicMock()
3915
sys.modules['boto3'] = boto3_mock
4016

41-
# Mock Elasticsearch
17+
# Mock external dependencies before importing backend modules that might initialize them
18+
with patch('backend.database.client.MinioClient') as minio_mock, \
19+
patch('elasticsearch.Elasticsearch', return_value=MagicMock()) as es_mock:
20+
minio_mock.return_value = MagicMock()
21+
22+
import backend.services.agent_service as agent_service
23+
from backend.services.agent_service import update_agent_info_impl
24+
from backend.services.agent_service import get_creating_sub_agent_info_impl
25+
from backend.services.agent_service import list_all_agent_info_impl
26+
from backend.services.agent_service import get_agent_info_impl
27+
from backend.services.agent_service import get_creating_sub_agent_id_service
28+
from backend.services.agent_service import get_enable_tool_id_by_agent_id
29+
from backend.services.agent_service import (
30+
get_agent_call_relationship_impl,
31+
delete_agent_impl,
32+
export_agent_impl,
33+
export_agent_by_agent_id,
34+
import_agent_by_agent_id,
35+
insert_related_agent_impl,
36+
load_default_agents_json_file,
37+
clear_agent_memory,
38+
import_agent_impl,
39+
get_agent_id_by_name,
40+
save_messages,
41+
prepare_agent_run,
42+
run_agent_stream,
43+
stop_agent_tasks,
44+
)
45+
from consts.model import ExportAndImportAgentInfo, ExportAndImportDataFormat, MCPInfo, AgentRequest
46+
47+
# Mock Elasticsearch (already done in the import section above, but keeping for reference)
4248
elasticsearch_client_mock = MagicMock()
43-
patch('elasticsearch._sync.client.Elasticsearch',
44-
return_value=elasticsearch_client_mock).start()
45-
patch('elasticsearch.Elasticsearch',
46-
return_value=elasticsearch_client_mock).start()
47-
48-
# Mock ElasticSearchCore
49-
elasticsearch_core_mock = MagicMock()
50-
patch('sdk.nexent.vector_database.elasticsearch_core.ElasticSearchCore',
51-
return_value=elasticsearch_core_mock).start()
5249

5350

5451
# Mock memory-related modules

test/run_all_test.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import os
22
import subprocess
3-
import glob
43
import sys
54
import logging
65

0 commit comments

Comments
 (0)