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.
Files changed (50) hide show
  1. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/PKG-INFO +27 -4
  2. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/README.md +26 -3
  3. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/core/settings.py +4 -2
  4. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/langgraph/checkpoint/agw_saver.py +28 -2
  5. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/llm/limiting.py +70 -28
  6. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/llm/llm.py +4 -0
  7. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/llm/throttled.py +8 -0
  8. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/metrics/create.py +5 -2
  9. agent_lab_sdk-0.1.58.dev1/agent_lab_sdk/tools/search_plugin/__init__.py +4 -0
  10. {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
  11. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk.egg-info/PKG-INFO +27 -4
  12. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/pyproject.toml +1 -1
  13. agent_lab_sdk-0.1.57.dev3/agent_lab_sdk/tools/search_plugin/__init__.py +0 -4
  14. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/LICENSE +0 -0
  15. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/__init__.py +0 -0
  16. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/core/__init__.py +0 -0
  17. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/core/unified_semaphore.py +0 -0
  18. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/langgraph/checkpoint/__init__.py +0 -0
  19. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/langgraph/checkpoint/serde.py +0 -0
  20. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/llm/__init__.py +0 -0
  21. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/llm/agw_token_manager.py +0 -0
  22. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/llm/clients/__init__.py +0 -0
  23. {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
  24. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/llm/clients/redis_client.py +0 -0
  25. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/llm/gigachat_token_manager.py +0 -0
  26. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/llm/managers/__init__.py +0 -0
  27. {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
  28. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/llm/managers/limits_manager.py +0 -0
  29. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/llm/managers/redis/__init__.py +0 -0
  30. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/llm/managers/redis/ratelimiter.py +0 -0
  31. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/llm/managers/redis/semaphore.py +0 -0
  32. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/llm/retry_utils.py +0 -0
  33. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/llm/token_manager_utils.py +0 -0
  34. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/metrics/__init__.py +0 -0
  35. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/metrics/metrics.py +0 -0
  36. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/schema/__init__.py +0 -0
  37. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/schema/input_types.py +0 -0
  38. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/schema/log_message.py +0 -0
  39. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/storage/__init__.py +0 -0
  40. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/storage/storage.py +0 -0
  41. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/storage/storage_v2.py +0 -0
  42. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk/tools/__init__.py +0 -0
  43. {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
  44. {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
  45. {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
  46. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk.egg-info/SOURCES.txt +0 -0
  47. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk.egg-info/dependency_links.txt +0 -0
  48. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk.egg-info/requires.txt +0 -0
  49. {agent_lab_sdk-0.1.57.dev3 → agent_lab_sdk-0.1.58.dev1}/agent_lab_sdk.egg-info/top_level.txt +0 -0
  50. {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.57.dev3
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
- result = plugin.retrieval("Что такое SDK?", "actual_info_web_search")
525
- print(result.documents, result.sources, result.images)
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
- result = plugin.retrieval("Что такое SDK?", "actual_info_web_search")
490
- print(result.documents, result.sources, result.images)
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: int = 30 # seconds
38
- SEARCH_PLUGIN_RAMBLER_PROXY_TIMEOUT: int = 30 # seconds
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("GET", path, params=params, label_path=path_template)
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("POST", "/checkpoint", json=payload, label_path="/checkpoint")
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
- if settings.REDIS_THROTTLE_ENABLED:
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._current = 0
32
- self._in_use.set(0)
33
- self._waiting.set(0)
34
- if settings.REDIS_THROTTLE_ENABLED:
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
- def _dec_waiting_inc_current(self):
43
- self._waiting.dec()
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._current += 1
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 _wait_hist_in_use_update(self, start):
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.observe(elapsed)
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._current -= 1
55
- self._in_use.set(self._current)
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._waiting.inc()
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 settings.REDIS_THROTTLE_ENABLED:
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._waiting.inc()
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 settings.REDIS_THROTTLE_ENABLED:
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
 
@@ -0,0 +1,4 @@
1
+ from .search_plugin import SearchPlugin, SearchMode, RetrieveResponse, ImagesProxyResult
2
+ from . import tools
3
+
4
+ __all__ = ["SearchPlugin", "SearchMode", "RetrieveResponse", "ImagesProxyResult", "tools"]
@@ -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 {ENV_PREFIX}{name} environment variable.")
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.57.dev3
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
- result = plugin.retrieval("Что такое SDK?", "actual_info_web_search")
525
- print(result.documents, result.sources, result.images)
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.57.dev3"
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" }
@@ -1,4 +0,0 @@
1
- from .search_plugin import SearchPlugin
2
- from . import tools
3
-
4
- __all__ = ["SearchPlugin", "tools"]