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.
- agent_api_server/__init__.py +0 -0
- agent_api_server/api/__init__.py +0 -0
- agent_api_server/api/v1/__init__.py +0 -0
- agent_api_server/api/v1/api.py +25 -0
- agent_api_server/api/v1/config.py +57 -0
- agent_api_server/api/v1/graph.py +59 -0
- agent_api_server/api/v1/schema.py +57 -0
- agent_api_server/api/v1/thread.py +563 -0
- agent_api_server/cache/__init__.py +0 -0
- agent_api_server/cache/redis_cache.py +385 -0
- agent_api_server/callback_handler.py +18 -0
- agent_api_server/client/css/styles.css +1202 -0
- agent_api_server/client/favicon.ico +0 -0
- agent_api_server/client/index.html +102 -0
- agent_api_server/client/js/app.js +1499 -0
- agent_api_server/client/js/index.umd.js +824 -0
- agent_api_server/config_center/config_center.py +239 -0
- agent_api_server/configs/__init__.py +3 -0
- agent_api_server/configs/config.py +163 -0
- agent_api_server/dynamic_llm/__init__.py +0 -0
- agent_api_server/dynamic_llm/dynamic_llm.py +331 -0
- agent_api_server/listener.py +530 -0
- agent_api_server/log/__init__.py +0 -0
- agent_api_server/log/formatters.py +122 -0
- agent_api_server/log/logging.json +50 -0
- agent_api_server/mcp_convert/__init__.py +0 -0
- agent_api_server/mcp_convert/mcp_convert.py +375 -0
- agent_api_server/memeory/__init__.py +0 -0
- agent_api_server/memeory/postgres.py +233 -0
- agent_api_server/register/__init__.py +0 -0
- agent_api_server/register/register.py +65 -0
- agent_api_server/service.py +354 -0
- agent_api_server/service_hub/service_hub.py +233 -0
- agent_api_server/service_hub/service_hub_test.py +700 -0
- agent_api_server/shared/__init__.py +0 -0
- agent_api_server/shared/ase.py +54 -0
- agent_api_server/shared/base_model.py +103 -0
- agent_api_server/shared/common.py +110 -0
- agent_api_server/shared/decode_token.py +107 -0
- agent_api_server/shared/detect_message.py +410 -0
- agent_api_server/shared/get_model_info.py +491 -0
- agent_api_server/shared/message.py +419 -0
- agent_api_server/shared/util_func.py +372 -0
- agent_api_server/sso_service/__init__.py +1 -0
- agent_api_server/sso_service/sdk/__init__.py +1 -0
- agent_api_server/sso_service/sdk/client.py +224 -0
- agent_api_server/sso_service/sdk/credential.py +11 -0
- agent_api_server/sso_service/sdk/encoding.py +22 -0
- agent_api_server/sso_service/sso_service.py +177 -0
- agent_api_server-2.1.7.dist-info/METADATA +130 -0
- agent_api_server-2.1.7.dist-info/RECORD +52 -0
- 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})"
|