agent-api-server 2.1.7__py3-none-any.whl

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 (52) hide show
  1. agent_api_server/__init__.py +0 -0
  2. agent_api_server/api/__init__.py +0 -0
  3. agent_api_server/api/v1/__init__.py +0 -0
  4. agent_api_server/api/v1/api.py +25 -0
  5. agent_api_server/api/v1/config.py +57 -0
  6. agent_api_server/api/v1/graph.py +59 -0
  7. agent_api_server/api/v1/schema.py +57 -0
  8. agent_api_server/api/v1/thread.py +563 -0
  9. agent_api_server/cache/__init__.py +0 -0
  10. agent_api_server/cache/redis_cache.py +385 -0
  11. agent_api_server/callback_handler.py +18 -0
  12. agent_api_server/client/css/styles.css +1202 -0
  13. agent_api_server/client/favicon.ico +0 -0
  14. agent_api_server/client/index.html +102 -0
  15. agent_api_server/client/js/app.js +1499 -0
  16. agent_api_server/client/js/index.umd.js +824 -0
  17. agent_api_server/config_center/config_center.py +239 -0
  18. agent_api_server/configs/__init__.py +3 -0
  19. agent_api_server/configs/config.py +163 -0
  20. agent_api_server/dynamic_llm/__init__.py +0 -0
  21. agent_api_server/dynamic_llm/dynamic_llm.py +331 -0
  22. agent_api_server/listener.py +530 -0
  23. agent_api_server/log/__init__.py +0 -0
  24. agent_api_server/log/formatters.py +122 -0
  25. agent_api_server/log/logging.json +50 -0
  26. agent_api_server/mcp_convert/__init__.py +0 -0
  27. agent_api_server/mcp_convert/mcp_convert.py +375 -0
  28. agent_api_server/memeory/__init__.py +0 -0
  29. agent_api_server/memeory/postgres.py +233 -0
  30. agent_api_server/register/__init__.py +0 -0
  31. agent_api_server/register/register.py +65 -0
  32. agent_api_server/service.py +354 -0
  33. agent_api_server/service_hub/service_hub.py +233 -0
  34. agent_api_server/service_hub/service_hub_test.py +700 -0
  35. agent_api_server/shared/__init__.py +0 -0
  36. agent_api_server/shared/ase.py +54 -0
  37. agent_api_server/shared/base_model.py +103 -0
  38. agent_api_server/shared/common.py +110 -0
  39. agent_api_server/shared/decode_token.py +107 -0
  40. agent_api_server/shared/detect_message.py +410 -0
  41. agent_api_server/shared/get_model_info.py +491 -0
  42. agent_api_server/shared/message.py +419 -0
  43. agent_api_server/shared/util_func.py +372 -0
  44. agent_api_server/sso_service/__init__.py +1 -0
  45. agent_api_server/sso_service/sdk/__init__.py +1 -0
  46. agent_api_server/sso_service/sdk/client.py +224 -0
  47. agent_api_server/sso_service/sdk/credential.py +11 -0
  48. agent_api_server/sso_service/sdk/encoding.py +22 -0
  49. agent_api_server/sso_service/sso_service.py +177 -0
  50. agent_api_server-2.1.7.dist-info/METADATA +130 -0
  51. agent_api_server-2.1.7.dist-info/RECORD +52 -0
  52. agent_api_server-2.1.7.dist-info/WHEEL +4 -0
