pyxecm 3.0.0__py3-none-any.whl → 3.1.0__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.
Potentially problematic release.
This version of pyxecm might be problematic. Click here for more details.
- pyxecm/avts.py +4 -4
- pyxecm/coreshare.py +14 -15
- pyxecm/helper/data.py +2 -1
- pyxecm/helper/web.py +11 -11
- pyxecm/helper/xml.py +41 -10
- pyxecm/otac.py +1 -1
- pyxecm/otawp.py +19 -19
- pyxecm/otca.py +870 -67
- pyxecm/otcs.py +1567 -280
- pyxecm/otds.py +332 -153
- pyxecm/otkd.py +4 -4
- pyxecm/otmm.py +1 -1
- pyxecm/otpd.py +246 -30
- pyxecm-3.1.0.dist-info/METADATA +127 -0
- pyxecm-3.1.0.dist-info/RECORD +82 -0
- pyxecm_api/app.py +45 -35
- pyxecm_api/auth/functions.py +2 -2
- pyxecm_api/auth/router.py +2 -3
- pyxecm_api/common/functions.py +164 -12
- pyxecm_api/settings.py +0 -8
- pyxecm_api/terminal/router.py +1 -1
- pyxecm_api/v1_csai/router.py +33 -18
- pyxecm_customizer/browser_automation.py +98 -48
- pyxecm_customizer/customizer.py +43 -25
- pyxecm_customizer/guidewire.py +422 -8
- pyxecm_customizer/k8s.py +23 -27
- pyxecm_customizer/knowledge_graph.py +501 -20
- pyxecm_customizer/m365.py +45 -44
- pyxecm_customizer/payload.py +1684 -1159
- pyxecm_customizer/payload_list.py +3 -0
- pyxecm_customizer/salesforce.py +122 -79
- pyxecm_customizer/servicenow.py +27 -7
- pyxecm_customizer/settings.py +3 -1
- pyxecm_customizer/successfactors.py +2 -2
- pyxecm_customizer/translate.py +1 -1
- pyxecm-3.0.0.dist-info/METADATA +0 -48
- pyxecm-3.0.0.dist-info/RECORD +0 -96
- pyxecm_api/agents/__init__.py +0 -7
- pyxecm_api/agents/app.py +0 -13
- pyxecm_api/agents/functions.py +0 -119
- pyxecm_api/agents/models.py +0 -10
- pyxecm_api/agents/otcm_knowledgegraph/__init__.py +0 -1
- pyxecm_api/agents/otcm_knowledgegraph/functions.py +0 -85
- pyxecm_api/agents/otcm_knowledgegraph/models.py +0 -61
- pyxecm_api/agents/otcm_knowledgegraph/router.py +0 -74
- pyxecm_api/agents/otcm_user_agent/__init__.py +0 -1
- pyxecm_api/agents/otcm_user_agent/models.py +0 -20
- pyxecm_api/agents/otcm_user_agent/router.py +0 -65
- pyxecm_api/agents/otcm_workspace_agent/__init__.py +0 -1
- pyxecm_api/agents/otcm_workspace_agent/models.py +0 -40
- pyxecm_api/agents/otcm_workspace_agent/router.py +0 -200
- {pyxecm-3.0.0.dist-info → pyxecm-3.1.0.dist-info}/WHEEL +0 -0
- {pyxecm-3.0.0.dist-info → pyxecm-3.1.0.dist-info}/entry_points.txt +0 -0
pyxecm_api/app.py
CHANGED
|
@@ -8,7 +8,6 @@ __email__ = "mdiefenb@opentext.com"
|
|
|
8
8
|
|
|
9
9
|
import logging
|
|
10
10
|
import os
|
|
11
|
-
import threading
|
|
12
11
|
from collections.abc import AsyncGenerator
|
|
13
12
|
from contextlib import asynccontextmanager
|
|
14
13
|
from datetime import UTC, datetime
|
|
@@ -23,9 +22,6 @@ from prometheus_fastapi_instrumentator import Instrumentator
|
|
|
23
22
|
from pyxecm.helper.otel_config import tracer
|
|
24
23
|
from pyxecm_maintenance_page import run_maintenance_page
|
|
25
24
|
|
|
26
|
-
from .agents.app import agent_routers
|
|
27
|
-
from .agents.functions import register_all
|
|
28
|
-
from .agents.otcm_knowledgegraph.functions import build_graph
|
|
29
25
|
from .auth.router import router as auth_router
|
|
30
26
|
from .common.functions import PAYLOAD_LIST
|
|
31
27
|
from .common.metrics import payload_logs_by_payload, payload_logs_total
|
|
@@ -50,31 +46,20 @@ if os.path.isfile(os.path.join(api_settings.logfolder, api_settings.logfile)):
|
|
|
50
46
|
elif not os.path.exists(api_settings.logfolder):
|
|
51
47
|
os.makedirs(api_settings.logfolder)
|
|
52
48
|
|
|
53
|
-
handlers = [logging.FileHandler(os.path.join(api_settings.logfolder, api_settings.logfile))]
|
|
54
|
-
if api_settings.log_payload_processing:
|
|
55
|
-
handlers.append(logging.StreamHandler())
|
|
56
|
-
|
|
57
|
-
logging.basicConfig(
|
|
58
|
-
format="%(asctime)s %(levelname)s [%(name)s] [%(threadName)s] %(message)s",
|
|
59
|
-
datefmt="%d-%b-%Y %H:%M:%S",
|
|
60
|
-
level=api_settings.loglevel,
|
|
61
|
-
handlers=handlers,
|
|
62
|
-
)
|
|
63
|
-
|
|
64
49
|
|
|
65
50
|
@asynccontextmanager
|
|
66
51
|
async def lifespan(
|
|
67
52
|
app: FastAPI, # noqa: ARG001
|
|
68
53
|
) -> AsyncGenerator:
|
|
69
|
-
"""Lifespan
|
|
54
|
+
"""Lifespan function for FASTAPI to handle the startup and shutdown process.
|
|
70
55
|
|
|
71
56
|
Args:
|
|
72
57
|
app (FastAPI):
|
|
73
|
-
The application.
|
|
58
|
+
The FastAPI application.
|
|
74
59
|
|
|
75
60
|
"""
|
|
76
61
|
|
|
77
|
-
logger.debug("
|
|
62
|
+
logger.debug("API settings -> %s", api_settings)
|
|
78
63
|
|
|
79
64
|
with tracer.start_as_current_span("import_payloads"):
|
|
80
65
|
if api_settings.import_payload:
|
|
@@ -89,20 +74,18 @@ async def lifespan(
|
|
|
89
74
|
# Optional Payload
|
|
90
75
|
import_payload(payload_dir=api_settings.payload_dir_optional)
|
|
91
76
|
|
|
92
|
-
logger.info("Starting
|
|
77
|
+
logger.info("Starting maintenance page thread...")
|
|
93
78
|
if api_settings.maintenance_page:
|
|
94
79
|
run_maintenance_page()
|
|
95
80
|
|
|
96
|
-
if api_settings.csai_studio_integration:
|
|
97
|
-
logger.info("Registering Content Aviator tools...")
|
|
98
|
-
register_all()
|
|
99
|
-
threading.Thread(name="KnowledgeGraph", target=build_graph).start()
|
|
100
|
-
|
|
101
81
|
yield
|
|
82
|
+
|
|
102
83
|
logger.info("Shutdown")
|
|
103
84
|
PAYLOAD_LIST.stop_payload_processing()
|
|
104
85
|
|
|
105
86
|
|
|
87
|
+
# end function lifespan
|
|
88
|
+
|
|
106
89
|
app = FastAPI(
|
|
107
90
|
docs_url="/api",
|
|
108
91
|
title=api_settings.title,
|
|
@@ -114,7 +97,7 @@ app = FastAPI(
|
|
|
114
97
|
openapi_tags=[
|
|
115
98
|
{
|
|
116
99
|
"name": "auth",
|
|
117
|
-
"description": "Authentication Endpoint - Users are authenticated against
|
|
100
|
+
"description": "Authentication Endpoint - Users are authenticated against OpenText Directory Services",
|
|
118
101
|
},
|
|
119
102
|
{
|
|
120
103
|
"name": "payload",
|
|
@@ -156,8 +139,6 @@ if api_settings.csai:
|
|
|
156
139
|
)
|
|
157
140
|
|
|
158
141
|
app.include_router(router=v1_csai_router)
|
|
159
|
-
for agent_router in agent_routers:
|
|
160
|
-
app.include_router(prefix="/agents", router=agent_router)
|
|
161
142
|
|
|
162
143
|
|
|
163
144
|
## Add Prometheus Instrumentator for /metrics,
|
|
@@ -170,7 +151,7 @@ if api_settings.metrics:
|
|
|
170
151
|
|
|
171
152
|
## Start the API Server
|
|
172
153
|
def run_api() -> None:
|
|
173
|
-
"""Start the
|
|
154
|
+
"""Start the FastAPI Webserver."""
|
|
174
155
|
|
|
175
156
|
# Check if Temp and Log dir exists
|
|
176
157
|
if not os.path.exists(api_settings.temp_dir):
|
|
@@ -183,27 +164,56 @@ def run_api() -> None:
|
|
|
183
164
|
customizer_start_time = datetime.now(UTC).strftime(
|
|
184
165
|
"%Y-%m-%d_%H-%M",
|
|
185
166
|
)
|
|
186
|
-
api_settings.logfile =
|
|
167
|
+
api_settings.logfile = "customizer_{}.log".format(customizer_start_time)
|
|
187
168
|
|
|
188
169
|
# Configure Logging for uvicorn
|
|
189
170
|
log_config = uvicorn.config.LOGGING_CONFIG
|
|
190
171
|
|
|
191
172
|
# Stdout
|
|
192
|
-
log_config["formatters"]["
|
|
173
|
+
log_config["formatters"]["standard"] = {
|
|
193
174
|
"()": "uvicorn.logging.DefaultFormatter",
|
|
194
175
|
"fmt": "%(levelprefix)s [%(name)s] [%(threadName)s] %(message)s",
|
|
195
176
|
"use_colors": True,
|
|
196
177
|
}
|
|
197
|
-
|
|
198
|
-
|
|
178
|
+
|
|
179
|
+
log_config["formatters"]["logfile"] = {
|
|
180
|
+
"()": "uvicorn.logging.DefaultFormatter",
|
|
181
|
+
"fmt": "%(asctime)s %(levelname)s [%(name)s] [%(threadName)s] %(message)s",
|
|
182
|
+
"datefmt": "%d-%b-%Y %H:%M:%S",
|
|
183
|
+
"use_colors": True,
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
log_config["formatters"]["accesslog"] = {
|
|
187
|
+
"()": "uvicorn.logging.AccessFormatter",
|
|
188
|
+
"fmt": '%(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s',
|
|
189
|
+
"use_colors": False,
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
log_config["handlers"]["console"] = {
|
|
193
|
+
"formatter": "standard",
|
|
199
194
|
"class": "logging.StreamHandler",
|
|
200
195
|
"stream": "ext://sys.stdout",
|
|
201
196
|
}
|
|
202
197
|
|
|
203
|
-
log_config["
|
|
204
|
-
"
|
|
198
|
+
log_config["handlers"]["file"] = {
|
|
199
|
+
"formatter": "logfile",
|
|
200
|
+
"class": "logging.FileHandler",
|
|
201
|
+
"filename": os.path.join(api_settings.logfolder, api_settings.logfile),
|
|
202
|
+
"mode": "a",
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
log_config["handlers"]["accesslog"] = {
|
|
206
|
+
"formatter": "accesslog",
|
|
207
|
+
"class": "logging.FileHandler",
|
|
208
|
+
"filename": os.path.join(api_settings.logfolder, "access.log"),
|
|
209
|
+
"mode": "a",
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
log_config["loggers"]["uvicorn.access"]["handlers"] = ["access", "accesslog"]
|
|
213
|
+
|
|
214
|
+
log_config["loggers"]["root"] = {
|
|
205
215
|
"level": api_settings.loglevel,
|
|
206
|
-
"
|
|
216
|
+
"handlers": ["console", "file"],
|
|
207
217
|
}
|
|
208
218
|
|
|
209
219
|
logger.info("Starting processing thread...")
|
pyxecm_api/auth/functions.py
CHANGED
|
@@ -27,8 +27,8 @@ def get_groups(response: dict, token: str) -> list:
|
|
|
27
27
|
"""Get the groups of the user.
|
|
28
28
|
|
|
29
29
|
Args:
|
|
30
|
-
response (
|
|
31
|
-
token (
|
|
30
|
+
response (dict): _description_
|
|
31
|
+
token (str): _description_
|
|
32
32
|
|
|
33
33
|
Returns:
|
|
34
34
|
list: _description_
|
pyxecm_api/auth/router.py
CHANGED
|
@@ -64,9 +64,8 @@ async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]) -> J
|
|
|
64
64
|
async def read_users_me(current_user: Annotated[User, Depends(get_current_user)]) -> JSONResponse:
|
|
65
65
|
"""Get the current user.
|
|
66
66
|
|
|
67
|
-
current_user:
|
|
68
|
-
|
|
69
|
-
description: The current user.
|
|
67
|
+
current_user (User):
|
|
68
|
+
The current user.
|
|
70
69
|
|
|
71
70
|
"""
|
|
72
71
|
|
pyxecm_api/common/functions.py
CHANGED
|
@@ -2,11 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
import os
|
|
5
|
+
import time
|
|
6
|
+
from datetime import UTC, datetime
|
|
5
7
|
from typing import Annotated
|
|
6
8
|
|
|
7
9
|
from fastapi import Depends
|
|
10
|
+
from pyxecm.otca import OTCA
|
|
8
11
|
from pyxecm.otcs import OTCS
|
|
9
12
|
from pyxecm_customizer import K8s, PayloadList, Settings
|
|
13
|
+
from pyxecm_customizer.knowledge_graph import KnowledgeGraph
|
|
10
14
|
|
|
11
15
|
from pyxecm_api.auth.functions import get_otcsticket
|
|
12
16
|
from pyxecm_api.settings import CustomizerAPISettings, api_settings
|
|
@@ -18,6 +22,100 @@ LOGS_LOCK = {}
|
|
|
18
22
|
# Initialize the globel Payloadlist object
|
|
19
23
|
PAYLOAD_LIST = PayloadList(logger=logger)
|
|
20
24
|
|
|
25
|
+
# This object is initialized in the build_graph() function below.
|
|
26
|
+
KNOWLEDGEGRAPH_OBJECT: KnowledgeGraph = None
|
|
27
|
+
|
|
28
|
+
# The following ontology is fed into the knowledge graph tool description.
|
|
29
|
+
# This is currently hard-coded. Ideally this should be derived from OTCM
|
|
30
|
+
# or provided via a payload file:
|
|
31
|
+
|
|
32
|
+
KNOWLEDGEGRAPH_ONTOLOGY = {
|
|
33
|
+
("Vendor", "Material", "child"): ["offers", "supplies", "provides"],
|
|
34
|
+
("Vendor", "Purchase Order", "child"): ["supplies", "provides"],
|
|
35
|
+
("Vendor", "Purchase Contract", "child"): ["signs", "owns"],
|
|
36
|
+
("Material", "Vendor", "parent"): ["is supplied by"],
|
|
37
|
+
("Purchase Order", "Material", "child"): ["includes", "is part of"],
|
|
38
|
+
("Customer", "Sales Order", "child"): ["has ordered"],
|
|
39
|
+
("Customer", "Sales Contract", "child"): ["signs", "owns"],
|
|
40
|
+
("Sales Order", "Customer", "parent"): ["belongs to", "is initiated by"],
|
|
41
|
+
("Sales Order", "Material", "child"): ["includes", "consists of"],
|
|
42
|
+
("Sales Order", "Delivery", "child"): ["triggers", "is followed by"],
|
|
43
|
+
("Sales Order", "Production Order", "child"): ["triggers", "is followed by"],
|
|
44
|
+
("Sales Contract", "Material", "child"): ["includes", "consists of"],
|
|
45
|
+
("Production Order", "Material", "child"): ["includes", "consists of"],
|
|
46
|
+
("Production Order", "Delivery", "child"): ["triggers", "is followed by"],
|
|
47
|
+
("Production Order", "Goods Movement", "child"): ["triggers", "is followed by"],
|
|
48
|
+
("Delivery", "Goods Movement", "child"): ["triggers", "is followed by"],
|
|
49
|
+
("Delivery", "Material", "child"): ["triggers", "is followed by"],
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
### Functions
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def get_ontology() -> dict:
|
|
57
|
+
"""Get the ontology for the knowledge graph.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
dict: The ontology as a dictionary.
|
|
61
|
+
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
return KNOWLEDGEGRAPH_ONTOLOGY
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def get_knowledgegraph_object() -> KnowledgeGraph:
|
|
68
|
+
"""Get the Knowledge Graph object."""
|
|
69
|
+
|
|
70
|
+
global KNOWLEDGEGRAPH_OBJECT # noqa: PLW0603
|
|
71
|
+
|
|
72
|
+
if KNOWLEDGEGRAPH_OBJECT is None:
|
|
73
|
+
KNOWLEDGEGRAPH_OBJECT = KnowledgeGraph(otcs_object=get_otcs_object(), ontology=KNOWLEDGEGRAPH_ONTOLOGY)
|
|
74
|
+
|
|
75
|
+
return KNOWLEDGEGRAPH_OBJECT
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def build_graph() -> None:
|
|
79
|
+
"""Build the knowledge Graph. And keep it updated every hour."""
|
|
80
|
+
|
|
81
|
+
def build() -> None:
|
|
82
|
+
"""Build the knowledge graph once."""
|
|
83
|
+
|
|
84
|
+
logger.info("Starting knowledge graph build...")
|
|
85
|
+
start_time = datetime.now(UTC)
|
|
86
|
+
result = get_knowledgegraph_object().build_graph(
|
|
87
|
+
workspace_type_exclusions=None,
|
|
88
|
+
workspace_type_inclusions=[
|
|
89
|
+
"Vendor",
|
|
90
|
+
"Purchase Contract",
|
|
91
|
+
"Purchase Order",
|
|
92
|
+
"Material",
|
|
93
|
+
"Customer",
|
|
94
|
+
"Sales Order",
|
|
95
|
+
"Sales Contract",
|
|
96
|
+
"Delivery",
|
|
97
|
+
"Goods Movement",
|
|
98
|
+
],
|
|
99
|
+
workers=20, # for multi-threaded traversal
|
|
100
|
+
filter_at_traversal=True, # also filter for workspace types if following relationships
|
|
101
|
+
relationship_types=["child"], # only go from parent to child
|
|
102
|
+
strategy="BFS", # Breadth-First-Search
|
|
103
|
+
metadata=True, # don't include workspace metadata
|
|
104
|
+
)
|
|
105
|
+
end_time = datetime.now(UTC)
|
|
106
|
+
logger.info(
|
|
107
|
+
"Knowledge graph completed in %s. Processed %d workspace nodes and traversed %d workspace relationships.",
|
|
108
|
+
str(end_time - start_time),
|
|
109
|
+
result["processed"],
|
|
110
|
+
result["traversed"],
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# Endless loop to build knowledge graph and update it every hour:
|
|
114
|
+
while True:
|
|
115
|
+
build()
|
|
116
|
+
logger.info("Waiting for 1 hour before rebuilding the knowledge graph...")
|
|
117
|
+
time.sleep(3600)
|
|
118
|
+
|
|
21
119
|
|
|
22
120
|
def get_k8s_object() -> K8s:
|
|
23
121
|
"""Get an instance of a K8s object.
|
|
@@ -31,12 +129,14 @@ def get_k8s_object() -> K8s:
|
|
|
31
129
|
|
|
32
130
|
|
|
33
131
|
def get_otcs_object() -> OTCS:
|
|
34
|
-
"""Get an instance of a
|
|
132
|
+
"""Get an instance of a Content Server (OTCS) object.
|
|
35
133
|
|
|
36
134
|
Returns:
|
|
37
|
-
|
|
135
|
+
OTCS:
|
|
136
|
+
Return a new OTCS object.
|
|
38
137
|
|
|
39
138
|
"""
|
|
139
|
+
|
|
40
140
|
settings = Settings()
|
|
41
141
|
|
|
42
142
|
otcs = OTCS(
|
|
@@ -52,27 +152,34 @@ def get_otcs_object() -> OTCS:
|
|
|
52
152
|
support_path=settings.otcs.support_path,
|
|
53
153
|
download_dir=settings.otcs.download_dir,
|
|
54
154
|
feme_uri=settings.otcs.feme_uri,
|
|
55
|
-
logger=logger
|
|
155
|
+
logger=logger,
|
|
56
156
|
)
|
|
57
157
|
|
|
158
|
+
# Authenticate at Content Server:
|
|
58
159
|
otcs.authenticate()
|
|
59
160
|
|
|
60
161
|
return otcs
|
|
61
162
|
|
|
62
163
|
|
|
63
|
-
def get_otcs_object_from_otcsticket(
|
|
64
|
-
"""Get an instance of a
|
|
164
|
+
def get_otcs_object_from_otcsticket(otcs_ticket: Annotated[str, Depends(get_otcsticket)]) -> OTCS:
|
|
165
|
+
"""Get an instance of a Content Server (OTCS) object.
|
|
65
166
|
|
|
66
167
|
Returns:
|
|
67
|
-
|
|
168
|
+
OTCS:
|
|
169
|
+
Return an OTCS object.
|
|
68
170
|
|
|
69
171
|
"""
|
|
172
|
+
|
|
70
173
|
settings = Settings()
|
|
71
174
|
|
|
175
|
+
# Create an OTCS object without defining the username and password:
|
|
72
176
|
otcs = OTCS(
|
|
73
|
-
protocol=settings.otcs.url_backend.scheme,
|
|
74
|
-
hostname=settings.otcs.url_backend.host,
|
|
75
|
-
port=settings.otcs.url_backend.port,
|
|
177
|
+
# protocol=settings.otcs.url_backend.scheme,
|
|
178
|
+
# hostname=settings.otcs.url_backend.host,
|
|
179
|
+
# port=settings.otcs.url_backend.port,
|
|
180
|
+
protocol=settings.otcs.url_frontend.scheme,
|
|
181
|
+
hostname=settings.otcs.url_frontend.host,
|
|
182
|
+
port=settings.otcs.url_frontend.port,
|
|
76
183
|
public_url=str(settings.otcs.url),
|
|
77
184
|
user_partition=settings.otcs.partition,
|
|
78
185
|
resource_name=settings.otcs.resource_name,
|
|
@@ -80,19 +187,62 @@ def get_otcs_object_from_otcsticket(otcsticket: Annotated[str, Depends(get_otcst
|
|
|
80
187
|
support_path=settings.otcs.support_path,
|
|
81
188
|
download_dir=settings.otcs.download_dir,
|
|
82
189
|
feme_uri=settings.otcs.feme_uri,
|
|
83
|
-
logger=logger
|
|
190
|
+
logger=logger,
|
|
84
191
|
)
|
|
85
192
|
|
|
86
|
-
|
|
193
|
+
# Instead set the OTCS authentication ticket directly:
|
|
194
|
+
otcs._otcs_ticket = otcs_ticket # noqa: SLF001
|
|
87
195
|
|
|
88
196
|
return otcs
|
|
89
197
|
|
|
90
198
|
|
|
199
|
+
def get_otca_object(otcs_object: OTCS | None = None) -> OTCA:
|
|
200
|
+
"""Get the Content Aviator (OTCA) object.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
otcs_object (OTCS | None, optional):
|
|
204
|
+
The Content Server (OTCS) object. Defaults to None.
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
OTCA:
|
|
208
|
+
The new Content Aviator object.
|
|
209
|
+
|
|
210
|
+
"""
|
|
211
|
+
|
|
212
|
+
settings = Settings()
|
|
213
|
+
|
|
214
|
+
# Get the Kubernetes object:
|
|
215
|
+
k8s_object = get_k8s_object()
|
|
216
|
+
content_system = {}
|
|
217
|
+
# Read the content system (e.g. OTCM) from the Kubernetes Config Map:
|
|
218
|
+
for service in ["chat", "embed"]:
|
|
219
|
+
cm = k8s_object.get_config_map(f"csai-{service}-svc")
|
|
220
|
+
if cm:
|
|
221
|
+
content_system[service] = cm.data.get("CONTENT_SYSTEM", "none")
|
|
222
|
+
logger.info("Set content system for '%s' to -> '%s'.", service, content_system[service])
|
|
223
|
+
|
|
224
|
+
# Create the Content Aviator object (OTCA class):
|
|
225
|
+
otca = OTCA(
|
|
226
|
+
chat_url=str(settings.aviator.chat_svc_url),
|
|
227
|
+
embed_url=str(settings.aviator.embed_svc_url),
|
|
228
|
+
studio_url=str(settings.aviator.studio_url),
|
|
229
|
+
otds_url=str(settings.otds.url_internal),
|
|
230
|
+
client_id=settings.aviator.oauth_client,
|
|
231
|
+
client_secret=settings.aviator.oauth_secret,
|
|
232
|
+
otcs_object=otcs_object,
|
|
233
|
+
content_system=content_system,
|
|
234
|
+
logger=logger.getChild("otca"),
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
return otca
|
|
238
|
+
|
|
239
|
+
|
|
91
240
|
def get_settings() -> CustomizerAPISettings:
|
|
92
241
|
"""Get the API Settings object.
|
|
93
242
|
|
|
94
243
|
Returns:
|
|
95
|
-
CustomizerPISettings:
|
|
244
|
+
CustomizerPISettings:
|
|
245
|
+
Returns the API Settings.
|
|
96
246
|
|
|
97
247
|
"""
|
|
98
248
|
|
|
@@ -112,6 +262,7 @@ def get_otcs_logs_lock() -> dict:
|
|
|
112
262
|
|
|
113
263
|
def list_files_in_directory(directory: str) -> dict:
|
|
114
264
|
"""Recursively list files in a directory and return a nested JSON structure with URLs."""
|
|
265
|
+
|
|
115
266
|
result = {}
|
|
116
267
|
for root, dirs, files in os.walk(directory):
|
|
117
268
|
# Sort directories and files alphabetically
|
|
@@ -128,4 +279,5 @@ def list_files_in_directory(directory: str) -> dict:
|
|
|
128
279
|
for file in files:
|
|
129
280
|
file_path = os.path.join(relative_path, file)
|
|
130
281
|
current_level[file] = file_path
|
|
282
|
+
|
|
131
283
|
return result
|
pyxecm_api/settings.py
CHANGED
|
@@ -56,9 +56,6 @@ class CustomizerAPISettings(BaseSettings):
|
|
|
56
56
|
)
|
|
57
57
|
|
|
58
58
|
loglevel: Literal["INFO", "DEBUG", "WARNING", "ERROR"] = "INFO"
|
|
59
|
-
log_payload_processing: bool = Field(
|
|
60
|
-
default=False, description="Print the customizer payload processing log messages to stdout"
|
|
61
|
-
)
|
|
62
59
|
logfolder: str = Field(
|
|
63
60
|
default=os.path.join(tempfile.gettempdir(), "customizer"),
|
|
64
61
|
description="Logfolder for Customizer logfiles",
|
|
@@ -122,11 +119,6 @@ class CustomizerAPISettings(BaseSettings):
|
|
|
122
119
|
description="Prefix for the CSAI",
|
|
123
120
|
)
|
|
124
121
|
|
|
125
|
-
csai_studio_integration: bool = Field(
|
|
126
|
-
default=False,
|
|
127
|
-
description="Enable the CSAI Studio Integration",
|
|
128
|
-
)
|
|
129
|
-
|
|
130
122
|
upload_folder: str = Field(default=os.path.join(tempfile.gettempdir(), "upload"), description="Folder for uploads")
|
|
131
123
|
|
|
132
124
|
upload_key: str = Field(default=str(uuid.uuid4()), description="Upload key for the Logs")
|
pyxecm_api/terminal/router.py
CHANGED
|
@@ -93,7 +93,7 @@ async def ws_terminal(
|
|
|
93
93
|
read_task = asyncio.create_task(read_from_pty())
|
|
94
94
|
write_task = asyncio.create_task(write_to_pty())
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
_done, pending = await asyncio.wait([read_task, write_task], return_when=asyncio.FIRST_COMPLETED)
|
|
97
97
|
|
|
98
98
|
# Cancel other task
|
|
99
99
|
for task in pending:
|
pyxecm_api/v1_csai/router.py
CHANGED
|
@@ -4,15 +4,14 @@ import logging
|
|
|
4
4
|
from http import HTTPStatus
|
|
5
5
|
from typing import Annotated
|
|
6
6
|
|
|
7
|
-
from fastapi import APIRouter, Body, Depends, Query
|
|
7
|
+
from fastapi import APIRouter, Body, Depends, HTTPException, Query
|
|
8
8
|
from fastapi.responses import HTMLResponse, JSONResponse
|
|
9
9
|
from pyxecm.otcs import OTCS
|
|
10
10
|
from pyxecm_customizer.k8s import K8s
|
|
11
11
|
|
|
12
|
-
from pyxecm_api.agents.functions import get_otca_object
|
|
13
12
|
from pyxecm_api.auth.functions import get_authorized_user
|
|
14
13
|
from pyxecm_api.auth.models import User
|
|
15
|
-
from pyxecm_api.common.functions import get_k8s_object, get_otcs_object, get_settings
|
|
14
|
+
from pyxecm_api.common.functions import get_k8s_object, get_otca_object, get_otcs_object, get_settings
|
|
16
15
|
from pyxecm_api.settings import CustomizerAPISettings
|
|
17
16
|
|
|
18
17
|
from .models import CSAIEmbedMetadata
|
|
@@ -31,12 +30,16 @@ def embed_metadata(
|
|
|
31
30
|
"""Embed the Metadata of the given objects.
|
|
32
31
|
|
|
33
32
|
Args:
|
|
34
|
-
user (Annotated[User, Depends):
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
user (Annotated[User, Depends):
|
|
34
|
+
User required for authentication.
|
|
35
|
+
otcs_object (Annotated[OTCS, Depends(get_otcs_object)]):
|
|
36
|
+
The OTCS object to interact with OTCM (Content Server).
|
|
37
|
+
body (Annotated[CSAIEmbedMetadata, Body):
|
|
38
|
+
The request body.
|
|
37
39
|
|
|
38
40
|
Returns:
|
|
39
|
-
JSONResponse:
|
|
41
|
+
JSONResponse:
|
|
42
|
+
JSONResponse with success=true/false
|
|
40
43
|
|
|
41
44
|
"""
|
|
42
45
|
|
|
@@ -53,7 +56,7 @@ def get_csai_config_data(
|
|
|
53
56
|
) -> JSONResponse:
|
|
54
57
|
"""Get the csai config data."""
|
|
55
58
|
|
|
56
|
-
logger.info("
|
|
59
|
+
logger.info("Read CSAI config data by user -> %s", user.id)
|
|
57
60
|
|
|
58
61
|
config_data = {}
|
|
59
62
|
|
|
@@ -79,9 +82,9 @@ def set_csai_config_data(
|
|
|
79
82
|
k8s_object: Annotated[K8s, Depends(get_k8s_object)],
|
|
80
83
|
config: Annotated[dict, Body()],
|
|
81
84
|
) -> JSONResponse:
|
|
82
|
-
"""
|
|
85
|
+
"""Set the CSAI config data."""
|
|
83
86
|
|
|
84
|
-
logger.info("
|
|
87
|
+
logger.info("Write CSAI config data by user -> %s", user.id)
|
|
85
88
|
|
|
86
89
|
for config_map in config:
|
|
87
90
|
if not config_map.startswith(settings.csai_prefix):
|
|
@@ -115,27 +118,39 @@ def set_csai_config_data(
|
|
|
115
118
|
|
|
116
119
|
|
|
117
120
|
@router.get("/graph")
|
|
118
|
-
def get_csai_graph(name: Annotated[str, Query(..., description="Name of the
|
|
121
|
+
def get_csai_graph(name: Annotated[str, Query(..., description="Name of the graph")]) -> HTMLResponse:
|
|
119
122
|
"""Display the graph of the given name.
|
|
120
123
|
|
|
121
124
|
Args:
|
|
122
|
-
|
|
123
|
-
|
|
125
|
+
name (str):
|
|
126
|
+
The name of the CSAI graph.
|
|
124
127
|
|
|
125
128
|
Returns:
|
|
126
|
-
HTMLResponse:
|
|
129
|
+
HTMLResponse: Visualization of the CSAI graph
|
|
127
130
|
|
|
128
131
|
"""
|
|
132
|
+
|
|
133
|
+
# Get the Content Aviator object:
|
|
129
134
|
otca = get_otca_object(otcs_object=None)
|
|
130
135
|
|
|
136
|
+
# Get all graphs configured in Content Aviator:
|
|
131
137
|
graphs = otca.get_graphs()
|
|
138
|
+
# Find the graph (LangGraph) with the given name:
|
|
132
139
|
graph = [g for g in graphs if g["name"] == name]
|
|
133
140
|
|
|
134
|
-
if graph:
|
|
141
|
+
if not graph:
|
|
142
|
+
logger.error("Couldn't find graph -> '%s' for visualization!", name)
|
|
143
|
+
raise HTTPException(status_code=404, detail="Graph -> '{}' not found!".format(name))
|
|
144
|
+
|
|
145
|
+
try:
|
|
135
146
|
filename = otca.visualize_graph(graph[0]["id"])
|
|
136
147
|
|
|
137
|
-
|
|
138
|
-
|
|
148
|
+
with open(filename) as f:
|
|
149
|
+
file_content = f.read()
|
|
150
|
+
except Exception as e:
|
|
151
|
+
logger.error("Error visualizing graph -> '%s': %s", name, str(e))
|
|
152
|
+
raise HTTPException(status_code=500, detail="Failed to visualize graph -> '{}'!".format(name)) from e
|
|
153
|
+
|
|
154
|
+
logger.info("Successfully visualized graph -> '%s'", name)
|
|
139
155
|
|
|
140
|
-
logger.info("name: %s", name)
|
|
141
156
|
return HTMLResponse(status_code=200, content=file_content)
|