ws-bom-robot-app 0.0.6__py3-none-any.whl → 0.0.8__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.
- ws_bom_robot_app/cron_manager.py +12 -6
- ws_bom_robot_app/llm/agent_description.py +5 -4
- ws_bom_robot_app/llm/agent_lcel.py +4 -4
- ws_bom_robot_app/llm/api.py +12 -6
- ws_bom_robot_app/llm/main.py +2 -2
- ws_bom_robot_app/llm/models/api.py +13 -7
- ws_bom_robot_app/llm/models/base.py +6 -0
- ws_bom_robot_app/llm/utils/agent_utils.py +4 -3
- ws_bom_robot_app/llm/utils/webhooks.py +53 -0
- ws_bom_robot_app/llm/vector_store/generator.py +0 -2
- ws_bom_robot_app/main.py +2 -0
- ws_bom_robot_app/task_manager.py +53 -22
- {ws_bom_robot_app-0.0.6.dist-info → ws_bom_robot_app-0.0.8.dist-info}/METADATA +3 -1
- {ws_bom_robot_app-0.0.6.dist-info → ws_bom_robot_app-0.0.8.dist-info}/RECORD +16 -14
- {ws_bom_robot_app-0.0.6.dist-info → ws_bom_robot_app-0.0.8.dist-info}/WHEEL +0 -0
- {ws_bom_robot_app-0.0.6.dist-info → ws_bom_robot_app-0.0.8.dist-info}/top_level.txt +0 -0
ws_bom_robot_app/cron_manager.py
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
|
-
import
|
|
2
|
-
import threading
|
|
3
|
-
import schedule, time
|
|
1
|
+
from math import floor
|
|
2
|
+
import schedule, time, threading
|
|
4
3
|
import asyncio
|
|
5
4
|
from fastapi import APIRouter
|
|
6
5
|
from ws_bom_robot_app.task_manager import task_manager
|
|
7
6
|
from ws_bom_robot_app.llm.utils.kb import kb_cleanup_data_file
|
|
8
7
|
from ws_bom_robot_app.util import _log
|
|
8
|
+
import random
|
|
9
9
|
|
|
10
10
|
class RecurringJob():
|
|
11
|
+
@staticmethod
|
|
12
|
+
def __add_jitter(interval: int) -> int:
|
|
13
|
+
#delay proportional with interval, min 10, max 100 sec
|
|
14
|
+
jitter: int = max(10, min(100, floor(interval * 0.075)))
|
|
15
|
+
return interval + random.randint(-jitter, jitter)
|
|
11
16
|
def __init__(self, interval: int, job_func, tags: list[str]):
|
|
12
|
-
|
|
17
|
+
#add a little jitter by default for better concurrency in case of multiple instances
|
|
18
|
+
self.interval = RecurringJob.__add_jitter(interval)
|
|
13
19
|
self.job_func = job_func
|
|
14
20
|
self.is_coroutine = asyncio.iscoroutinefunction(job_func)
|
|
15
21
|
self.job_func = job_func
|
|
@@ -25,8 +31,8 @@ class RecurringJob():
|
|
|
25
31
|
class CronManager:
|
|
26
32
|
|
|
27
33
|
_list: dict[str, RecurringJob] = {
|
|
28
|
-
|
|
29
|
-
|
|
34
|
+
'cleanup-task': RecurringJob(5*60, task_manager.cleanup_task, tags=["cleanup","cleanup-task"]),
|
|
35
|
+
'cleanup-data': RecurringJob(180*60, kb_cleanup_data_file, tags=["cleanup","cleanup-data"]),
|
|
30
36
|
}
|
|
31
37
|
|
|
32
38
|
def __init__(self):
|
|
@@ -7,6 +7,7 @@ from langchain_core.messages import AIMessage
|
|
|
7
7
|
from langchain_core.runnables import RunnableSerializable
|
|
8
8
|
from langchain_core.runnables import RunnableLambda
|
|
9
9
|
from bs4 import BeautifulSoup
|
|
10
|
+
from ws_bom_robot_app.llm.models.api import LlmRules
|
|
10
11
|
from ws_bom_robot_app.llm.utils.agent_utils import get_rules
|
|
11
12
|
|
|
12
13
|
# SafeDict helper class
|
|
@@ -85,19 +86,19 @@ class AgentDescriptor:
|
|
|
85
86
|
def get_strategy(name: str) -> AgentDescriptorStrategy:
|
|
86
87
|
return AgentDescriptor._list.get(name.lower(), DefaultAgentDescriptor())
|
|
87
88
|
|
|
88
|
-
def __init__(self, api_key: str, prompt: str, mode: str,
|
|
89
|
+
def __init__(self, api_key: str, prompt: str, mode: str, rules: LlmRules = None):
|
|
89
90
|
self.__prompt = prompt
|
|
90
91
|
self.__llm = ChatOpenAI(model="gpt-4o", temperature=0, api_key=api_key) # type: ignore
|
|
91
92
|
self.api_key = api_key
|
|
92
|
-
self.
|
|
93
|
+
self.rules= rules
|
|
93
94
|
self.strategy = self.get_strategy(mode) # Selects the strategy from the dictionary
|
|
94
95
|
|
|
95
96
|
async def __create_prompt(self, input_dict: dict):
|
|
96
97
|
input_data = json.loads(input_dict.get("input", {}))
|
|
97
98
|
system = self.strategy.enrich_prompt(self.__prompt, input_data)
|
|
98
|
-
if self.
|
|
99
|
+
if self.rules:
|
|
99
100
|
rule_input = self.strategy.rule_input(input_data)
|
|
100
|
-
rules_prompt = await get_rules(self.
|
|
101
|
+
rules_prompt = await get_rules(self.rules,self.api_key, rule_input)
|
|
101
102
|
system += rules_prompt
|
|
102
103
|
return ChatPromptTemplate.from_messages(
|
|
103
104
|
[
|
|
@@ -9,20 +9,20 @@ from langchain.agents.output_parsers.openai_tools import OpenAIToolsAgentOutputP
|
|
|
9
9
|
from langchain_core.runnables import RunnableLambda
|
|
10
10
|
from datetime import datetime
|
|
11
11
|
from langchain_openai import OpenAIEmbeddings
|
|
12
|
-
from ws_bom_robot_app.llm.models.api import LlmMessage,
|
|
12
|
+
from ws_bom_robot_app.llm.models.api import LlmMessage, LlmRules
|
|
13
13
|
from ws_bom_robot_app.llm.utils.agent_utils import get_rules
|
|
14
14
|
from ws_bom_robot_app.llm.defaut_prompt import default_prompt
|
|
15
15
|
|
|
16
16
|
class AgentLcel:
|
|
17
17
|
|
|
18
|
-
def __init__(self, openai_config: dict, sys_message: str, tools: list,
|
|
18
|
+
def __init__(self, openai_config: dict, sys_message: str, tools: list, rules: LlmRules = None):
|
|
19
19
|
self.__apy_key = openai_config["api_key"]
|
|
20
20
|
self.sys_message = sys_message.format(
|
|
21
21
|
date_stamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
22
22
|
lang="it",
|
|
23
23
|
)
|
|
24
24
|
self.__tools = tools
|
|
25
|
-
self.
|
|
25
|
+
self.rules = rules
|
|
26
26
|
self.embeddings = OpenAIEmbeddings(api_key= self.__apy_key) # type: ignore
|
|
27
27
|
self.memory_key = "chat_history"
|
|
28
28
|
self.__llm = ChatOpenAI(
|
|
@@ -37,7 +37,7 @@ class AgentLcel:
|
|
|
37
37
|
async def __create_prompt(self, input):
|
|
38
38
|
message : LlmMessage = input["input"]
|
|
39
39
|
input = message.content
|
|
40
|
-
rules_prompt = await get_rules(self.
|
|
40
|
+
rules_prompt = await get_rules(self.rules,self.__apy_key, input) if self.rules else ""
|
|
41
41
|
system = default_prompt + self.sys_message + rules_prompt
|
|
42
42
|
return ChatPromptTemplate.from_messages(
|
|
43
43
|
[
|
ws_bom_robot_app/llm/api.py
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import openai
|
|
2
|
-
from
|
|
2
|
+
from typing import Annotated
|
|
3
|
+
from fastapi import APIRouter, HTTPException, Header
|
|
3
4
|
from fastapi.responses import StreamingResponse
|
|
4
5
|
from ws_bom_robot_app.llm.agent_description import AgentDescriptor
|
|
5
6
|
from ws_bom_robot_app.llm.models.api import InvokeRequest, StreamRequest, RulesRequest, KbRequest, VectorDbResponse
|
|
6
7
|
from ws_bom_robot_app.llm.main import invoke, stream, stream_none
|
|
8
|
+
from ws_bom_robot_app.llm.models.base import IdentifiableEntity
|
|
7
9
|
from ws_bom_robot_app.llm.vector_store.generator import kb, rules, kb_stream_file
|
|
8
10
|
from ws_bom_robot_app.llm.tools.tool_manager import ToolManager
|
|
9
11
|
from ws_bom_robot_app.llm.vector_store.integration.manager import IntegrationManager
|
|
10
|
-
from ws_bom_robot_app.task_manager import task_manager
|
|
12
|
+
from ws_bom_robot_app.task_manager import task_manager, TaskHeader
|
|
11
13
|
|
|
12
14
|
router = APIRouter(prefix="/api/llm", tags=["llm"])
|
|
13
15
|
|
|
@@ -40,12 +42,16 @@ async def _kb(rq: KbRequest) -> VectorDbResponse:
|
|
|
40
42
|
return await kb(rq)
|
|
41
43
|
|
|
42
44
|
@router.post("/kb/task")
|
|
43
|
-
async def
|
|
44
|
-
return
|
|
45
|
+
async def _kb_task(rq: KbRequest, headers: Annotated[TaskHeader, Header()]) -> IdentifiableEntity:
|
|
46
|
+
return task_manager.create_task(kb(rq),headers)
|
|
45
47
|
|
|
46
48
|
@router.post("/rules")
|
|
47
|
-
def _rules(rq: RulesRequest) -> VectorDbResponse:
|
|
48
|
-
return rules(rq)
|
|
49
|
+
async def _rules(rq: RulesRequest) -> VectorDbResponse:
|
|
50
|
+
return await rules(rq)
|
|
51
|
+
|
|
52
|
+
@router.post("/rules/task")
|
|
53
|
+
async def _rules_task(rq: RulesRequest, headers: Annotated[TaskHeader, Header()]) -> IdentifiableEntity:
|
|
54
|
+
return task_manager.create_task(rules(rq),headers)
|
|
49
55
|
|
|
50
56
|
@router.get("/kb/file/{filename}")
|
|
51
57
|
async def _kb_get_file(filename: str) -> StreamingResponse:
|
ws_bom_robot_app/llm/main.py
CHANGED
|
@@ -17,7 +17,7 @@ async def invoke(rq: InvokeRequest) -> str:
|
|
|
17
17
|
processor = AgentDescriptor(api_key=rq.secrets["openAIApiKey"],
|
|
18
18
|
prompt=rq.system_message,
|
|
19
19
|
mode = rq.mode,
|
|
20
|
-
|
|
20
|
+
rules=rq.rules if rq.rules else None
|
|
21
21
|
)
|
|
22
22
|
result: AIMessage = await processor.run_agent(_msg)
|
|
23
23
|
return {"result": result.content}
|
|
@@ -49,7 +49,7 @@ async def __stream(rq: StreamRequest,formatted: bool = True) -> None:
|
|
|
49
49
|
openai_config={"api_key": rq.secrets["openAIApiKey"], "openai_model": rq.model, "temperature": rq.temperature},
|
|
50
50
|
sys_message=rq.system_message,
|
|
51
51
|
tools=get_structured_tools(tools=rq.app_tools, api_key=rq.secrets["openAIApiKey"], callbacks=[callbacks[0]]),
|
|
52
|
-
|
|
52
|
+
rules=rq.rules
|
|
53
53
|
)
|
|
54
54
|
|
|
55
55
|
if "NEBULY_API_KEY" in os.environ:
|
|
@@ -15,6 +15,10 @@ class LlmSearchSettings(BaseModel):
|
|
|
15
15
|
score_threshold_id: Optional[float] = Field(None, validation_alias=AliasChoices("scoreThresholdId","score_threshold_id"))
|
|
16
16
|
search_k: Optional[int] = Field(None, validation_alias=AliasChoices("searchK","search_k"))
|
|
17
17
|
|
|
18
|
+
class LlmRules(BaseModel):
|
|
19
|
+
vector_db: Optional[str] = Field(None, validation_alias=AliasChoices("rulesVectorDb","vector_db"))
|
|
20
|
+
threshold: Optional[float] = 0.7
|
|
21
|
+
|
|
18
22
|
class LlmAppToolChainSettings(BaseModel):
|
|
19
23
|
prompt: Optional[str] = None
|
|
20
24
|
model: Optional[str] = None
|
|
@@ -41,7 +45,6 @@ class LlmAppTool(BaseModel):
|
|
|
41
45
|
endpoints: Optional[List[LlmKbEndpoint]] = Field(None, validation_alias=AliasChoices("externalEndpoints","endpoints"))
|
|
42
46
|
waiting_message: Optional[str] = Field("", validation_alias=AliasChoices("waitingMessage","waiting_message"))
|
|
43
47
|
vector_db: Optional[str] = Field(None, validation_alias=AliasChoices("vectorDbFile","vector_db"))
|
|
44
|
-
rules_vector_db: Optional[str] = Field(None, validation_alias=AliasChoices("rulesVectorDb","rules_vector_db"))
|
|
45
48
|
is_active: Optional[bool] = Field(True, validation_alias=AliasChoices("isActive","is_active"))
|
|
46
49
|
model_config = ConfigDict(
|
|
47
50
|
extra='allow'
|
|
@@ -58,7 +61,7 @@ class LlmApp(BaseModel):
|
|
|
58
61
|
secrets: Dict[str, str]
|
|
59
62
|
app_tools: Optional[List[LlmAppTool]] = Field([], validation_alias=AliasChoices("appTools","app_tools"))
|
|
60
63
|
vector_db: Optional[str] = Field(None, validation_alias=AliasChoices("vectorDb","vector_db"))
|
|
61
|
-
|
|
64
|
+
rules: Optional[LlmRules] = None
|
|
62
65
|
fine_tuned_model: Optional[str] = Field(None, validation_alias=AliasChoices("fineTunedModel","fine_tuned_model"))
|
|
63
66
|
lang_chain_tracing: Optional[bool] = Field(False, validation_alias=AliasChoices("langChainTracing","lang_chain_tracing"))
|
|
64
67
|
lang_chain_project: Optional[str] = Field(None, validation_alias=AliasChoices("langChainProject","lang_chain_project"))
|
|
@@ -68,10 +71,12 @@ class LlmApp(BaseModel):
|
|
|
68
71
|
def __vector_db_folder(self) -> str:
|
|
69
72
|
return os.path.join(config.robot_data_folder,config.robot_data_db_folder,config.robot_data_db_folder_store)
|
|
70
73
|
def __vector_dbs(self):
|
|
71
|
-
return
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
return list(set(
|
|
75
|
+
os.path.basename(db) for db in [self.vector_db] +
|
|
76
|
+
([self.rules.vector_db] if self.rules and self.rules.vector_db else []) +
|
|
77
|
+
[db for tool in (self.app_tools or []) for db in [tool.vector_db]]
|
|
78
|
+
if db is not None
|
|
79
|
+
))
|
|
75
80
|
def __decompress_zip(self,zip_file_path, extract_to):
|
|
76
81
|
shutil.unpack_archive(zip_file_path, extract_to, "zip")
|
|
77
82
|
os.remove(zip_file_path)
|
|
@@ -96,7 +101,8 @@ class LlmApp(BaseModel):
|
|
|
96
101
|
def __normalize_vector_db_path(self) -> None:
|
|
97
102
|
_vector_db_folder = self.__vector_db_folder()
|
|
98
103
|
self.vector_db = os.path.join(_vector_db_folder, os.path.splitext(os.path.basename(self.vector_db))[0]) if self.vector_db else None
|
|
99
|
-
|
|
104
|
+
if self.rules:
|
|
105
|
+
self.rules.vector_db = os.path.join(_vector_db_folder, os.path.splitext(os.path.basename(self.rules.vector_db))[0]) if self.rules.vector_db else ""
|
|
100
106
|
for tool in self.app_tools or []:
|
|
101
107
|
tool.vector_db = os.path.join(_vector_db_folder, os.path.splitext(os.path.basename(tool.vector_db))[0]) if tool.vector_db else None
|
|
102
108
|
async def initialize(self) -> None:
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import os
|
|
2
|
+
from ws_bom_robot_app.llm.models.api import LlmRules
|
|
2
3
|
from ws_bom_robot_app.llm.utils.print import HiddenPrints
|
|
3
4
|
from ws_bom_robot_app.llm.utils.faiss_helper import FaissHelper
|
|
4
5
|
|
|
5
|
-
async def get_rules(
|
|
6
|
+
async def get_rules(rules: LlmRules, api_key:str, input: str) -> str:
|
|
6
7
|
with HiddenPrints():
|
|
7
|
-
if input==""
|
|
8
|
+
if any([input=="",rules is None,rules.vector_db == "",not os.path.exists(rules.vector_db)]):
|
|
8
9
|
return ""
|
|
9
10
|
rules_prompt = ""
|
|
10
|
-
rules_doc = await FaissHelper.invoke(
|
|
11
|
+
rules_doc = await FaissHelper.invoke(rules.vector_db,api_key,input,search_type="similarity_score_threshold", search_kwargs={"score_threshold": rules.threshold}) #type: ignore
|
|
11
12
|
if len(rules_doc) > 0:
|
|
12
13
|
rules_prompt = "\nFollow this rules: \n RULES: \n"
|
|
13
14
|
for rule_doc in rules_doc:
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import base64
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
from math import floor
|
|
5
|
+
import httpx
|
|
6
|
+
import logging
|
|
7
|
+
from standardwebhooks import Webhook
|
|
8
|
+
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
|
|
9
|
+
from typing import Optional
|
|
10
|
+
from ws_bom_robot_app.config import config
|
|
11
|
+
from ws_bom_robot_app.llm.models.base import IdentifiableEntity
|
|
12
|
+
|
|
13
|
+
class WebhookNotifier:
|
|
14
|
+
MAX_RETRIES = 10
|
|
15
|
+
BASE_DELAY = 5 # seconds
|
|
16
|
+
MAX_DELAY = 120 # maximum delay between retries
|
|
17
|
+
|
|
18
|
+
@retry(
|
|
19
|
+
stop=stop_after_attempt(MAX_RETRIES),
|
|
20
|
+
wait=wait_exponential(multiplier=BASE_DELAY, max=MAX_DELAY),
|
|
21
|
+
retry=retry_if_exception_type((httpx.HTTPError, asyncio.TimeoutError)),
|
|
22
|
+
after=lambda retry_state: logging.warning(
|
|
23
|
+
f"Webhook notification attempt {retry_state.attempt_number} failed "
|
|
24
|
+
f"for endpoint {retry_state.args[1]}"
|
|
25
|
+
)
|
|
26
|
+
)
|
|
27
|
+
async def notify_webhook(
|
|
28
|
+
self,
|
|
29
|
+
data: IdentifiableEntity,
|
|
30
|
+
endpoint: str,
|
|
31
|
+
timeout: Optional[float] = 30.0
|
|
32
|
+
) -> None:
|
|
33
|
+
"""
|
|
34
|
+
Notify a signed webhook. App credentials are used to sign the webhook.
|
|
35
|
+
Args:
|
|
36
|
+
task: The task result to send
|
|
37
|
+
endpoint: Webhook URL to notify
|
|
38
|
+
timeout: Request timeout in seconds
|
|
39
|
+
Raises:
|
|
40
|
+
Exception: If all retry attempts fail
|
|
41
|
+
"""
|
|
42
|
+
wh = Webhook(base64.b64encode((config.robot_user + ':' + config.robot_password).encode('utf-8')).decode('utf-8'))
|
|
43
|
+
_data = data.model_dump_json()
|
|
44
|
+
_datetime = datetime.now(tz=timezone.utc)
|
|
45
|
+
_signature = wh.sign(data.id,_datetime,_data)
|
|
46
|
+
_headers = {
|
|
47
|
+
'webhook-id': data.id,
|
|
48
|
+
'webhook-timestamp': str(floor(_datetime.replace(tzinfo=timezone.utc).timestamp())),
|
|
49
|
+
'webhook-signature': _signature
|
|
50
|
+
}
|
|
51
|
+
async with httpx.AsyncClient(headers=_headers,verify=False,timeout=timeout) as client:
|
|
52
|
+
response = await client.post(endpoint, data=_data)
|
|
53
|
+
response.raise_for_status()
|
|
@@ -2,9 +2,7 @@ import os, shutil, traceback
|
|
|
2
2
|
import asyncio
|
|
3
3
|
from fastapi import HTTPException
|
|
4
4
|
from fastapi.responses import StreamingResponse
|
|
5
|
-
from langchain_openai import OpenAIEmbeddings
|
|
6
5
|
from langchain_core.documents import Document
|
|
7
|
-
from ws_bom_robot_app.llm import api
|
|
8
6
|
from ws_bom_robot_app.llm.vector_store.loader.base import Loader
|
|
9
7
|
from ws_bom_robot_app.llm.models.api import RulesRequest, KbRequest, VectorDbResponse
|
|
10
8
|
from ws_bom_robot_app.llm.vector_store.integration.manager import IntegrationManager
|
ws_bom_robot_app/main.py
CHANGED
|
@@ -33,6 +33,7 @@ async def openapi(authenticate: bool = Depends(authenticate)):
|
|
|
33
33
|
|
|
34
34
|
@app.get("/diag",tags=["diag"])
|
|
35
35
|
def diag(authenticate: bool = Depends(authenticate)):
|
|
36
|
+
import pkg_resources
|
|
36
37
|
from ws_bom_robot_app.llm.vector_store.loader.base import Loader as wsll
|
|
37
38
|
from ws_bom_robot_app.llm.vector_store.integration.manager import IntegrationManager as wsim
|
|
38
39
|
from ws_bom_robot_app.llm.tools.tool_manager import ToolManager as wstm
|
|
@@ -58,6 +59,7 @@ def diag(authenticate: bool = Depends(authenticate)):
|
|
|
58
59
|
"os": {
|
|
59
60
|
"pid": os.getpid(),
|
|
60
61
|
"cwd": os.getcwd(),
|
|
62
|
+
"ws_bom_robot_app": pkg_resources.get_distribution("ws_bom_robot_app").version,
|
|
61
63
|
"env": os.environ,
|
|
62
64
|
},
|
|
63
65
|
},
|
ws_bom_robot_app/task_manager.py
CHANGED
|
@@ -1,14 +1,24 @@
|
|
|
1
|
+
import inspect
|
|
1
2
|
import asyncio, os
|
|
2
3
|
from datetime import datetime, timedelta
|
|
3
4
|
from enum import Enum
|
|
4
|
-
from typing import TypeVar, Optional, Dict, Union
|
|
5
|
-
from pydantic import BaseModel, ConfigDict, computed_field
|
|
5
|
+
from typing import Annotated, TypeVar, Optional, Dict, Union, Any
|
|
6
|
+
from pydantic import BaseModel, ConfigDict, Field, computed_field
|
|
6
7
|
from uuid import uuid4
|
|
7
8
|
from fastapi import APIRouter, HTTPException
|
|
8
9
|
from ws_bom_robot_app.config import config
|
|
10
|
+
from ws_bom_robot_app.llm.models.base import IdentifiableEntity
|
|
11
|
+
from ws_bom_robot_app.llm.utils.webhooks import WebhookNotifier
|
|
9
12
|
|
|
10
13
|
T = TypeVar('T')
|
|
11
14
|
|
|
15
|
+
class TaskHeader(BaseModel):
|
|
16
|
+
x_ws_bom_msg_type: Optional[str] = None
|
|
17
|
+
x_ws_bom_webhooks: Optional[str] = None
|
|
18
|
+
model_config = ConfigDict(
|
|
19
|
+
extra='allow'
|
|
20
|
+
)
|
|
21
|
+
|
|
12
22
|
class TaskMetaData(BaseModel):
|
|
13
23
|
start_time: datetime
|
|
14
24
|
end_time: Optional[datetime] = None
|
|
@@ -25,38 +35,52 @@ class TaskMetaData(BaseModel):
|
|
|
25
35
|
}
|
|
26
36
|
)
|
|
27
37
|
|
|
28
|
-
class TaskStatus(
|
|
38
|
+
class TaskStatus(IdentifiableEntity):
|
|
29
39
|
class TaskStatusEnum(str, Enum):
|
|
30
40
|
pending = "pending"
|
|
31
41
|
completed = "completed"
|
|
32
42
|
failure = "failure"
|
|
33
|
-
|
|
43
|
+
type: Optional[str] = None
|
|
34
44
|
status: TaskStatusEnum
|
|
35
|
-
#result: Optional[Dict[str, Any]] = None
|
|
36
45
|
result: Optional[T] = None
|
|
37
46
|
metadata: TaskMetaData = None
|
|
38
47
|
error: Optional[str] = None
|
|
39
48
|
|
|
40
|
-
class TaskEntry(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
status: TaskStatus = None
|
|
44
|
-
def
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
class TaskEntry(IdentifiableEntity):
|
|
50
|
+
task: Annotated[asyncio.Task, Field(default=None, validate_default=False)] = None
|
|
51
|
+
headers: TaskHeader | None = None
|
|
52
|
+
status: Union[TaskStatus, None] = None
|
|
53
|
+
def _get_coroutine_name(self, coroutine: asyncio.coroutines) -> str:
|
|
54
|
+
if inspect.iscoroutine(coroutine):
|
|
55
|
+
return coroutine.cr_code.co_name
|
|
56
|
+
return "<unknown>"
|
|
57
|
+
def __init__(self, **data):
|
|
58
|
+
#separate task from data to handle asyncio.Task
|
|
59
|
+
task = data.pop('task',None)
|
|
60
|
+
super().__init__(**data)
|
|
61
|
+
#bypass pydantic validation
|
|
62
|
+
object.__setattr__(self, 'task', task)
|
|
63
|
+
#init status
|
|
64
|
+
if not self.status:
|
|
65
|
+
self.status = TaskStatus(
|
|
66
|
+
id=self.id,
|
|
67
|
+
type=self.headers.x_ws_bom_msg_type if self.headers and self.headers.x_ws_bom_msg_type else self._get_coroutine_name(task._coro) if task else None,
|
|
68
|
+
status=TaskStatus.TaskStatusEnum.pending,
|
|
69
|
+
metadata=TaskMetaData(
|
|
70
|
+
start_time=datetime.now(),
|
|
71
|
+
source=self._get_coroutine_name(task._coro) if task else None,
|
|
72
|
+
pid=os.getpid())
|
|
73
|
+
)
|
|
51
74
|
model_config = ConfigDict(
|
|
52
|
-
arbitrary_types_allowed=True
|
|
75
|
+
arbitrary_types_allowed=True,
|
|
76
|
+
validate_assignment=True
|
|
53
77
|
)
|
|
54
78
|
|
|
55
79
|
class TaskManager:
|
|
56
80
|
def __init__(self):
|
|
57
81
|
self.tasks: Dict[str, TaskEntry] = {}
|
|
58
82
|
|
|
59
|
-
def _task_done_callback(self, task_id: str):
|
|
83
|
+
def _task_done_callback(self, task_id: str, headers: TaskHeader | None = None):
|
|
60
84
|
def callback(task: asyncio.Task):
|
|
61
85
|
if _task := self.tasks.get(task_id):
|
|
62
86
|
try:
|
|
@@ -68,14 +92,21 @@ class TaskManager:
|
|
|
68
92
|
_task.status.error = str(e)
|
|
69
93
|
finally:
|
|
70
94
|
_task.status.metadata.end_time = datetime.now()
|
|
95
|
+
if headers and headers.x_ws_bom_webhooks:
|
|
96
|
+
asyncio.create_task(
|
|
97
|
+
WebhookNotifier().notify_webhook(_task.status,headers.x_ws_bom_webhooks)
|
|
98
|
+
)
|
|
71
99
|
return callback
|
|
72
100
|
|
|
73
|
-
def create_task(self, coroutine: asyncio.coroutines) ->
|
|
101
|
+
def create_task(self, coroutine: asyncio.coroutines, headers: TaskHeader | None = None) -> IdentifiableEntity:
|
|
74
102
|
_task = asyncio.create_task(coroutine)
|
|
75
|
-
task = TaskEntry(
|
|
76
|
-
|
|
103
|
+
task = TaskEntry(
|
|
104
|
+
id=str(uuid4()),
|
|
105
|
+
task=_task,
|
|
106
|
+
headers=headers)
|
|
107
|
+
task.task.add_done_callback(self._task_done_callback(task.id, headers))
|
|
77
108
|
self.tasks[task.id] = task
|
|
78
|
-
return task.id
|
|
109
|
+
return IdentifiableEntity(id=task.id)
|
|
79
110
|
|
|
80
111
|
def get_task(self, task_id: str) -> TaskEntry | None:
|
|
81
112
|
if _task := self.tasks.get(task_id):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ws_bom_robot_app
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.8
|
|
4
4
|
Summary: A FastAPI application serving ws bom/robot/llm platform ai.
|
|
5
5
|
Home-page: https://github.com/websolutespa/bom
|
|
6
6
|
Author: Websolute Spa
|
|
@@ -10,6 +10,8 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
10
10
|
Classifier: Operating System :: OS Independent
|
|
11
11
|
Requires-Python: >=3.12
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: standardwebhooks
|
|
14
|
+
Requires-Dist: schedule
|
|
13
15
|
Requires-Dist: aiofiles
|
|
14
16
|
Requires-Dist: pydantic==2.9.2
|
|
15
17
|
Requires-Dist: pydantic-settings==2.6.0
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
ws_bom_robot_app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
2
|
ws_bom_robot_app/auth.py,sha256=84nIbmJsMrNs0sxIQGEHbjsjc2P6ZrZZGSn8dkiL6is,895
|
|
3
3
|
ws_bom_robot_app/config.py,sha256=VUoE6MBpYnZ8FQvUjWOLCzfs35aUvpwrz4ROFVbO3AA,1831
|
|
4
|
-
ws_bom_robot_app/cron_manager.py,sha256=
|
|
5
|
-
ws_bom_robot_app/main.py,sha256=
|
|
6
|
-
ws_bom_robot_app/task_manager.py,sha256=
|
|
4
|
+
ws_bom_robot_app/cron_manager.py,sha256=Gd76VwhvlauZ5jCrZa4gyM1NoQdynzskfRYPw6niFyk,3548
|
|
5
|
+
ws_bom_robot_app/main.py,sha256=UPRqJkdhFBS8vmJYFJb4hgpx4pMFwOCrbJwt5MTQymE,3556
|
|
6
|
+
ws_bom_robot_app/task_manager.py,sha256=aiQV5EJ_Q0B0mTcERVbzgaEepEUf4OtljC_UACs6pr8,5837
|
|
7
7
|
ws_bom_robot_app/util.py,sha256=3aBK-bhsvKJwJeWOHh0c1B1BOyJ_tnUxOa1mJmFKwYQ,2618
|
|
8
8
|
ws_bom_robot_app/llm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
-
ws_bom_robot_app/llm/agent_description.py,sha256=
|
|
9
|
+
ws_bom_robot_app/llm/agent_description.py,sha256=SDJYMmwfdMxEK3a_HDEQ19bfNKmwMSFf5hqU0VSCCIE,4705
|
|
10
10
|
ws_bom_robot_app/llm/agent_handler.py,sha256=hbOf9i-ynDL3bcClqtUG-yWY8ohbCxONfT5ek9Cv0gY,5667
|
|
11
|
-
ws_bom_robot_app/llm/agent_lcel.py,sha256=
|
|
12
|
-
ws_bom_robot_app/llm/api.py,sha256=
|
|
11
|
+
ws_bom_robot_app/llm/agent_lcel.py,sha256=mOh9jfqAuN8XGNqreAXt4xw_PMydqlglIcXfkw3ppBk,2706
|
|
12
|
+
ws_bom_robot_app/llm/api.py,sha256=5cO49yhU5EXvl20zJORmrZZPc1G_nlvftdcF7cyn4Qc,3252
|
|
13
13
|
ws_bom_robot_app/llm/defaut_prompt.py,sha256=pn5a4lNLWE1NngHYjA_7tD8GasePMgsgude5fIJxsW0,756
|
|
14
|
-
ws_bom_robot_app/llm/main.py,sha256=
|
|
14
|
+
ws_bom_robot_app/llm/main.py,sha256=HWTaAmxZpVo9HEc2cfv9DqA3tlaVGl1NdBONgEI16ds,3595
|
|
15
15
|
ws_bom_robot_app/llm/settings.py,sha256=EkFGCppORenStH9W4e6_dYvQ-5p6xiEMpmUHBqNqG9M,117
|
|
16
16
|
ws_bom_robot_app/llm/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
-
ws_bom_robot_app/llm/models/api.py,sha256=
|
|
17
|
+
ws_bom_robot_app/llm/models/api.py,sha256=IGeHJhyMaZ5iqOPNj6Bko2jp6R9yALMfpyeOzwW_NGk,6982
|
|
18
|
+
ws_bom_robot_app/llm/models/base.py,sha256=1TqxuTK3rjJEALn7lvgoen_1ba3R2brAgGx6EDTtDZo,152
|
|
18
19
|
ws_bom_robot_app/llm/models/kb.py,sha256=EqC9M_nQzDKX9ttL5QG2d04aH19sh-svJdw1MinvGfk,8925
|
|
19
20
|
ws_bom_robot_app/llm/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
21
|
ws_bom_robot_app/llm/tools/tool_builder.py,sha256=rkYu0PrXV84PMi7INjCSWlrWMykUCI8aeF-QjZgLysM,854
|
|
@@ -23,13 +24,14 @@ ws_bom_robot_app/llm/tools/utils.py,sha256=Cx_6L1IpJCEQ9j0zd1Z3iou5li9mnuZbVilOY
|
|
|
23
24
|
ws_bom_robot_app/llm/tools/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
25
|
ws_bom_robot_app/llm/tools/models/main.py,sha256=LsOJ7vkcSzYLoE1oa3TG0Rs0pr9J5VS_e4li6aDx_fw,260
|
|
25
26
|
ws_bom_robot_app/llm/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
|
-
ws_bom_robot_app/llm/utils/agent_utils.py,sha256=
|
|
27
|
+
ws_bom_robot_app/llm/utils/agent_utils.py,sha256=OdrFcu2WAUFfsxj7moEehaYrUp-PX82KGIHgaWetXQ4,794
|
|
27
28
|
ws_bom_robot_app/llm/utils/download.py,sha256=iAUxH_NiCpTPtGzhC4hBtxotd2HPFt2MBhttslIxqiI,3194
|
|
28
29
|
ws_bom_robot_app/llm/utils/faiss_helper.py,sha256=DowmroVT6eIbvnA-TG84PS_D7ujvxSRIKdLuIcJmd6Q,4650
|
|
29
30
|
ws_bom_robot_app/llm/utils/kb.py,sha256=jja45WCbNI7SGEgqDS99nErlwB5eY8Ga7BMnhdMHZ90,1279
|
|
30
31
|
ws_bom_robot_app/llm/utils/print.py,sha256=bpLWY0KHXe7x7PWcWy8NS54ZWzHY8b4jrLRkpnDl108,818
|
|
32
|
+
ws_bom_robot_app/llm/utils/webhooks.py,sha256=Sdwu4dkbUC3JEf9ck1hsu4gSGQdj7gcsIrhs3cOVXxk,2112
|
|
31
33
|
ws_bom_robot_app/llm/vector_store/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
32
|
-
ws_bom_robot_app/llm/vector_store/generator.py,sha256=
|
|
34
|
+
ws_bom_robot_app/llm/vector_store/generator.py,sha256=HRVqUNoNvSmiktdeBbKzGwqDm3UYXz_yfDdk_piq8_4,5733
|
|
33
35
|
ws_bom_robot_app/llm/vector_store/integration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
34
36
|
ws_bom_robot_app/llm/vector_store/integration/base.py,sha256=eCKD3U0KPoVDMtKr2iZqauMFEKd9b2k6rqPG_YjDy0g,626
|
|
35
37
|
ws_bom_robot_app/llm/vector_store/integration/manager.py,sha256=cSFlE2erMv3Uchy788mlCFdcvmyeoqdeIiGmJ9QbLhY,583
|
|
@@ -37,7 +39,7 @@ ws_bom_robot_app/llm/vector_store/integration/sitemap.py,sha256=bdrgHBVB8Jt3xiRu
|
|
|
37
39
|
ws_bom_robot_app/llm/vector_store/loader/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
40
|
ws_bom_robot_app/llm/vector_store/loader/base.py,sha256=YKtJWTVQuOvRR2CLYTa0bGEbjqgmYVVud1YKQ9QLibc,4536
|
|
39
41
|
ws_bom_robot_app/llm/vector_store/loader/json_loader.py,sha256=G9BoxwsevgqL72h2n28O2LpzaCYNymBkX66wru9GkCw,884
|
|
40
|
-
ws_bom_robot_app-0.0.
|
|
41
|
-
ws_bom_robot_app-0.0.
|
|
42
|
-
ws_bom_robot_app-0.0.
|
|
43
|
-
ws_bom_robot_app-0.0.
|
|
42
|
+
ws_bom_robot_app-0.0.8.dist-info/METADATA,sha256=qdJuZ_aTS1xhXtkFxEGWQocb5a5swxhglUR3tdw-UZE,5641
|
|
43
|
+
ws_bom_robot_app-0.0.8.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
|
44
|
+
ws_bom_robot_app-0.0.8.dist-info/top_level.txt,sha256=Yl0akyHVbynsBX_N7wx3H3ZTkcMLjYyLJs5zBMDAKcM,17
|
|
45
|
+
ws_bom_robot_app-0.0.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|