@@ -0,0 +1,331 @@
1
+ from __future__ import annotations
2
+ import ast
3
+ import json
4
+ import os
5
+ import threading
6
+ import hashlib
7
+ import logging
8
+ from typing import Dict, Any, Optional, List, AsyncGenerator, Iterator, Union, Type
9
+ from langchain_core.tools import BaseTool
10
+ from langchain_core.language_models import LanguageModelInput
11
+ from langchain_core.runnables.config import RunnableConfig
12
+ from langchain_core.runnables import Runnable, RunnableLambda
13
+ from llm_sdk.llm import ModelInstance, ModelInstanceFactory
14
+ from llm_sdk.model_providers.base import ConfigType
15
+ from langchain_core.messages import BaseMessage, BaseMessageChunk
16
+ from model_manage_client import ModelManageClient
17
+ from pydantic import BaseModel
18
+ from functools import partial
19
+ from contextlib import contextmanager
20
+
21
+ from agent_api_server.configs import global_config
22
+ from agent_api_server.shared.util_func import set_model_config, get_env
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ class DynamicLLM(Runnable):
28
+ _instance_lock = threading.Lock()
29
+ _llm_cache: Dict[str, ModelInstance] = {}
30
+ _hash_fn = partial(hashlib.md5, usedforsecurity=False)
31
+
32
+ def __init__(self, tool_name: str, config_type: ConfigType = ConfigType.CHAT, use_sys_llm: Optional[bool] = False):
33
+ super().__init__()
34
+ self._model: Optional[ModelInstance] = None
35
+ self._current_config_hash: Optional[str] = None
36
+ self._tools: Optional[List[BaseTool]] = None
37
+ self.tool_name = tool_name.lower()
38
+ self.config_type = config_type
39
+ self.use_sys_llm = use_sys_llm
40
+ self._lock = threading.RLock()
41
+
42
+ def rerank(self, input_dict: Union[List[BaseMessage], List[str], str],
43
+ documents: List[str], top_n: Optional[int] = None, score_threshold: Optional[float] = None, config: Optional[RunnableConfig] = None) -> Any:
44
+ if self.config_type != ConfigType.RERANK:
45
+ raise ValueError("Only rerank model type support rerank function")
46
+
47
+ llm_config = self._get_llm_config_from_context(config or {})
48
+
49
+ with self._get_model_instance(llm_config, config) as model:
50
+ return model.rerank(input_dict, documents, top_n, score_threshold)
51
+
52
+ def invoke(self, input_dict: Union[List[BaseMessage], List[str], str],
53
+ config: Optional[RunnableConfig] = None, **kwargs) -> Any:
54
+ llm_config = self._get_llm_config_from_context(config or {})
55
+
56
+ with self._get_model_instance(llm_config, config) as model:
57
+ if self.config_type == ConfigType.CHAT:
58
+ if not isinstance(input_dict, list) or not all(isinstance(x, BaseMessage) for x in input_dict):
59
+ raise ValueError("Chat model requires List[BaseMessage] input_dict")
60
+ return model.invoke(input_dict, **kwargs)
61
+ elif self.config_type == ConfigType.EMBEDDING:
62
+ if isinstance(input_dict, str):
63
+ return model.embed_query(input_dict)
64
+ return model.embed_documents(input_dict if isinstance(input_dict, list) else [input_dict])
65
+ elif self.config_type == ConfigType.RERANK:
66
+ raise ValueError("Rerank model should use rerank() method")
67
+ else:
68
+ raise ValueError(f"Unsupported config type: {self.config_type}")
69
+
70
+ async def ainvoke(self, input_dict: Union[List[BaseMessage], List[str], str],
71
+ config: Optional[RunnableConfig] = None, **kwargs) -> Any:
72
+ llm_config = self._get_llm_config_from_context(config or {})
73
+
74
+ with self._get_model_instance(llm_config, config) as model:
75
+ if self.config_type == ConfigType.CHAT:
76
+ if not isinstance(input_dict, list) or not all(isinstance(x, BaseMessage) for x in input_dict):
77
+ raise ValueError("Chat model requires List[BaseMessage] input_dict")
78
+ return await model.ainvoke(input_dict, **kwargs)
79
+ elif self.config_type == ConfigType.EMBEDDING:
80
+ if isinstance(input_dict, str):
81
+ return await model.aembed_query(input_dict)
82
+ return await model.aembed_documents(input_dict if isinstance(input_dict, list) else [input_dict])
83
+ elif self.config_type == ConfigType.RERANK:
84
+ raise ValueError("Rerank model should use rerank() method")
85
+ else:
86
+ raise ValueError(f"Unsupported config type: {self.config_type}")
87
+
88
+ def stream(self, input_dict: List[BaseMessage], config: Optional[RunnableConfig] = None,
89
+ **kwargs) -> Iterator[BaseMessageChunk]:
90
+ if self.config_type != ConfigType.CHAT:
91
+ raise ValueError("Only chat models support streaming")
92
+
93
+ llm_config = self._get_llm_config_from_context(config or {})
94
+
95
+ with self._get_model_instance(llm_config, config) as model:
96
+ return model.stream(input_dict, **kwargs)
97
+
98
+ async def astream(self, input_dict: List[BaseMessage], config: Optional[RunnableConfig] = None,
99
+ **kwargs) -> AsyncGenerator[BaseMessageChunk, None]:
100
+ if self.config_type != ConfigType.CHAT:
101
+ raise ValueError("Only chat models support streaming")
102
+
103
+ llm_config = self._get_llm_config_from_context(config or {})
104
+
105
+ with self._get_model_instance(llm_config, config) as model:
106
+ async for chunk in model.astream(input_dict, **kwargs):
107
+ yield chunk
108
+
109
+ def embed_documents(self, texts: List[str], config: Optional[RunnableConfig] = None) -> List[List[float]]:
110
+ if self.config_type != ConfigType.EMBEDDING:
111
+ raise ValueError("Only embedding models support this method")
112
+
113
+ llm_config = self._get_llm_config_from_context(config or {})
114
+
115
+ with self._get_model_instance(llm_config, config) as model:
116
+ return model.embed_documents(texts)
117
+
118
+ async def aembed_documents(self, texts: List[str], config: Optional[RunnableConfig] = None) -> List[List[float]]:
119
+ if self.config_type != ConfigType.EMBEDDING:
120
+ raise ValueError("Only embedding models support this method")
121
+
122
+ llm_config = self._get_llm_config_from_context(config or {})
123
+
124
+ with self._get_model_instance(llm_config, config) as model:
125
+ return await model.aembed_documents(texts)
126
+
127
+ def embed_query(self, text: str, config: Optional[RunnableConfig] = None) -> List[float]:
128
+ if self.config_type != ConfigType.EMBEDDING:
129
+ raise ValueError("Only embedding models support this method")
130
+
131
+ llm_config = self._get_llm_config_from_context(config or {})
132
+
133
+ with self._get_model_instance(llm_config, config) as model:
134
+ return model.embed_query(text)
135
+
136
+ async def aembed_query(self, text: str, config: Optional[RunnableConfig] = None) -> List[float]:
137
+ if self.config_type != ConfigType.EMBEDDING:
138
+ raise ValueError("Only embedding models support this method")
139
+
140
+ llm_config = self._get_llm_config_from_context(config or {})
141
+
142
+ with self._get_model_instance(llm_config, config) as model:
143
+ return await model.aembed_query(text)
144
+
145
+ def bind_tools(self, tools: List[BaseTool], **kwargs) -> 'DynamicLLM':
146
+ if self.config_type != ConfigType.CHAT:
147
+ raise ValueError("Cannot bind tools to non-chat models")
148
+
149
+ with self._lock:
150
+ self._tools = tools
151
+ if self._model is not None:
152
+ self._model.bind_tools(tools, **kwargs)
153
+ return self
154
+
155
+ def with_structured_output(self, schema: Union[Dict, Type[BaseModel]], **kwargs) -> Runnable:
156
+ if self.config_type != ConfigType.CHAT:
157
+ raise ValueError("Only chat models support structured output")
158
+
159
+ def wrapper(input_data: LanguageModelInput, config: Optional[RunnableConfig] = None):
160
+ llm_config = self._get_llm_config_from_context(config or {})
161
+ with self._get_model_instance(llm_config, config) as model:
162
+ return model.with_structured_output(
163
+ schema=schema,
164
+ **kwargs
165
+ ).invoke(input_data)
166
+
167
+ return RunnableLambda(wrapper)
168
+
169
+ @contextmanager
170
+ def _get_model_instance(self, llm_config: Dict[str, Any], config: RunnableConfig):
171
+ if self.use_sys_llm:
172
+ logger.info(f"use_sys_llm is {self.use_sys_llm}, so call model with system llm configuration, CLIENT_TOKEN is {global_config.CLIENT_TOKEN}")
173
+
174
+ self._model = ModelInstanceFactory.get_model_instance(
175
+ model_managment_url=os.environ['MODEL_MANAGER_SERVICE_URL'],
176
+ config_type=self.config_type,
177
+ token=os.environ['CLIENT_TOKEN'],
178
+ app_id=llm_config.get('agent_id'),
179
+ app_name=llm_config.get('agent_id'),
180
+ )
181
+ yield self._model
182
+ else:
183
+ if self._is_credentials_empty(llm_config):
184
+ agent_id = llm_config.get('agent_id')
185
+ ts_id = llm_config.get('ts_tenant')
186
+
187
+ logger.warning(
188
+ f"Local cache does not contain LLM credentials for tenant '{ts_id}'. "
189
+ f"Retrieve the credentials from Model Management and configure them in the system.")
190
+
191
+ m_client = ModelManageClient(global_config.MODEL_MANAGER_SERVICE_URL, global_config.CLIENT_TOKEN)
192
+ model_info = m_client.get_agent_models(agent_id, ts_id)
193
+
194
+ logger.info(f"LLM credentials for tenant {ts_id} is {model_info}")
195
+
196
+ for tool_name, tool_cfg in model_info.items():
197
+ for model_type, model_cfg in tool_cfg.items():
198
+ model_provider = model_cfg.get("provider", "")
199
+ model_name = model_cfg.get("model", "")
200
+ model_type = model_type
201
+ credentials_json = json.dumps(model_cfg.get("credentials", {}), ensure_ascii=False)
202
+
203
+ logger.info(f"""
204
+ - Tenant ID: {agent_id}
205
+ - Tool: {tool_name}
206
+ - Provider: {model_provider}
207
+ - Model: {model_name}
208
+ - Model Type: {model_type}
209
+ """)
210
+
211
+ set_model_config(
212
+ tool_name=tool_name,
213
+ model_type=model_type,
214
+ model_name=model_name,
215
+ model_provider=model_provider,
216
+ credentials=credentials_json,
217
+ agent_id=agent_id,
218
+ ts_id=ts_id
219
+ )
220
+
221
+ configurable = config.get("configurable", {})
222
+ configurable.update(dict(get_env(ts_tenant=ts_id)))
223
+ llm_config = self._get_llm_config_from_context(config or {})
224
+ with self._lock:
225
+ self._ensure_model_initialized(llm_config)
226
+ yield self._model
227
+ else:
228
+ with self._lock:
229
+ self._ensure_model_initialized(llm_config)
230
+ yield self._model
231
+
232
+ def _ensure_model_initialized(self, config: Dict[str, Any]) -> None:
233
+ config_hash = self._generate_config_hash(config)
234
+
235
+ if self._model is None or self._current_config_hash != config_hash:
236
+ provider = config.get('provider', 'unknown')
237
+ cache_key = f"{provider}:{self.config_type.value}:{config_hash}"
238
+
239
+ with self._instance_lock:
240
+ if cache_key in self._llm_cache:
241
+ self._model = self._llm_cache[cache_key]
242
+ else:
243
+ credentials = config.get('credentials')
244
+ credentials_dict = {}
245
+
246
+ if credentials:
247
+ if isinstance(credentials, dict):
248
+ credentials_dict = credentials
249
+ elif isinstance(credentials, str):
250
+ try:
251
+ credentials_dict = json.loads(credentials)
252
+ except json.JSONDecodeError:
253
+ try:
254
+ credentials_dict = ast.literal_eval(credentials)
255
+ except (ValueError, SyntaxError) as e:
256
+ logger.error(f"Failed to parse credentials string: {e}")
257
+ else:
258
+ logger.warning(f"Unexpected credentials type: {type(credentials)}")
259
+
260
+ model_params = {
261
+ 'provider': config.get('provider'),
262
+ 'model': config.get('model'),
263
+ 'credentials': credentials_dict,
264
+ }
265
+ self._model = ModelInstance(
266
+ config_type=self.config_type,
267
+ model_params=model_params,
268
+ app_id=config.get('agent_id')
269
+ )
270
+ self._llm_cache[cache_key] = self._model
271
+
272
+ if self.config_type == ConfigType.CHAT and self._tools:
273
+ self._model.bind_tools(self._tools)
274
+
275
+ self._current_config_hash = config_hash
276
+
277
+ def _get_llm_config_from_context(self, context: Dict[str, Any]) -> Dict[str, Any]:
278
+ configurable = context.get("configurable", {})
279
+ ts_tenant = configurable.get("ts_tenant") or configurable.get("TSTenant")
280
+ graph_name = configurable.get("graph_name")
281
+
282
+ value = configurable.get("use_sys_llm")
283
+ if value:
284
+ self.use_sys_llm = True if value == "true" else False
285
+
286
+ config = {
287
+ "provider": configurable.get(self._get_config_key("PROVIDER", ts_tenant)),
288
+ "model": configurable.get(self._get_config_key("MODEL", ts_tenant)),
289
+ "credentials": configurable.get(self._get_config_key("CREDENTIALS", ts_tenant)),
290
+ "agent_id": graph_name,
291
+ "ts_tenant": ts_tenant
292
+ }
293
+
294
+ return config
295
+
296
+ def _get_config_key(self, field: str, ts_tenant: Optional[str] = None) -> str:
297
+ parts = [self.tool_name.upper(), self.config_type.value, field] if self.tool_name != "default" else [
298
+ self.config_type.value, field]
299
+ if ts_tenant:
300
+ parts.append(ts_tenant)
301
+ return "_".join(parts)
302
+
303
+ @classmethod
304
+ def _generate_config_hash(cls, config: Dict[str, Any]) -> str:
305
+ relevant_keys = ['provider', 'model', 'credentials']
306
+ hash_input = "|".join(str(config.get(k, "")) for k in relevant_keys)
307
+ return cls._hash_fn(hash_input.encode()).hexdigest()
308
+
309
+ @staticmethod
310
+ def _is_credentials_empty(config: Optional[Dict[str, Any]]) -> bool:
311
+ if config is None:
312
+ return True
313
+
314
+ credentials = config.get('credentials')
315
+ if not credentials:
316
+ return True
317
+
318
+ return False
319
+
320
+ def clear_cache(self):
321
+ with self._instance_lock:
322
+ self._llm_cache.clear()
323
+ self._model = None
324
+ self._current_config_hash = None
325
+
326
+ @classmethod
327
+ def get_cache_size(cls) -> int:
328
+ return len(cls._llm_cache)
329
+
330
+ def __repr__(self) -> str:
331
+ return f"DynamicLLM(tool_name={self.tool_name}, config_type={self.config_type})"