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
|
File without changes
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import hashlib
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from cryptography.hazmat.backends import default_backend
|
|
6
|
+
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AESCipher:
|
|
10
|
+
def __init__(self, key_string):
|
|
11
|
+
self.key = hashlib.sha256(key_string.encode()).digest()
|
|
12
|
+
self.backend = default_backend()
|
|
13
|
+
|
|
14
|
+
def encrypt(self, plaintext):
|
|
15
|
+
iv = os.urandom(16)
|
|
16
|
+
plaintext_bytes = plaintext.encode("utf-8")
|
|
17
|
+
|
|
18
|
+
encryptor = Cipher(algorithms.AES(self.key), modes.CBC(iv), backend=self.backend).encryptor()
|
|
19
|
+
padder = self._create_padder()
|
|
20
|
+
|
|
21
|
+
padded_plaintext = padder.update(plaintext_bytes) + padder.finalize()
|
|
22
|
+
ciphertext = encryptor.update(padded_plaintext) + encryptor.finalize()
|
|
23
|
+
encrypted_data = base64.b64encode(iv + ciphertext).decode("utf-8")
|
|
24
|
+
|
|
25
|
+
return encrypted_data
|
|
26
|
+
|
|
27
|
+
def decrypt(self, encrypted_data):
|
|
28
|
+
try:
|
|
29
|
+
encrypted_bytes = base64.b64decode(encrypted_data)
|
|
30
|
+
|
|
31
|
+
iv = encrypted_bytes[:16]
|
|
32
|
+
ciphertext = encrypted_bytes[16:]
|
|
33
|
+
|
|
34
|
+
decryptor = Cipher(algorithms.AES(self.key), modes.CBC(iv), backend=self.backend).decryptor()
|
|
35
|
+
padded_plaintext = decryptor.update(ciphertext) + decryptor.finalize()
|
|
36
|
+
|
|
37
|
+
unpadder = self._create_unpadder()
|
|
38
|
+
plaintext_bytes = unpadder.update(padded_plaintext) + unpadder.finalize()
|
|
39
|
+
|
|
40
|
+
plaintext = plaintext_bytes.decode("utf-8")
|
|
41
|
+
|
|
42
|
+
return plaintext
|
|
43
|
+
except Exception as e:
|
|
44
|
+
raise ValueError(f"Decryption failed: {str(e)}")
|
|
45
|
+
|
|
46
|
+
@staticmethod
|
|
47
|
+
def _create_padder():
|
|
48
|
+
from cryptography.hazmat.primitives.padding import PKCS7
|
|
49
|
+
return PKCS7(algorithms.AES.block_size).padder()
|
|
50
|
+
|
|
51
|
+
@staticmethod
|
|
52
|
+
def _create_unpadder():
|
|
53
|
+
from cryptography.hazmat.primitives.padding import PKCS7
|
|
54
|
+
return PKCS7(algorithms.AES.block_size).unpadder()
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Optional
|
|
3
|
+
from pydantic import Field
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
from fastapi import HTTPException, status
|
|
6
|
+
from typing import Any, Literal, NotRequired
|
|
7
|
+
from typing_extensions import TypedDict
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
class ThreadInfo(BaseModel):
|
|
12
|
+
thread_id: str
|
|
13
|
+
graph_name: str
|
|
14
|
+
status: str
|
|
15
|
+
|
|
16
|
+
class RunResponse(BaseModel):
|
|
17
|
+
status: str
|
|
18
|
+
thread_id: str
|
|
19
|
+
result: Optional[Any] = None
|
|
20
|
+
error: Optional[str] = None
|
|
21
|
+
|
|
22
|
+
def error_response(
|
|
23
|
+
status_code: int,
|
|
24
|
+
error_type: str,
|
|
25
|
+
message: str,
|
|
26
|
+
**additional_info: Any
|
|
27
|
+
) -> HTTPException:
|
|
28
|
+
"""Helper function to create consistent error responses."""
|
|
29
|
+
detail = {
|
|
30
|
+
"status": "error",
|
|
31
|
+
"error": error_type,
|
|
32
|
+
"message": message,
|
|
33
|
+
**additional_info
|
|
34
|
+
}
|
|
35
|
+
return HTTPException(
|
|
36
|
+
status_code=status_code,
|
|
37
|
+
detail=detail
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
class ToolCall(TypedDict):
|
|
41
|
+
"""Represents a request to call a tool."""
|
|
42
|
+
|
|
43
|
+
name: str
|
|
44
|
+
"""The name of the tool to be called."""
|
|
45
|
+
args: dict[str, Any]
|
|
46
|
+
"""The arguments to the tool call."""
|
|
47
|
+
id: str | None
|
|
48
|
+
"""An identifier associated with the tool call."""
|
|
49
|
+
type: NotRequired[Literal["tool_call"]]
|
|
50
|
+
|
|
51
|
+
class ChatMessage(BaseModel):
|
|
52
|
+
"""Message in a chat."""
|
|
53
|
+
type: Literal["human", "ai", "tool"] = Field(
|
|
54
|
+
description="Role of the message.",
|
|
55
|
+
examples=["human", "ai", "tool"],
|
|
56
|
+
)
|
|
57
|
+
content_type: Literal["markdown", "json", "html", "xml", "python", "yaml", "text"] = Field(
|
|
58
|
+
description="The data type of the message.",
|
|
59
|
+
examples=["markdown", "json", "html", "xml", "python", "yaml", "text"],
|
|
60
|
+
)
|
|
61
|
+
content: str = Field(
|
|
62
|
+
description="Content of the message.",
|
|
63
|
+
examples=["Hello, world!"],
|
|
64
|
+
)
|
|
65
|
+
tool_calls: list[ToolCall] = Field(
|
|
66
|
+
description="Tool calls in the message.",
|
|
67
|
+
default=[],
|
|
68
|
+
)
|
|
69
|
+
response_metadata: dict[str, Any] = Field(
|
|
70
|
+
description="Response metadata. For example: response headers, logprobs, token counts.",
|
|
71
|
+
default={},
|
|
72
|
+
)
|
|
73
|
+
references: list[dict[str, Any]] = Field(
|
|
74
|
+
description="references metadata. For example: List of reference materials.",
|
|
75
|
+
default=[],
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
def pretty_repr(self) -> str:
|
|
79
|
+
"""Get a pretty representation of the message."""
|
|
80
|
+
base_title = self.type.title() + " Message"
|
|
81
|
+
padded = " " + base_title + " "
|
|
82
|
+
sep_len = (80 - len(padded)) // 2
|
|
83
|
+
sep = "=" * sep_len
|
|
84
|
+
second_sep = sep + "=" if len(padded) % 2 else sep
|
|
85
|
+
title = f"{sep}{padded}{second_sep}"
|
|
86
|
+
return f"{title}\n\n{self.content}"
|
|
87
|
+
|
|
88
|
+
def pretty_print(self) -> None:
|
|
89
|
+
logger.info(self.pretty_repr()) # noqa: T201
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def sse_response_example() -> dict[int | str, Any]:
|
|
93
|
+
return {
|
|
94
|
+
status.HTTP_200_OK: {
|
|
95
|
+
"description": "Server Sent Event Response",
|
|
96
|
+
"content": {
|
|
97
|
+
"text/event-stream": {
|
|
98
|
+
"example": "data: {'type': 'token', 'content': 'Hello'}\n\ndata: {'type': 'token', 'content': ' World'}\n\ndata: [DONE]\n\n",
|
|
99
|
+
"schema": {"type": "string"},
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Dict, Set
|
|
3
|
+
from enum import Enum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ConfigCategory(Enum):
|
|
7
|
+
CHAT_PROVIDER = "CHAT_PROVIDER"
|
|
8
|
+
CHAT_MODEL = "CHAT_MODEL"
|
|
9
|
+
CHAT_CREDENTIALS = "CHAT_CREDENTIALS"
|
|
10
|
+
|
|
11
|
+
EMBEDDING_PROVIDER = "EMBEDDING_PROVIDER"
|
|
12
|
+
EMBEDDING_MODEL = "EMBEDDING_MODEL"
|
|
13
|
+
EMBEDDING_CREDENTIALS = "EMBEDDING_CREDENTIALS"
|
|
14
|
+
|
|
15
|
+
RERANK_PROVIDER = "RERANK_PROVIDER"
|
|
16
|
+
RERANK_MODEL = "RERANK_MODEL"
|
|
17
|
+
RERANK_CREDENTIALS = "RERANK_CREDENTIALS"
|
|
18
|
+
|
|
19
|
+
AGENT_ID = "AGENT_ID"
|
|
20
|
+
|
|
21
|
+
MODEL_SUFFIX_MAP = {
|
|
22
|
+
"CHAT_PROVIDER": "llm",
|
|
23
|
+
"EMBEDDING_PROVIDER": "text-embedding",
|
|
24
|
+
"RERANK_PROVIDER": "rerank",
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
PROVIDER_FIELDS = {
|
|
28
|
+
ConfigCategory.CHAT_PROVIDER.value: "llm",
|
|
29
|
+
ConfigCategory.EMBEDDING_PROVIDER.value: "text-embedding",
|
|
30
|
+
ConfigCategory.RERANK_PROVIDER.value: "rerank"
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
def process_model_from_config_dict(input_dict: Dict) -> Dict:
|
|
34
|
+
result = {
|
|
35
|
+
"models": {},
|
|
36
|
+
"required": {}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if input_dict is None:
|
|
40
|
+
return result
|
|
41
|
+
|
|
42
|
+
properties = input_dict.get('properties', {})
|
|
43
|
+
|
|
44
|
+
tool_model_map: Dict[str, Set[str]] = {}
|
|
45
|
+
|
|
46
|
+
for prop_name, prop_def in properties.items():
|
|
47
|
+
prop_name_upper = prop_name.upper()
|
|
48
|
+
|
|
49
|
+
if prop_name_upper in PROVIDER_FIELDS:
|
|
50
|
+
tool_name = "default"
|
|
51
|
+
model_type = PROVIDER_FIELDS[prop_name_upper]
|
|
52
|
+
if tool_name not in tool_model_map:
|
|
53
|
+
tool_model_map[tool_name] = set()
|
|
54
|
+
tool_model_map[tool_name].add(model_type)
|
|
55
|
+
continue
|
|
56
|
+
|
|
57
|
+
if prop_name_upper.endswith("_CHAT_PROVIDER"):
|
|
58
|
+
tool_name = prop_name_upper[:-len("_CHAT_PROVIDER")].lower()
|
|
59
|
+
if not tool_name:
|
|
60
|
+
tool_name = "default"
|
|
61
|
+
model_type = "llm"
|
|
62
|
+
elif prop_name_upper.endswith("_EMBEDDING_PROVIDER"):
|
|
63
|
+
tool_name = prop_name_upper[:-len("_EMBEDDING_PROVIDER")].lower()
|
|
64
|
+
if not tool_name:
|
|
65
|
+
tool_name = "default"
|
|
66
|
+
model_type = "text-embedding"
|
|
67
|
+
elif prop_name_upper.endswith("_RERANK_PROVIDER"):
|
|
68
|
+
tool_name = prop_name_upper[:-len("_RERANK_PROVIDER")].lower()
|
|
69
|
+
if not tool_name:
|
|
70
|
+
tool_name = "default"
|
|
71
|
+
model_type = "rerank"
|
|
72
|
+
else:
|
|
73
|
+
continue
|
|
74
|
+
|
|
75
|
+
if tool_name:
|
|
76
|
+
if tool_name not in tool_model_map:
|
|
77
|
+
tool_model_map[tool_name] = set()
|
|
78
|
+
tool_model_map[tool_name].add(model_type)
|
|
79
|
+
|
|
80
|
+
standard_fields = ['CHAT_PROVIDER', 'EMBEDDING_PROVIDER']
|
|
81
|
+
for field in standard_fields:
|
|
82
|
+
if field in properties:
|
|
83
|
+
tool_name = "default"
|
|
84
|
+
model_type = "llm" if field == "CHAT_PROVIDER" else "text-embedding"
|
|
85
|
+
if tool_name not in tool_model_map:
|
|
86
|
+
tool_model_map[tool_name] = set()
|
|
87
|
+
tool_model_map[tool_name].add(model_type)
|
|
88
|
+
|
|
89
|
+
if not tool_model_map:
|
|
90
|
+
for prop_name in properties.keys():
|
|
91
|
+
prop_name_upper = prop_name.upper()
|
|
92
|
+
if 'CHAT' in prop_name_upper and 'PROVIDER' in prop_name_upper:
|
|
93
|
+
tool_name = "default"
|
|
94
|
+
model_type = "llm"
|
|
95
|
+
if tool_name not in tool_model_map:
|
|
96
|
+
tool_model_map[tool_name] = set()
|
|
97
|
+
tool_model_map[tool_name].add(model_type)
|
|
98
|
+
elif 'EMBEDDING' in prop_name_upper and 'PROVIDER' in prop_name_upper:
|
|
99
|
+
tool_name = "default"
|
|
100
|
+
model_type = "text-embedding"
|
|
101
|
+
if tool_name not in tool_model_map:
|
|
102
|
+
tool_model_map[tool_name] = set()
|
|
103
|
+
tool_model_map[tool_name].add(model_type)
|
|
104
|
+
|
|
105
|
+
for tool, model_types in tool_model_map.items():
|
|
106
|
+
model_list = list(model_types)
|
|
107
|
+
result['models'][tool] = model_list
|
|
108
|
+
result['required'][tool] = model_list.copy()
|
|
109
|
+
|
|
110
|
+
return result
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import Dict, Any, Optional, Tuple
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def decode_jwt(token: str) -> Tuple[Optional[Dict], Optional[Dict], Optional[Dict]]:
|
|
11
|
+
try:
|
|
12
|
+
parts = token.split('.')
|
|
13
|
+
|
|
14
|
+
if len(parts) != 3:
|
|
15
|
+
logger.error("Invalid JWT format: expected 3 parts, got %d", len(parts))
|
|
16
|
+
return None, None, None
|
|
17
|
+
|
|
18
|
+
header = _decode_base64_json(parts[0], "header")
|
|
19
|
+
if not header:
|
|
20
|
+
return None, None, None
|
|
21
|
+
|
|
22
|
+
payload = _decode_base64_json(parts[1], "payload")
|
|
23
|
+
if not payload:
|
|
24
|
+
return None, None, None
|
|
25
|
+
|
|
26
|
+
user_info = _extract_user_info(payload)
|
|
27
|
+
|
|
28
|
+
_log_jwt_info(header, payload, parts[2])
|
|
29
|
+
|
|
30
|
+
return header, payload, user_info
|
|
31
|
+
|
|
32
|
+
except Exception as e:
|
|
33
|
+
logger.error("Failed to decode JWT: %s", str(e))
|
|
34
|
+
return None, None, None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _decode_base64_json(encoded_str: str, component_name: str) -> Optional[Dict]:
|
|
38
|
+
"""Decode base64 URL safe string to JSON dictionary."""
|
|
39
|
+
try:
|
|
40
|
+
# Pad with '=' for proper base64 decoding
|
|
41
|
+
padding_needed = len(encoded_str) % 4
|
|
42
|
+
if padding_needed:
|
|
43
|
+
encoded_str += '=' * (4 - padding_needed)
|
|
44
|
+
|
|
45
|
+
decoded_bytes = base64.urlsafe_b64decode(encoded_str)
|
|
46
|
+
decoded_dict = json.loads(decoded_bytes)
|
|
47
|
+
return decoded_dict
|
|
48
|
+
|
|
49
|
+
except Exception as e:
|
|
50
|
+
logger.error("Failed to decode %s: %s", component_name, str(e))
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _extract_user_info(payload: Dict) -> Dict[str, Any]:
|
|
55
|
+
user_info = {}
|
|
56
|
+
|
|
57
|
+
user_id = payload.get('id')
|
|
58
|
+
if user_id:
|
|
59
|
+
user_info['id'] = user_id
|
|
60
|
+
logger.info("Extracted user ID: %s", user_id)
|
|
61
|
+
else:
|
|
62
|
+
logger.warning("User ID not found in payload")
|
|
63
|
+
|
|
64
|
+
username = payload.get('username')
|
|
65
|
+
if username:
|
|
66
|
+
user_info['username'] = username
|
|
67
|
+
logger.info("Extracted username: %s", username)
|
|
68
|
+
else:
|
|
69
|
+
logger.warning("Username not found in payload")
|
|
70
|
+
|
|
71
|
+
return user_info
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _log_jwt_info(header: Dict, payload: Dict, signature: str) -> None:
|
|
75
|
+
logger.info("JWT Decoded Successfully")
|
|
76
|
+
logger.info("=" * 50)
|
|
77
|
+
|
|
78
|
+
logger.info("Header:")
|
|
79
|
+
logger.info(json.dumps(header, indent=2))
|
|
80
|
+
logger.info("")
|
|
81
|
+
|
|
82
|
+
logger.info("Payload:")
|
|
83
|
+
for key, value in payload.items():
|
|
84
|
+
if key in ['creationTime', 'exp', 'iat', 'lastModifiedTime'] and isinstance(value, (int, float)):
|
|
85
|
+
try:
|
|
86
|
+
dt = datetime.fromtimestamp(value)
|
|
87
|
+
logger.info(" %s: %s (%s)", key, value, dt.strftime('%Y-%m-%d %H:%M:%S'))
|
|
88
|
+
except Exception:
|
|
89
|
+
logger.info(" %s: %s", key, value)
|
|
90
|
+
else:
|
|
91
|
+
logger.info(" %s: %s", key, value)
|
|
92
|
+
|
|
93
|
+
signature_preview = signature[:50] + "..." if len(signature) > 50 else signature
|
|
94
|
+
logger.info("\nSignature: %s", signature_preview)
|
|
95
|
+
logger.info("=" * 50)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
if __name__ == "__main__":
|
|
99
|
+
# Example token (truncated for demonstration)
|
|
100
|
+
example_token = "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJjb3VudHJ5IjoiQ04iLCJjcmVhdGlvblRpbWUiOjE3NjQxNDgwNTMsImV4cCI6MTc3MjI2MDYxNywiZmlyc3ROYW1lIjoiYWRtaW4iLCJpYXQiOjE3NzIyNTcwMTcsImlkIjoiZTQ2MDk2YWUtODhmMC00NDIxLWI1YmUtMTU3OTAxZmRhZDZkIiwiaXNzIjoid2lzZS1wYWFzIiwibGFzdE1vZGlmaWVkVGltZSI6MTc3MjE2MDg3OCwibGFzdE5hbWUiOiJhZG1pbiIsImxvZ2luIjoiYWRtaW5AaW90LnNlbnNlIiwicmVmcmVzaFRva2VuIjoiN2Y4ZTkzNmMtMTQ2Ny0xMWYxLThiODUtZTJlZTMyNTQ2MWJlIiwic3RhdHVzIjoiQWN0aXZlIiwidXNlcm5hbWUiOiJhZG1pbkBpb3Quc2Vuc2UifQ.cmxJ4OG6xsKKK0qom2ur84qmra0P1_nGmCvEM7YRThN8ECyJNXQaLnXYhzEHYjbtr9anBuUoMwqkIc1kZg7t2Q"
|
|
101
|
+
|
|
102
|
+
header, payload, user_info = decode_jwt(example_token)
|
|
103
|
+
|
|
104
|
+
if user_info:
|
|
105
|
+
print(f"\nExtracted User Information:")
|
|
106
|
+
print(f"User ID: {user_info.get('id', 'Not found')}")
|
|
107
|
+
print(f"Username: {user_info.get('username', 'Not found')}")
|