agent-lab-sdk 0.1.57.dev3__tar.gz → 0.1.58.dev1__tar.gz
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.
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/PKG-INFO +27 -4
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/README.md +26 -3
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/core/settings.py +4 -2
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/langgraph/checkpoint/agw_saver.py +28 -2
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/llm/limiting.py +70 -28
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/llm/llm.py +4 -0
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/llm/throttled.py +8 -0
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/metrics/create.py +5 -2
- agent_lab_sdk-0.1.58.dev1/agent_lab_sdk/tools/search_plugin/__init__.py +4 -0
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/tools/search_plugin/search_plugin.py +14 -2
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk.egg-info/PKG-INFO +27 -4
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/pyproject.toml +1 -1
- agent_lab_sdk-0.1.57.dev3/agent_lab_sdk/tools/search_plugin/__init__.py +0 -4
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/LICENSE +0 -0
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/__init__.py +0 -0
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/core/__init__.py +0 -0
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/core/unified_semaphore.py +0 -0
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/langgraph/checkpoint/__init__.py +0 -0
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/langgraph/checkpoint/serde.py +0 -0
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/llm/__init__.py +0 -0
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/llm/agw_token_manager.py +0 -0
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/llm/clients/__init__.py +0 -0
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/llm/clients/agent_service_client.py +0 -0
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/llm/clients/redis_client.py +0 -0
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/llm/gigachat_token_manager.py +0 -0
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/llm/managers/__init__.py +0 -0
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/llm/managers/ags_token_manager.py +0 -0
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/llm/managers/limits_manager.py +0 -0
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/llm/managers/redis/__init__.py +0 -0
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/llm/managers/redis/ratelimiter.py +0 -0
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/llm/managers/redis/semaphore.py +0 -0
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/llm/retry_utils.py +0 -0
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/llm/token_manager_utils.py +0 -0
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/metrics/__init__.py +0 -0
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/metrics/metrics.py +0 -0
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/schema/__init__.py +0 -0
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/schema/input_types.py +0 -0
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/schema/log_message.py +0 -0
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/storage/__init__.py +0 -0
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/storage/storage.py +0 -0
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/storage/storage_v2.py +0 -0
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/tools/__init__.py +0 -0
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/tools/search_plugin/tools/__init__.py +0 -0
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/tools/search_plugin/tools/search_image.py +0 -0
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/tools/search_plugin/tools/search_plugin_web_search.py +0 -0
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk.egg-info/SOURCES.txt +0 -0
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk.egg-info/dependency_links.txt +0 -0
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk.egg-info/requires.txt +0 -0
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk.egg-info/top_level.txt +0 -0
- {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-lab-sdk
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.58.dev1
|
|
4
4
|
Summary: SDK для работы с Agent Lab
|
|
5
5
|
Author-email: Andrew Ohurtsov <andermirik@yandex.com>
|
|
6
6
|
License: Proprietary and Confidential — All Rights Reserved
|
|
@@ -136,6 +136,7 @@ model = get_model(
|
|
|
136
136
|
| `SDK_AGENT_LAB_GIGACHAT_CREDENTIAL_ID` | Credential Id для получения токена из Agent Service | - |
|
|
137
137
|
| `AGENT_SERVICE_NAME` | Имя сервиса агента (обязательно) | - |
|
|
138
138
|
| `SDK_AGENT_LAB_USE_TOKEN_PROVIDER_AGS` | Использовать `AgsTokenManager` для получения токена и лимитов GigaChat | `false` |
|
|
139
|
+
| `SDK_AGENT_LAB_GIGACHAT_USE_MTLS` | Не использовать токен менеджеры и глобальный лимитер или использовать | `false` |
|
|
139
140
|
|
|
140
141
|
|
|
141
142
|
---
|
|
@@ -189,6 +190,9 @@ chat_with_retry = ThrottledGigaChat(
|
|
|
189
190
|
| `embed_waiting_tasks` | Число задач, ожидающих слота эмбеддингов | Gauge |
|
|
190
191
|
| `embed_wait_time_seconds` | Время ожидания слота эмбеддингов (секунды) | Histogram |
|
|
191
192
|
|
|
193
|
+
Все метрики семафоров экспортируются с label `credential_id`.
|
|
194
|
+
Если credential не задан (локальный режим), используется значение `credential_id="local"`.
|
|
195
|
+
|
|
192
196
|
---
|
|
193
197
|
|
|
194
198
|
## 3. Модуль `agent_lab_sdk.metrics`
|
|
@@ -518,11 +522,30 @@ pip install -e .
|
|
|
518
522
|
Позволяет использовать API search plugin + llm tool + лимитирование запросов
|
|
519
523
|
|
|
520
524
|
```python
|
|
521
|
-
from agent_lab_sdk.tools.search_plugin.search_plugin import SearchPlugin
|
|
525
|
+
from agent_lab_sdk.tools.search_plugin.search_plugin import SearchPlugin, SearchMode
|
|
526
|
+
from agent_lab_sdk.tools.search_plugin.tools import get_search_plugin_tool, get_search_image_tool
|
|
527
|
+
from langgraph.prebuilt import ToolNode
|
|
528
|
+
from langgraph.graph import StateGraph
|
|
529
|
+
from agent_lab_sdk.llm import get_model
|
|
522
530
|
|
|
523
531
|
plugin = SearchPlugin()
|
|
524
|
-
|
|
525
|
-
print(
|
|
532
|
+
retrieval_result = plugin.retrieval("Что такое SDK?", SearchMode.actual_info_web_search)
|
|
533
|
+
print(retrieval_result.documents, retrieval_result.sources, retrieval_result.images)
|
|
534
|
+
|
|
535
|
+
images_result = plugin.rambler_images_proxy("Логотип питона")
|
|
536
|
+
for img in images_result:
|
|
537
|
+
print(img.url, img.image_link)
|
|
538
|
+
|
|
539
|
+
search_tool = get_search_plugin_tool()
|
|
540
|
+
image_tool = get_search_image_tool()
|
|
541
|
+
tools = [search_tool, image_tool]
|
|
542
|
+
|
|
543
|
+
llm = get_model().bind_tools(tools)
|
|
544
|
+
|
|
545
|
+
graph = StateGraph(State)
|
|
546
|
+
graph.add_node("tools", ToolNode(tools), metadata={'description': 'Вызов инструментов'})
|
|
547
|
+
|
|
548
|
+
|
|
526
549
|
```
|
|
527
550
|
|
|
528
551
|
Реализованы методы POST /retrieval, GET /rambler_proxy_images
|
|
@@ -101,6 +101,7 @@ model = get_model(
|
|
|
101
101
|
| `SDK_AGENT_LAB_GIGACHAT_CREDENTIAL_ID` | Credential Id для получения токена из Agent Service | - |
|
|
102
102
|
| `AGENT_SERVICE_NAME` | Имя сервиса агента (обязательно) | - |
|
|
103
103
|
| `SDK_AGENT_LAB_USE_TOKEN_PROVIDER_AGS` | Использовать `AgsTokenManager` для получения токена и лимитов GigaChat | `false` |
|
|
104
|
+
| `SDK_AGENT_LAB_GIGACHAT_USE_MTLS` | Не использовать токен менеджеры и глобальный лимитер или использовать | `false` |
|
|
104
105
|
|
|
105
106
|
|
|
106
107
|
---
|
|
@@ -154,6 +155,9 @@ chat_with_retry = ThrottledGigaChat(
|
|
|
154
155
|
| `embed_waiting_tasks` | Число задач, ожидающих слота эмбеддингов | Gauge |
|
|
155
156
|
| `embed_wait_time_seconds` | Время ожидания слота эмбеддингов (секунды) | Histogram |
|
|
156
157
|
|
|
158
|
+
Все метрики семафоров экспортируются с label `credential_id`.
|
|
159
|
+
Если credential не задан (локальный режим), используется значение `credential_id="local"`.
|
|
160
|
+
|
|
157
161
|
---
|
|
158
162
|
|
|
159
163
|
## 3. Модуль `agent_lab_sdk.metrics`
|
|
@@ -483,11 +487,30 @@ pip install -e .
|
|
|
483
487
|
Позволяет использовать API search plugin + llm tool + лимитирование запросов
|
|
484
488
|
|
|
485
489
|
```python
|
|
486
|
-
from agent_lab_sdk.tools.search_plugin.search_plugin import SearchPlugin
|
|
490
|
+
from agent_lab_sdk.tools.search_plugin.search_plugin import SearchPlugin, SearchMode
|
|
491
|
+
from agent_lab_sdk.tools.search_plugin.tools import get_search_plugin_tool, get_search_image_tool
|
|
492
|
+
from langgraph.prebuilt import ToolNode
|
|
493
|
+
from langgraph.graph import StateGraph
|
|
494
|
+
from agent_lab_sdk.llm import get_model
|
|
487
495
|
|
|
488
496
|
plugin = SearchPlugin()
|
|
489
|
-
|
|
490
|
-
print(
|
|
497
|
+
retrieval_result = plugin.retrieval("Что такое SDK?", SearchMode.actual_info_web_search)
|
|
498
|
+
print(retrieval_result.documents, retrieval_result.sources, retrieval_result.images)
|
|
499
|
+
|
|
500
|
+
images_result = plugin.rambler_images_proxy("Логотип питона")
|
|
501
|
+
for img in images_result:
|
|
502
|
+
print(img.url, img.image_link)
|
|
503
|
+
|
|
504
|
+
search_tool = get_search_plugin_tool()
|
|
505
|
+
image_tool = get_search_image_tool()
|
|
506
|
+
tools = [search_tool, image_tool]
|
|
507
|
+
|
|
508
|
+
llm = get_model().bind_tools(tools)
|
|
509
|
+
|
|
510
|
+
graph = StateGraph(State)
|
|
511
|
+
graph.add_node("tools", ToolNode(tools), metadata={'description': 'Вызов инструментов'})
|
|
512
|
+
|
|
513
|
+
|
|
491
514
|
```
|
|
492
515
|
|
|
493
516
|
Реализованы методы POST /retrieval, GET /rambler_proxy_images
|
|
@@ -34,11 +34,13 @@ class Settings(BaseSettings):
|
|
|
34
34
|
SEARCH_PLUGIN_KEY: str | None = Field(alias="SEARCH_PLUGIN_KEY", default=None)
|
|
35
35
|
SEARCH_PLUGIN_URL: str | None = Field(alias="SEARCH_PLUGIN_URL", default=None)
|
|
36
36
|
SEARCH_IMAGES_PLUGIN_URL: str | None = Field(alias="SEARCH_IMAGES_PLUGIN_URL", default=None)
|
|
37
|
-
SEARCH_PLUGIN_RETRIEVAL_TIMEOUT:
|
|
38
|
-
SEARCH_PLUGIN_RAMBLER_PROXY_TIMEOUT:
|
|
37
|
+
SEARCH_PLUGIN_RETRIEVAL_TIMEOUT: float = 30 # seconds
|
|
38
|
+
SEARCH_PLUGIN_RAMBLER_PROXY_TIMEOUT: float = 30 # seconds
|
|
39
39
|
|
|
40
40
|
SEARCH_PLUGIN_MAX_CONCURRENCY: int = 100000
|
|
41
41
|
|
|
42
|
+
GIGACHAT_USE_MTLS: bool = False
|
|
43
|
+
|
|
42
44
|
class Config:
|
|
43
45
|
env_prefix = ENV_PREFIX
|
|
44
46
|
|
|
@@ -491,6 +491,16 @@ class AsyncAGWCheckpointSaver(BaseCheckpointSaver):
|
|
|
491
491
|
# Всё остальное — лист, через _safe_load
|
|
492
492
|
return self._safe_load(obj)
|
|
493
493
|
|
|
494
|
+
# ----------------------- per-request headers --------------------
|
|
495
|
+
def _per_request_headers(self, cfg: RunnableConfig | None) -> Dict[str, str]:
|
|
496
|
+
"""Extract per-request headers from RunnableConfig (e.g. x-user-id)."""
|
|
497
|
+
if not cfg:
|
|
498
|
+
return {}
|
|
499
|
+
user_id = cfg.get("configurable", {}).get("user_id")
|
|
500
|
+
if user_id:
|
|
501
|
+
return {"x-user-id": str(user_id)}
|
|
502
|
+
return {}
|
|
503
|
+
|
|
494
504
|
# ----------------------- config <-> api --------------------------
|
|
495
505
|
def _to_api_config(self, cfg: RunnableConfig | None) -> Dict[str, Any]:
|
|
496
506
|
if not cfg:
|
|
@@ -594,10 +604,14 @@ class AsyncAGWCheckpointSaver(BaseCheckpointSaver):
|
|
|
594
604
|
method: str,
|
|
595
605
|
path: str,
|
|
596
606
|
*,
|
|
607
|
+
headers: Dict[str, str] | None = None,
|
|
597
608
|
ok_statuses: Iterable[int] | None = None,
|
|
598
609
|
label_path: str | None = None,
|
|
599
610
|
**kw,
|
|
600
611
|
) -> httpx.Response:
|
|
612
|
+
if headers:
|
|
613
|
+
kw["headers"] = {**kw.get("headers", {}), **headers}
|
|
614
|
+
|
|
601
615
|
if "json" in kw:
|
|
602
616
|
payload = kw.pop("json")
|
|
603
617
|
kw["data"] = orjson.dumps(payload)
|
|
@@ -746,7 +760,12 @@ class AsyncAGWCheckpointSaver(BaseCheckpointSaver):
|
|
|
746
760
|
path = f"/checkpoint/{tid}"
|
|
747
761
|
params = None
|
|
748
762
|
|
|
749
|
-
resp = await self._http(
|
|
763
|
+
resp = await self._http(
|
|
764
|
+
"GET", path,
|
|
765
|
+
params=params,
|
|
766
|
+
headers=self._per_request_headers(cfg),
|
|
767
|
+
label_path=path_template,
|
|
768
|
+
)
|
|
750
769
|
logger.debug("AGW aget_tuple response: %s", resp.text)
|
|
751
770
|
|
|
752
771
|
if not resp.text:
|
|
@@ -774,6 +793,7 @@ class AsyncAGWCheckpointSaver(BaseCheckpointSaver):
|
|
|
774
793
|
"POST",
|
|
775
794
|
"/checkpoint/list",
|
|
776
795
|
json=payload,
|
|
796
|
+
headers=self._per_request_headers(cfg),
|
|
777
797
|
label_path="/checkpoint/list",
|
|
778
798
|
)
|
|
779
799
|
logger.debug("AGW alist response: %s", resp.text)
|
|
@@ -794,7 +814,12 @@ class AsyncAGWCheckpointSaver(BaseCheckpointSaver):
|
|
|
794
814
|
"metadata": self._enc_meta(get_checkpoint_metadata(cfg, metadata)),
|
|
795
815
|
"newVersions": self._safe_dump_deep(new_versions),
|
|
796
816
|
}
|
|
797
|
-
resp = await self._http(
|
|
817
|
+
resp = await self._http(
|
|
818
|
+
"POST", "/checkpoint",
|
|
819
|
+
json=payload,
|
|
820
|
+
headers=self._per_request_headers(cfg),
|
|
821
|
+
label_path="/checkpoint",
|
|
822
|
+
)
|
|
798
823
|
logger.debug("AGW aput response: %s", resp.text)
|
|
799
824
|
resp.raise_for_status()
|
|
800
825
|
return resp.json()["config"]
|
|
@@ -817,6 +842,7 @@ class AsyncAGWCheckpointSaver(BaseCheckpointSaver):
|
|
|
817
842
|
"POST",
|
|
818
843
|
"/checkpoint/writes",
|
|
819
844
|
json=payload,
|
|
845
|
+
headers=self._per_request_headers(cfg),
|
|
820
846
|
label_path="/checkpoint/writes",
|
|
821
847
|
)
|
|
822
848
|
logger.debug("AGW aput_writes response: %s", resp.text)
|
|
@@ -8,7 +8,10 @@ from agent_lab_sdk.core.unified_semaphore import UnifiedSemaphore
|
|
|
8
8
|
from agent_lab_sdk.llm.managers.ags_token_manager import AgsTokenManager
|
|
9
9
|
from agent_lab_sdk.metrics.create import create_metrics
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
_redis_throttle_enabled = settings.REDIS_THROTTLE_ENABLED
|
|
12
|
+
if settings.GIGACHAT_USE_MTLS:
|
|
13
|
+
_redis_throttle_enabled = False
|
|
14
|
+
if _redis_throttle_enabled:
|
|
12
15
|
from agent_lab_sdk.llm.managers.limits_manager import get_limits_manager
|
|
13
16
|
|
|
14
17
|
logger = logging.getLogger(__name__)
|
|
@@ -28,10 +31,11 @@ class Limiting:
|
|
|
28
31
|
self._internal_semaphore = internal_semaphore
|
|
29
32
|
self._model_type = model_type
|
|
30
33
|
|
|
31
|
-
self.
|
|
32
|
-
self.
|
|
33
|
-
self.
|
|
34
|
-
|
|
34
|
+
self._current_by_credential = {}
|
|
35
|
+
self._waiting_by_credential = {}
|
|
36
|
+
self._set_metric_value(self._in_use, "local", 0)
|
|
37
|
+
self._set_metric_value(self._waiting, "local", 0)
|
|
38
|
+
if _redis_throttle_enabled:
|
|
35
39
|
self._limits_manager = get_limits_manager()
|
|
36
40
|
|
|
37
41
|
def get_semaphore_keys(self, key_type, model, agent_id, credential_id):
|
|
@@ -39,29 +43,66 @@ class Limiting:
|
|
|
39
43
|
total_key = "{}_{}".format(key_type, model)
|
|
40
44
|
return key, total_key
|
|
41
45
|
|
|
42
|
-
|
|
43
|
-
|
|
46
|
+
@staticmethod
|
|
47
|
+
def _normalize_credential_id(credential_id):
|
|
48
|
+
if credential_id is None:
|
|
49
|
+
return "local"
|
|
50
|
+
value = str(credential_id).strip()
|
|
51
|
+
return value or "local"
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def _metric_with_credential(metric, credential_id):
|
|
55
|
+
if metric is None:
|
|
56
|
+
return None
|
|
57
|
+
labelnames = getattr(metric, "_labelnames", ())
|
|
58
|
+
if "credential_id" in labelnames:
|
|
59
|
+
return metric.labels(credential_id=credential_id)
|
|
60
|
+
return metric
|
|
61
|
+
|
|
62
|
+
def _set_metric_value(self, metric, credential_id, value):
|
|
63
|
+
metric_ref = self._metric_with_credential(metric, credential_id)
|
|
64
|
+
if metric_ref is not None:
|
|
65
|
+
metric_ref.set(value)
|
|
66
|
+
|
|
67
|
+
def _observe_metric_value(self, metric, credential_id, value):
|
|
68
|
+
metric_ref = self._metric_with_credential(metric, credential_id)
|
|
69
|
+
if metric_ref is not None:
|
|
70
|
+
metric_ref.observe(value)
|
|
71
|
+
|
|
72
|
+
def _inc_waiting(self, credential_id):
|
|
44
73
|
with self._lock:
|
|
45
|
-
self.
|
|
74
|
+
waiting = self._waiting_by_credential.get(credential_id, 0) + 1
|
|
75
|
+
self._waiting_by_credential[credential_id] = waiting
|
|
76
|
+
self._set_metric_value(self._waiting, credential_id, waiting)
|
|
46
77
|
|
|
47
|
-
def
|
|
78
|
+
def _dec_waiting_inc_current(self, credential_id):
|
|
79
|
+
with self._lock:
|
|
80
|
+
waiting = max(0, self._waiting_by_credential.get(credential_id, 0) - 1)
|
|
81
|
+
self._waiting_by_credential[credential_id] = waiting
|
|
82
|
+
current = self._current_by_credential.get(credential_id, 0) + 1
|
|
83
|
+
self._current_by_credential[credential_id] = current
|
|
84
|
+
self._set_metric_value(self._waiting, credential_id, waiting)
|
|
85
|
+
self._set_metric_value(self._in_use, credential_id, current)
|
|
86
|
+
|
|
87
|
+
def _wait_hist_in_use_update(self, start, credential_id):
|
|
48
88
|
elapsed = time.time() - start
|
|
49
|
-
self._wait_hist
|
|
50
|
-
self._in_use.set(self._current)
|
|
89
|
+
self._observe_metric_value(self._wait_hist, credential_id, elapsed)
|
|
51
90
|
|
|
52
|
-
def _dec_current_in_use_update(self):
|
|
91
|
+
def _dec_current_in_use_update(self, credential_id):
|
|
53
92
|
with self._lock:
|
|
54
|
-
self.
|
|
55
|
-
|
|
93
|
+
current = max(0, self._current_by_credential.get(credential_id, 0) - 1)
|
|
94
|
+
self._current_by_credential[credential_id] = current
|
|
95
|
+
self._set_metric_value(self._in_use, credential_id, current)
|
|
56
96
|
|
|
57
97
|
@contextmanager
|
|
58
98
|
def acquire(self, model, credential_id=None):
|
|
59
|
-
self.
|
|
99
|
+
metric_credential_id = self._normalize_credential_id(credential_id)
|
|
100
|
+
self._inc_waiting(metric_credential_id)
|
|
60
101
|
start = time.time()
|
|
61
102
|
|
|
62
103
|
try:
|
|
63
104
|
self._internal_semaphore.acquire()
|
|
64
|
-
if
|
|
105
|
+
if _redis_throttle_enabled:
|
|
65
106
|
logger.info("redis throttle enabled")
|
|
66
107
|
with ExitStack() as stack:
|
|
67
108
|
try:
|
|
@@ -72,27 +113,28 @@ class Limiting:
|
|
|
72
113
|
self._limits_manager.with_rate_limit(ags_limits.total_limits, total_key)
|
|
73
114
|
self._limits_manager.with_rate_limit(ags_limits.limits, key)
|
|
74
115
|
finally:
|
|
75
|
-
self._dec_waiting_inc_current()
|
|
76
|
-
self._wait_hist_in_use_update(start)
|
|
116
|
+
self._dec_waiting_inc_current(metric_credential_id)
|
|
117
|
+
self._wait_hist_in_use_update(start, metric_credential_id)
|
|
77
118
|
yield
|
|
78
119
|
else:
|
|
79
|
-
self._dec_waiting_inc_current()
|
|
80
|
-
self._wait_hist_in_use_update(start)
|
|
120
|
+
self._dec_waiting_inc_current(metric_credential_id)
|
|
121
|
+
self._wait_hist_in_use_update(start, metric_credential_id)
|
|
81
122
|
yield
|
|
82
123
|
finally:
|
|
83
124
|
try:
|
|
84
125
|
self._internal_semaphore.release()
|
|
85
126
|
finally:
|
|
86
|
-
self._dec_current_in_use_update()
|
|
127
|
+
self._dec_current_in_use_update(metric_credential_id)
|
|
87
128
|
|
|
88
129
|
@asynccontextmanager
|
|
89
130
|
async def acquire_async(self, model, credential_id=None):
|
|
90
|
-
self.
|
|
131
|
+
metric_credential_id = self._normalize_credential_id(credential_id)
|
|
132
|
+
self._inc_waiting(metric_credential_id)
|
|
91
133
|
start = time.time()
|
|
92
134
|
|
|
93
135
|
try:
|
|
94
136
|
await self._internal_semaphore.acquire_async()
|
|
95
|
-
if
|
|
137
|
+
if _redis_throttle_enabled:
|
|
96
138
|
logger.info("redis throttle enabled")
|
|
97
139
|
async with AsyncExitStack() as stack:
|
|
98
140
|
try:
|
|
@@ -103,15 +145,15 @@ class Limiting:
|
|
|
103
145
|
await self._limits_manager.with_rate_limit_async(ags_limits.total_limits, total_key)
|
|
104
146
|
await self._limits_manager.with_rate_limit_async(ags_limits.limits, key)
|
|
105
147
|
finally:
|
|
106
|
-
self._dec_waiting_inc_current()
|
|
107
|
-
self._wait_hist_in_use_update(start)
|
|
148
|
+
self._dec_waiting_inc_current(metric_credential_id)
|
|
149
|
+
self._wait_hist_in_use_update(start, metric_credential_id)
|
|
108
150
|
yield
|
|
109
151
|
else:
|
|
110
|
-
self._dec_waiting_inc_current()
|
|
111
|
-
self._wait_hist_in_use_update(start)
|
|
152
|
+
self._dec_waiting_inc_current(metric_credential_id)
|
|
153
|
+
self._wait_hist_in_use_update(start, metric_credential_id)
|
|
112
154
|
yield
|
|
113
155
|
finally:
|
|
114
156
|
try:
|
|
115
157
|
await self._internal_semaphore.release_async()
|
|
116
158
|
finally:
|
|
117
|
-
self._dec_current_in_use_update()
|
|
159
|
+
self._dec_current_in_use_update(metric_credential_id)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from langchain_gigachat.chat_models import GigaChat
|
|
2
2
|
from langchain_gigachat.embeddings import GigaChatEmbeddings
|
|
3
|
+
from agent_lab_sdk.core.settings import settings
|
|
3
4
|
from agent_lab_sdk.llm.gigachat_token_manager import GigaChatTokenManager
|
|
4
5
|
from agent_lab_sdk.llm.token_manager_utils import build_token_manager_kwargs
|
|
5
6
|
from agent_lab_sdk.llm.throttled import ThrottledGigaChat, ThrottledGigaChatEmbeddings
|
|
@@ -34,6 +35,9 @@ def get_model(
|
|
|
34
35
|
kwargs,
|
|
35
36
|
kwargs.pop("token_manager_kwargs", None),
|
|
36
37
|
)
|
|
38
|
+
if settings.GIGACHAT_USE_MTLS:
|
|
39
|
+
access_token = "mtls"
|
|
40
|
+
manage_access_token = False
|
|
37
41
|
if not access_token:
|
|
38
42
|
user = kwargs.get("user", None)
|
|
39
43
|
password = kwargs.get("password", None)
|
|
@@ -71,6 +71,10 @@ class ThrottledGigaChatEmbeddings(GigaChatEmbeddings):
|
|
|
71
71
|
model = kwargs.get("model") or settings.GIGACHAT_MODEL
|
|
72
72
|
use_token_provider_ags = token_manager_kwargs.get("use_ags_token") or settings.USE_TOKEN_PROVIDER_AGS
|
|
73
73
|
|
|
74
|
+
if settings.GIGACHAT_USE_MTLS:
|
|
75
|
+
manage_access_token = False
|
|
76
|
+
kwargs["access_token"] = "mtls"
|
|
77
|
+
|
|
74
78
|
if manage_access_token and "access_token" not in kwargs:
|
|
75
79
|
new_kwargs = _resolve_token_kwargs(
|
|
76
80
|
model, gigachat_credential_id, use_token_provider_ags, kwargs, token_manager_kwargs
|
|
@@ -163,6 +167,10 @@ class ThrottledGigaChat(GigaChat):
|
|
|
163
167
|
model = kwargs.get("model") or settings.GIGACHAT_MODEL
|
|
164
168
|
use_token_provider_ags = token_manager_kwargs.get("use_ags_token") or settings.USE_TOKEN_PROVIDER_AGS
|
|
165
169
|
|
|
170
|
+
if settings.GIGACHAT_USE_MTLS:
|
|
171
|
+
manage_access_token = False
|
|
172
|
+
kwargs["access_token"] = "mtls"
|
|
173
|
+
|
|
166
174
|
if manage_access_token and "access_token" not in kwargs:
|
|
167
175
|
new_kwargs = _resolve_token_kwargs(
|
|
168
176
|
model, gigachat_credential_id, use_token_provider_ags, kwargs, token_manager_kwargs
|
|
@@ -4,15 +4,18 @@ from agent_lab_sdk.metrics import get_metric
|
|
|
4
4
|
def create_metrics(prefix: str):
|
|
5
5
|
in_use = get_metric(
|
|
6
6
|
metric_type = "gauge", name = f"{prefix}_slots_in_use",
|
|
7
|
-
documentation = f"Number of {prefix} slots currently in use"
|
|
7
|
+
documentation = f"Number of {prefix} slots currently in use",
|
|
8
|
+
labelnames=["credential_id"],
|
|
8
9
|
)
|
|
9
10
|
waiting = get_metric(
|
|
10
11
|
metric_type = "gauge", name = f"{prefix}_waiting_tasks",
|
|
11
|
-
documentation = f"Number of tasks waiting for {prefix}"
|
|
12
|
+
documentation = f"Number of tasks waiting for {prefix}",
|
|
13
|
+
labelnames=["credential_id"],
|
|
12
14
|
)
|
|
13
15
|
wait_time = get_metric(
|
|
14
16
|
metric_type = "histogram", name = f"{prefix}_wait_time_seconds",
|
|
15
17
|
documentation = f"Time tasks wait for {prefix}",
|
|
18
|
+
labelnames=["credential_id"],
|
|
16
19
|
buckets = [3, 5, 10, 15, 30, 60, 120, 240, 480, 960, 1920, float("inf")]
|
|
17
20
|
)
|
|
18
21
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import logging
|
|
3
|
+
import uuid
|
|
3
4
|
from dataclasses import dataclass
|
|
5
|
+
from enum import StrEnum
|
|
4
6
|
|
|
5
7
|
import httpx
|
|
6
8
|
|
|
@@ -41,6 +43,12 @@ class ImagesProxyResult:
|
|
|
41
43
|
file_size: int
|
|
42
44
|
mime_type: str
|
|
43
45
|
|
|
46
|
+
class SearchMode(StrEnum):
|
|
47
|
+
actual_info_web_search = "actual_info_web_search"
|
|
48
|
+
safe_search = "safe_search"
|
|
49
|
+
web_search = "web_search"
|
|
50
|
+
custom = "custom"
|
|
51
|
+
|
|
44
52
|
|
|
45
53
|
class SearchPlugin:
|
|
46
54
|
"""
|
|
@@ -54,7 +62,7 @@ class SearchPlugin:
|
|
|
54
62
|
def _get_setting_or_raise(self, name):
|
|
55
63
|
value = getattr(settings, name, None)
|
|
56
64
|
if not value:
|
|
57
|
-
raise ValueError(f"SearchPlugin setting not found. Please set the {
|
|
65
|
+
raise ValueError(f"SearchPlugin setting not found. Please set the {name} environment variable.")
|
|
58
66
|
return value
|
|
59
67
|
|
|
60
68
|
async def retrieval_async(self, query, search_mode, doc_text_max_len=None, docs_count=None, headers=None, body_params=None) -> RetrieveResponse:
|
|
@@ -109,13 +117,17 @@ class SearchPlugin:
|
|
|
109
117
|
|
|
110
118
|
body = {
|
|
111
119
|
"query": search_query,
|
|
112
|
-
"search_mode": search_mode,
|
|
120
|
+
"search_mode": str(search_mode),
|
|
113
121
|
**body_params
|
|
114
122
|
}
|
|
115
123
|
|
|
116
124
|
headers = {
|
|
117
125
|
"Authorization": f"{self._api_key}",
|
|
118
126
|
"User-Agent": "agent-toolkit",
|
|
127
|
+
"X-User-ID": str(uuid.uuid4()),
|
|
128
|
+
"X-Client-ID": str(uuid.uuid4()),
|
|
129
|
+
"X-Request-ID": str(uuid.uuid4()),
|
|
130
|
+
"X-Session-ID": str(uuid.uuid4()),
|
|
119
131
|
**headers
|
|
120
132
|
}
|
|
121
133
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-lab-sdk
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.58.dev1
|
|
4
4
|
Summary: SDK для работы с Agent Lab
|
|
5
5
|
Author-email: Andrew Ohurtsov <andermirik@yandex.com>
|
|
6
6
|
License: Proprietary and Confidential — All Rights Reserved
|
|
@@ -136,6 +136,7 @@ model = get_model(
|
|
|
136
136
|
| `SDK_AGENT_LAB_GIGACHAT_CREDENTIAL_ID` | Credential Id для получения токена из Agent Service | - |
|
|
137
137
|
| `AGENT_SERVICE_NAME` | Имя сервиса агента (обязательно) | - |
|
|
138
138
|
| `SDK_AGENT_LAB_USE_TOKEN_PROVIDER_AGS` | Использовать `AgsTokenManager` для получения токена и лимитов GigaChat | `false` |
|
|
139
|
+
| `SDK_AGENT_LAB_GIGACHAT_USE_MTLS` | Не использовать токен менеджеры и глобальный лимитер или использовать | `false` |
|
|
139
140
|
|
|
140
141
|
|
|
141
142
|
---
|
|
@@ -189,6 +190,9 @@ chat_with_retry = ThrottledGigaChat(
|
|
|
189
190
|
| `embed_waiting_tasks` | Число задач, ожидающих слота эмбеддингов | Gauge |
|
|
190
191
|
| `embed_wait_time_seconds` | Время ожидания слота эмбеддингов (секунды) | Histogram |
|
|
191
192
|
|
|
193
|
+
Все метрики семафоров экспортируются с label `credential_id`.
|
|
194
|
+
Если credential не задан (локальный режим), используется значение `credential_id="local"`.
|
|
195
|
+
|
|
192
196
|
---
|
|
193
197
|
|
|
194
198
|
## 3. Модуль `agent_lab_sdk.metrics`
|
|
@@ -518,11 +522,30 @@ pip install -e .
|
|
|
518
522
|
Позволяет использовать API search plugin + llm tool + лимитирование запросов
|
|
519
523
|
|
|
520
524
|
```python
|
|
521
|
-
from agent_lab_sdk.tools.search_plugin.search_plugin import SearchPlugin
|
|
525
|
+
from agent_lab_sdk.tools.search_plugin.search_plugin import SearchPlugin, SearchMode
|
|
526
|
+
from agent_lab_sdk.tools.search_plugin.tools import get_search_plugin_tool, get_search_image_tool
|
|
527
|
+
from langgraph.prebuilt import ToolNode
|
|
528
|
+
from langgraph.graph import StateGraph
|
|
529
|
+
from agent_lab_sdk.llm import get_model
|
|
522
530
|
|
|
523
531
|
plugin = SearchPlugin()
|
|
524
|
-
|
|
525
|
-
print(
|
|
532
|
+
retrieval_result = plugin.retrieval("Что такое SDK?", SearchMode.actual_info_web_search)
|
|
533
|
+
print(retrieval_result.documents, retrieval_result.sources, retrieval_result.images)
|
|
534
|
+
|
|
535
|
+
images_result = plugin.rambler_images_proxy("Логотип питона")
|
|
536
|
+
for img in images_result:
|
|
537
|
+
print(img.url, img.image_link)
|
|
538
|
+
|
|
539
|
+
search_tool = get_search_plugin_tool()
|
|
540
|
+
image_tool = get_search_image_tool()
|
|
541
|
+
tools = [search_tool, image_tool]
|
|
542
|
+
|
|
543
|
+
llm = get_model().bind_tools(tools)
|
|
544
|
+
|
|
545
|
+
graph = StateGraph(State)
|
|
546
|
+
graph.add_node("tools", ToolNode(tools), metadata={'description': 'Вызов инструментов'})
|
|
547
|
+
|
|
548
|
+
|
|
526
549
|
```
|
|
527
550
|
|
|
528
551
|
Реализованы методы POST /retrieval, GET /rambler_proxy_images
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "agent-lab-sdk"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.58.dev1"
|
|
8
8
|
description = "SDK для работы с Agent Lab"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "Proprietary and Confidential — All Rights Reserved" }
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/core/unified_semaphore.py
RENAMED
|
File without changes
|
|
File without changes
|
{agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/langgraph/checkpoint/serde.py
RENAMED
|
File without changes
|
|
File without changes
|
{agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/llm/agw_token_manager.py
RENAMED
|
File without changes
|
{agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/llm/clients/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/llm/clients/redis_client.py
RENAMED
|
File without changes
|
{agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/llm/gigachat_token_manager.py
RENAMED
|
File without changes
|
{agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/llm/managers/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/llm/managers/limits_manager.py
RENAMED
|
File without changes
|
{agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/llm/managers/redis/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/llm/token_manager_utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
{agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|