ws-bom-robot-app 0.0.63__py3-none-any.whl → 0.0.103__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/config.py +30 -8
- ws_bom_robot_app/cron_manager.py +13 -12
- ws_bom_robot_app/llm/agent_context.py +1 -1
- ws_bom_robot_app/llm/agent_handler.py +11 -12
- ws_bom_robot_app/llm/agent_lcel.py +80 -18
- ws_bom_robot_app/llm/api.py +69 -7
- ws_bom_robot_app/llm/evaluator.py +319 -0
- ws_bom_robot_app/llm/main.py +51 -28
- ws_bom_robot_app/llm/models/api.py +40 -6
- ws_bom_robot_app/llm/nebuly_handler.py +18 -15
- ws_bom_robot_app/llm/providers/llm_manager.py +233 -75
- ws_bom_robot_app/llm/tools/tool_builder.py +4 -1
- ws_bom_robot_app/llm/tools/tool_manager.py +48 -22
- ws_bom_robot_app/llm/utils/chunker.py +6 -1
- ws_bom_robot_app/llm/utils/cleanup.py +81 -0
- ws_bom_robot_app/llm/utils/cms.py +60 -14
- ws_bom_robot_app/llm/utils/download.py +112 -8
- ws_bom_robot_app/llm/vector_store/db/base.py +50 -0
- ws_bom_robot_app/llm/vector_store/db/chroma.py +28 -8
- ws_bom_robot_app/llm/vector_store/db/faiss.py +35 -8
- ws_bom_robot_app/llm/vector_store/db/qdrant.py +29 -14
- ws_bom_robot_app/llm/vector_store/integration/api.py +216 -0
- ws_bom_robot_app/llm/vector_store/integration/azure.py +1 -1
- ws_bom_robot_app/llm/vector_store/integration/base.py +58 -15
- ws_bom_robot_app/llm/vector_store/integration/confluence.py +33 -5
- ws_bom_robot_app/llm/vector_store/integration/dropbox.py +1 -1
- ws_bom_robot_app/llm/vector_store/integration/gcs.py +1 -1
- ws_bom_robot_app/llm/vector_store/integration/github.py +22 -22
- ws_bom_robot_app/llm/vector_store/integration/googledrive.py +46 -17
- ws_bom_robot_app/llm/vector_store/integration/jira.py +93 -60
- ws_bom_robot_app/llm/vector_store/integration/manager.py +6 -2
- ws_bom_robot_app/llm/vector_store/integration/s3.py +1 -1
- ws_bom_robot_app/llm/vector_store/integration/sftp.py +1 -1
- ws_bom_robot_app/llm/vector_store/integration/sharepoint.py +7 -14
- ws_bom_robot_app/llm/vector_store/integration/shopify.py +143 -0
- ws_bom_robot_app/llm/vector_store/integration/sitemap.py +6 -1
- ws_bom_robot_app/llm/vector_store/integration/slack.py +3 -2
- ws_bom_robot_app/llm/vector_store/integration/thron.py +236 -0
- ws_bom_robot_app/llm/vector_store/loader/base.py +52 -8
- ws_bom_robot_app/llm/vector_store/loader/docling.py +71 -33
- ws_bom_robot_app/main.py +148 -146
- ws_bom_robot_app/subprocess_runner.py +106 -0
- ws_bom_robot_app/task_manager.py +204 -53
- ws_bom_robot_app/util.py +6 -0
- {ws_bom_robot_app-0.0.63.dist-info → ws_bom_robot_app-0.0.103.dist-info}/METADATA +158 -75
- ws_bom_robot_app-0.0.103.dist-info/RECORD +76 -0
- ws_bom_robot_app/llm/settings.py +0 -4
- ws_bom_robot_app/llm/utils/kb.py +0 -34
- ws_bom_robot_app-0.0.63.dist-info/RECORD +0 -72
- {ws_bom_robot_app-0.0.63.dist-info → ws_bom_robot_app-0.0.103.dist-info}/WHEEL +0 -0
- {ws_bom_robot_app-0.0.63.dist-info → ws_bom_robot_app-0.0.103.dist-info}/top_level.txt +0 -0
ws_bom_robot_app/config.py
CHANGED
|
@@ -14,9 +14,18 @@ class Settings(BaseSettings):
|
|
|
14
14
|
robot_data_db_folder_out: str = 'out'
|
|
15
15
|
robot_data_db_folder_store: str = 'store'
|
|
16
16
|
robot_data_db_retention_days: float = 60
|
|
17
|
+
robot_data_attachment_folder: str = 'attachment'
|
|
18
|
+
robot_data_attachment_retention_days: float = 1
|
|
19
|
+
robot_ingest_max_threads: int = 1 # safe choice to 1, avoid potential process-related issues with Docker
|
|
17
20
|
robot_loader_max_threads: int = 1
|
|
18
21
|
robot_task_max_total_parallelism: int = 2 * (os.cpu_count() or 1)
|
|
19
22
|
robot_task_retention_days: float = 1
|
|
23
|
+
robot_task_strategy: str = 'memory' # memory / db
|
|
24
|
+
robot_task_mp_enable: bool = True
|
|
25
|
+
robot_task_mp_method: str = 'spawn' # spawn / fork
|
|
26
|
+
robot_task_mp_max_retries: int = 1
|
|
27
|
+
robot_task_mp_retry_delay: float = 60 # seconds
|
|
28
|
+
robot_cron_strategy: str = 'memory' # memory / db
|
|
20
29
|
robot_cms_host: str = ''
|
|
21
30
|
robot_cms_auth: str = ''
|
|
22
31
|
robot_cms_db_folder: str = 'llmVectorDb'
|
|
@@ -27,8 +36,12 @@ class Settings(BaseSettings):
|
|
|
27
36
|
OLLAMA_API_URL: str = 'http://localhost:11434'
|
|
28
37
|
GROQ_API_KEY: str = ''
|
|
29
38
|
GOOGLE_API_KEY: str = ''
|
|
30
|
-
NEBULY_API_URL: str =''
|
|
31
39
|
GOOGLE_APPLICATION_CREDENTIALS: str = '' # path to google credentials iam file, e.d. ./.secrets/google-credentials.json
|
|
40
|
+
WATSONX_URL: str = ''
|
|
41
|
+
WATSONX_APIKEY: str = ''
|
|
42
|
+
WATSONX_PROJECTID: str = ''
|
|
43
|
+
NEBULY_API_URL: str ='https://backend.nebuly.com/'
|
|
44
|
+
LANGSMITH_API_KEY: str = '' # app-wide api key to run evaluation
|
|
32
45
|
model_config = ConfigDict(
|
|
33
46
|
env_file='./.env',
|
|
34
47
|
extra='ignore',
|
|
@@ -36,6 +49,7 @@ class Settings(BaseSettings):
|
|
|
36
49
|
)
|
|
37
50
|
def __init__(self, **kwargs):
|
|
38
51
|
super().__init__(**kwargs)
|
|
52
|
+
# env
|
|
39
53
|
os.environ["USER_AGENT"] = self.USER_AGENT
|
|
40
54
|
os.environ["OPENAI_API_KEY"] = self.OPENAI_API_KEY
|
|
41
55
|
os.environ["OLLAMA_API_URL"] = self.OLLAMA_API_URL
|
|
@@ -44,11 +58,19 @@ class Settings(BaseSettings):
|
|
|
44
58
|
os.environ["GROQ_API_KEY"] = self.GROQ_API_KEY
|
|
45
59
|
os.environ["GOOGLE_API_KEY"] = self.GOOGLE_API_KEY
|
|
46
60
|
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = self.GOOGLE_APPLICATION_CREDENTIALS
|
|
61
|
+
os.environ["WATSONX_URL"] = self.WATSONX_URL
|
|
62
|
+
os.environ["WATSONX_APIKEY"] = self.WATSONX_APIKEY
|
|
63
|
+
os.environ["WATSONX_PROJECTID"] = self.WATSONX_PROJECTID
|
|
47
64
|
os.environ["NEBULY_API_URL"] = self.NEBULY_API_URL
|
|
65
|
+
os.environ["LANGSMITH_API_KEY"] = self.LANGSMITH_API_KEY
|
|
66
|
+
# dir
|
|
67
|
+
os.makedirs(self.robot_data_folder, exist_ok=True)
|
|
68
|
+
for subfolder in [self.robot_data_db_folder, self.robot_data_attachment_folder, 'db']:
|
|
69
|
+
os.makedirs(os.path.join(self.robot_data_folder, subfolder), exist_ok=True)
|
|
48
70
|
|
|
49
71
|
class RuntimeOptions(BaseModel):
|
|
50
72
|
@staticmethod
|
|
51
|
-
def
|
|
73
|
+
def _get_sys_arg(arg: str, default: int) -> int:
|
|
52
74
|
"""
|
|
53
75
|
Returns the number of worker processes to use for the application.
|
|
54
76
|
|
|
@@ -66,18 +88,18 @@ class Settings(BaseSettings):
|
|
|
66
88
|
"""
|
|
67
89
|
import sys
|
|
68
90
|
try:
|
|
69
|
-
for i,
|
|
70
|
-
if
|
|
91
|
+
for i, argv in enumerate(sys.argv):
|
|
92
|
+
if argv == f"--{arg}" and i + 1 < len(sys.argv):
|
|
71
93
|
return int(sys.argv[i + 1])
|
|
72
94
|
except (ValueError, IndexError):
|
|
73
95
|
pass
|
|
74
|
-
return
|
|
96
|
+
return default
|
|
75
97
|
debug: bool
|
|
98
|
+
tcp_port: int = _get_sys_arg("port", 6001)
|
|
76
99
|
loader_show_progress: bool
|
|
77
100
|
loader_silent_errors: bool
|
|
78
|
-
number_of_workers: int =
|
|
79
|
-
is_multi_process: bool =
|
|
80
|
-
|
|
101
|
+
number_of_workers: int = _get_sys_arg("workers", 1)
|
|
102
|
+
is_multi_process: bool = _get_sys_arg("workers", 1) > 1
|
|
81
103
|
|
|
82
104
|
def runtime_options(self) -> RuntimeOptions:
|
|
83
105
|
"""_summary_
|
ws_bom_robot_app/cron_manager.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import os
|
|
1
2
|
from apscheduler.schedulers.background import BackgroundScheduler
|
|
2
3
|
#from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
|
3
4
|
from apscheduler.jobstores.memory import MemoryJobStore
|
|
@@ -7,8 +8,7 @@ from apscheduler.triggers.interval import IntervalTrigger
|
|
|
7
8
|
from apscheduler.triggers.date import DateTrigger
|
|
8
9
|
from fastapi import APIRouter
|
|
9
10
|
from datetime import datetime
|
|
10
|
-
from ws_bom_robot_app.
|
|
11
|
-
from ws_bom_robot_app.llm.utils.kb import kb_cleanup_data_file
|
|
11
|
+
from ws_bom_robot_app.llm.utils.cleanup import kb_cleanup_data_file, chat_cleanup_attachment, task_cleanup_history
|
|
12
12
|
from ws_bom_robot_app.util import _log
|
|
13
13
|
from ws_bom_robot_app.config import config
|
|
14
14
|
|
|
@@ -22,8 +22,8 @@ class MemoryJobstoreStrategy(JobstoreStrategy):
|
|
|
22
22
|
return {"default": MemoryJobStore()}
|
|
23
23
|
|
|
24
24
|
class PersistentJobstoreStrategy(JobstoreStrategy):
|
|
25
|
-
def get_jobstore(self, db_url: str = "sqlite
|
|
26
|
-
_log.info(f"Using persistent
|
|
25
|
+
def get_jobstore(self, db_url: str = f"sqlite:///{config.robot_data_folder}/db/jobs.sqlite"):
|
|
26
|
+
_log.info(f"Using persistent cron jobstore with database URL: {db_url}.")
|
|
27
27
|
return {"default": SQLAlchemyJobStore(url=db_url)}
|
|
28
28
|
|
|
29
29
|
class Job:
|
|
@@ -56,11 +56,12 @@ class Job:
|
|
|
56
56
|
|
|
57
57
|
class CronManager:
|
|
58
58
|
_list_default = [
|
|
59
|
-
Job('cleanup-task',
|
|
60
|
-
Job('cleanup-data',kb_cleanup_data_file, interval=
|
|
59
|
+
Job('cleanup-task-history',task_cleanup_history, interval=4 * 60 * 60),
|
|
60
|
+
Job('cleanup-kb-data',kb_cleanup_data_file, interval=8 * 60 * 60),
|
|
61
|
+
Job('cleanup-chat-attachment',chat_cleanup_attachment, interval=6 * 60 * 60),
|
|
61
62
|
]
|
|
62
63
|
def __get_jobstore_strategy(self) -> JobstoreStrategy:
|
|
63
|
-
if
|
|
64
|
+
if config.robot_cron_strategy == 'memory':
|
|
64
65
|
return MemoryJobstoreStrategy()
|
|
65
66
|
return PersistentJobstoreStrategy()
|
|
66
67
|
def __init__(self, strategy: JobstoreStrategy = None, enable_defaults: bool = True):
|
|
@@ -139,22 +140,22 @@ class CronManager:
|
|
|
139
140
|
|
|
140
141
|
def execute_recurring_jobs(self):
|
|
141
142
|
for job in self.scheduler.get_jobs():
|
|
142
|
-
if job.interval:
|
|
143
|
-
job.
|
|
143
|
+
if job.trigger.interval:
|
|
144
|
+
job.func()
|
|
144
145
|
|
|
145
146
|
def pause_recurring_jobs(self):
|
|
146
147
|
for job in self.scheduler.get_jobs():
|
|
147
|
-
if job.interval:
|
|
148
|
+
if job.trigger.interval:
|
|
148
149
|
self.pause_job(job.id)
|
|
149
150
|
|
|
150
151
|
def resume_recurring_jobs(self):
|
|
151
152
|
for job in self.scheduler.get_jobs():
|
|
152
|
-
if job.interval:
|
|
153
|
+
if job.trigger.interval:
|
|
153
154
|
self.resume_job(job.id)
|
|
154
155
|
|
|
155
156
|
def remove_recurring_jobs(self):
|
|
156
157
|
for job in self.scheduler.get_jobs():
|
|
157
|
-
if job.interval:
|
|
158
|
+
if job.trigger.interval:
|
|
158
159
|
self.remove_job(job.id)
|
|
159
160
|
|
|
160
161
|
def clear(self):
|
|
@@ -7,7 +7,7 @@ class AgentContext(BaseModel):
|
|
|
7
7
|
class _i18n(BaseModel):
|
|
8
8
|
lg: Optional[str] = "en"
|
|
9
9
|
country: Optional[str] = "US"
|
|
10
|
-
timestamp: Optional[str] = Field(default_factory=lambda: datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
|
|
10
|
+
timestamp: Optional[str] = Field(default_factory=lambda: datetime.now().strftime("%Y-%m-%d %H:%M:%S %A"))
|
|
11
11
|
timezone: Optional[str] = "UTC"
|
|
12
12
|
model_config = ConfigDict(extra='allow')
|
|
13
13
|
class _user(BaseModel):
|
|
@@ -5,10 +5,9 @@ from langchain.callbacks.base import AsyncCallbackHandler
|
|
|
5
5
|
from ws_bom_robot_app.llm.utils.print import print_json, print_string
|
|
6
6
|
from typing import Any, Dict, List, Optional, Union
|
|
7
7
|
from uuid import UUID
|
|
8
|
-
import ws_bom_robot_app.llm.settings as settings
|
|
9
8
|
from langchain_core.callbacks.base import AsyncCallbackHandler
|
|
10
9
|
from langchain_core.outputs import ChatGenerationChunk, GenerationChunk
|
|
11
|
-
from langchain_core.messages import BaseMessage,
|
|
10
|
+
from langchain_core.messages import BaseMessage, AIMessage
|
|
12
11
|
import json, logging, re
|
|
13
12
|
|
|
14
13
|
# Here is a custom handler that will print the tokens to stdout.
|
|
@@ -85,6 +84,8 @@ class AgentHandler(AsyncCallbackHandler):
|
|
|
85
84
|
# if the pending text is long, we output most of it except the last few characters.
|
|
86
85
|
if len(self.stream_buffer) > self.stream_cut_last_output_chunk_size:
|
|
87
86
|
safe_cut = self.stream_buffer[:-3]
|
|
87
|
+
if safe_cut.startswith('`'):
|
|
88
|
+
safe_cut = safe_cut[1:]
|
|
88
89
|
await self.queue.put(print_string(safe_cut))
|
|
89
90
|
self.stream_buffer = self.stream_buffer[-3:]
|
|
90
91
|
else:
|
|
@@ -109,6 +110,10 @@ class AgentHandler(AsyncCallbackHandler):
|
|
|
109
110
|
self.json_buffer += self.stream_buffer
|
|
110
111
|
self.stream_buffer = ""
|
|
111
112
|
|
|
113
|
+
async def on_tool_end(self, output, *, run_id, parent_run_id = None, tags = None, **kwargs):
|
|
114
|
+
if "stream" in (tags or []):
|
|
115
|
+
await self.queue.put(print_json(output))
|
|
116
|
+
|
|
112
117
|
async def on_agent_finish(
|
|
113
118
|
self,
|
|
114
119
|
finish: AgentFinish,
|
|
@@ -118,11 +123,6 @@ class AgentHandler(AsyncCallbackHandler):
|
|
|
118
123
|
tags: List[str] = None,
|
|
119
124
|
**kwargs: Any,
|
|
120
125
|
) -> None:
|
|
121
|
-
settings.chat_history.extend(
|
|
122
|
-
[
|
|
123
|
-
AIMessage(content=_parse_token(self.llm,finish.return_values["output"])),
|
|
124
|
-
]
|
|
125
|
-
)
|
|
126
126
|
# end-of-stream: flush any remaining text
|
|
127
127
|
if self.in_json_block:
|
|
128
128
|
try:
|
|
@@ -160,6 +160,10 @@ class RawAgentHandler(AsyncCallbackHandler):
|
|
|
160
160
|
if token: # only process non-empty tokens
|
|
161
161
|
await self.queue.put(_parse_token(self.llm,token))
|
|
162
162
|
|
|
163
|
+
async def on_tool_end(self, output, *, run_id, parent_run_id = None, tags = None, **kwargs):
|
|
164
|
+
if "stream" in (tags or []):
|
|
165
|
+
await self.queue.put(print_json(output))
|
|
166
|
+
|
|
163
167
|
async def on_agent_finish(
|
|
164
168
|
self,
|
|
165
169
|
finish: AgentFinish,
|
|
@@ -169,9 +173,4 @@ class RawAgentHandler(AsyncCallbackHandler):
|
|
|
169
173
|
tags: List[str] = None,
|
|
170
174
|
**kwargs: Any,
|
|
171
175
|
) -> None:
|
|
172
|
-
settings.chat_history.extend(
|
|
173
|
-
[
|
|
174
|
-
AIMessage(content=_parse_token(self.llm,finish.return_values["output"]))
|
|
175
|
-
]
|
|
176
|
-
)
|
|
177
176
|
await self.queue.put(None)
|
|
@@ -3,6 +3,7 @@ from langchain.agents import AgentExecutor, create_tool_calling_agent
|
|
|
3
3
|
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
|
|
4
4
|
from langchain_core.runnables import RunnableLambda
|
|
5
5
|
from langchain_core.tools import render_text_description
|
|
6
|
+
from pydantic import create_model, BaseModel
|
|
6
7
|
import chevron
|
|
7
8
|
from ws_bom_robot_app.llm.agent_context import AgentContext
|
|
8
9
|
from ws_bom_robot_app.llm.providers.llm_manager import LlmInterface
|
|
@@ -12,34 +13,95 @@ from ws_bom_robot_app.llm.defaut_prompt import default_prompt, tool_prompt
|
|
|
12
13
|
|
|
13
14
|
class AgentLcel:
|
|
14
15
|
|
|
15
|
-
def __init__(self, llm: LlmInterface, sys_message: str, sys_context: AgentContext, tools: list, rules: LlmRules = None):
|
|
16
|
+
def __init__(self, llm: LlmInterface, sys_message: str, sys_context: AgentContext, tools: list, rules: LlmRules = None, json_schema: Optional[dict] = None):
|
|
16
17
|
self.sys_message = chevron.render(template=sys_message,data=sys_context)
|
|
17
18
|
self.__llm = llm
|
|
18
19
|
self.__tools = tools
|
|
19
20
|
self.rules = rules
|
|
21
|
+
self.json_schema = json_schema
|
|
20
22
|
self.embeddings = llm.get_embeddings()
|
|
21
23
|
self.memory_key: str = "chat_history"
|
|
22
24
|
self.__llm_with_tools = llm.get_llm().bind_tools(self.__tools) if len(self.__tools) > 0 else llm.get_llm()
|
|
25
|
+
if self.json_schema:
|
|
26
|
+
self.__pydantic_schema = self.__create_pydantic_schema()
|
|
27
|
+
else:
|
|
28
|
+
self.__pydantic_schema = None
|
|
29
|
+
|
|
23
30
|
self.executor = self.__create_agent()
|
|
24
31
|
|
|
32
|
+
def __create_pydantic_schema(self) -> type[BaseModel]:
|
|
33
|
+
"""Crea un Pydantic model dinamico dallo schema JSON."""
|
|
34
|
+
if not self.json_schema:
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
type_map = {
|
|
38
|
+
"string": str,
|
|
39
|
+
"text": str,
|
|
40
|
+
"number": float,
|
|
41
|
+
"float": float,
|
|
42
|
+
"int": int,
|
|
43
|
+
"integer": int,
|
|
44
|
+
"bool": bool,
|
|
45
|
+
"boolean": bool,
|
|
46
|
+
"list": list,
|
|
47
|
+
"array": list,
|
|
48
|
+
"dict": dict,
|
|
49
|
+
"object": dict,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
fields: dict[str, tuple[Any, Any]] = {}
|
|
53
|
+
for k, v in self.json_schema.items():
|
|
54
|
+
if isinstance(v, str):
|
|
55
|
+
py_type = type_map.get(v.lower(), str)
|
|
56
|
+
fields[k] = (py_type, ...)
|
|
57
|
+
elif isinstance(v, dict):
|
|
58
|
+
fields[k] = (dict, ...)
|
|
59
|
+
elif isinstance(v, list):
|
|
60
|
+
fields[k] = (list, ...)
|
|
61
|
+
else:
|
|
62
|
+
fields[k] = (type(v), ...)
|
|
63
|
+
|
|
64
|
+
return create_model('JsonSchema', **fields)
|
|
65
|
+
|
|
66
|
+
def __get_output_parser(self):
|
|
67
|
+
return self.__llm.get_parser()
|
|
68
|
+
|
|
25
69
|
async def __create_prompt(self, input: dict) -> ChatPromptTemplate:
|
|
70
|
+
from langchain_core.messages import SystemMessage
|
|
26
71
|
message : LlmMessage = input[self.memory_key][-1]
|
|
27
72
|
rules_prompt = await get_rules(self.embeddings, self.rules, message.content) if self.rules else ""
|
|
28
73
|
system = default_prompt + (tool_prompt(render_text_description(self.__tools)) if len(self.__tools)>0 else "") + self.sys_message + rules_prompt
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
74
|
+
|
|
75
|
+
# Aggiungi istruzioni per output JSON strutturato se necessario
|
|
76
|
+
if self.json_schema and self.__pydantic_schema:
|
|
77
|
+
json_instructions = f"\n\nIMPORTANT: You must format your final response as a JSON object with the following structure:\n"
|
|
78
|
+
for field_name, field_info in self.__pydantic_schema.model_fields.items():
|
|
79
|
+
field_type = field_info.annotation.__name__ if hasattr(field_info.annotation, '__name__') else str(field_info.annotation)
|
|
80
|
+
json_instructions += f"- {field_name}: {field_type}\n"
|
|
81
|
+
json_instructions += "\nProvide ONLY the JSON object in your response, no additional text."
|
|
82
|
+
system += json_instructions
|
|
83
|
+
|
|
84
|
+
messages = [
|
|
85
|
+
SystemMessage(content=system),
|
|
86
|
+
MessagesPlaceholder(variable_name=self.memory_key),
|
|
87
|
+
MessagesPlaceholder(variable_name="agent_scratchpad"),
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
prompt = ChatPromptTemplate.from_messages(
|
|
91
|
+
messages=messages,
|
|
92
|
+
template_format=None,
|
|
93
|
+
)
|
|
94
|
+
return prompt
|
|
95
|
+
|
|
96
|
+
def __create_agent(self):
|
|
97
|
+
# Un solo AgentExecutor per entrambe le modalità
|
|
98
|
+
agent = (
|
|
99
|
+
{
|
|
100
|
+
"agent_scratchpad": lambda x: self.__llm.get_formatter(x["intermediate_steps"]),
|
|
101
|
+
self.memory_key: lambda x: x[self.memory_key],
|
|
102
|
+
}
|
|
103
|
+
| RunnableLambda(self.__create_prompt)
|
|
104
|
+
| self.__llm_with_tools
|
|
105
|
+
| self.__get_output_parser()
|
|
106
|
+
)
|
|
107
|
+
return AgentExecutor(agent=agent, tools=self.__tools, verbose=False)
|
ws_bom_robot_app/llm/api.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
from typing import Annotated, Any, Mapping
|
|
1
|
+
from typing import Annotated, Any, Mapping, Union
|
|
2
2
|
from fastapi import APIRouter, HTTPException, Request, Header, Body
|
|
3
3
|
from fastapi.responses import StreamingResponse
|
|
4
4
|
from ws_bom_robot_app.llm.agent_description import AgentDescriptor
|
|
5
|
+
from ws_bom_robot_app.llm.evaluator import EvaluatorRunRequest
|
|
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
|
|
7
8
|
from ws_bom_robot_app.llm.models.base import IdentifiableEntity
|
|
@@ -21,9 +22,9 @@ async def root():
|
|
|
21
22
|
async def _invoke(rq: InvokeRequest):
|
|
22
23
|
return await invoke(rq)
|
|
23
24
|
|
|
24
|
-
def
|
|
25
|
+
def _rs_stream_headers(rq: StreamRequest) -> Mapping[str, str]:
|
|
25
26
|
return {
|
|
26
|
-
"X-thread-id": rq.
|
|
27
|
+
"X-thread-id": rq.thread_id or str(uuid4()),
|
|
27
28
|
"X-msg-id": rq.msg_id or str(uuid4()),
|
|
28
29
|
}
|
|
29
30
|
|
|
@@ -40,11 +41,11 @@ async def cms_app_by_id(id: str):
|
|
|
40
41
|
|
|
41
42
|
@router.post("/stream")
|
|
42
43
|
async def _stream(rq: StreamRequest, ctx: Request) -> StreamingResponse:
|
|
43
|
-
return StreamingResponse(stream(rq, ctx), media_type="application/json", headers=
|
|
44
|
+
return StreamingResponse(stream(rq, ctx), media_type="application/json", headers=_rs_stream_headers(rq))
|
|
44
45
|
|
|
45
46
|
@router.post("/stream/raw")
|
|
46
47
|
async def _stream_raw(rq: StreamRequest, ctx: Request) -> StreamingResponse:
|
|
47
|
-
return StreamingResponse(stream(rq, ctx, formatted=False), media_type="application/json", headers=
|
|
48
|
+
return StreamingResponse(stream(rq, ctx, formatted=False), media_type="application/json", headers=_rs_stream_headers(rq))
|
|
48
49
|
|
|
49
50
|
@router.post("/kb")
|
|
50
51
|
async def _kb(rq: KbRequest) -> VectorDbResponse:
|
|
@@ -52,7 +53,7 @@ async def _kb(rq: KbRequest) -> VectorDbResponse:
|
|
|
52
53
|
|
|
53
54
|
@router.post("/kb/task")
|
|
54
55
|
async def _kb_task(rq: KbRequest, headers: Annotated[TaskHeader, Header()]) -> IdentifiableEntity:
|
|
55
|
-
return task_manager.create_task(kb(rq),headers)
|
|
56
|
+
return task_manager.create_task(lambda: kb(rq),headers, queue="slow")
|
|
56
57
|
|
|
57
58
|
@router.post("/rules")
|
|
58
59
|
async def _rules(rq: RulesRequest) -> VectorDbResponse:
|
|
@@ -60,7 +61,7 @@ async def _rules(rq: RulesRequest) -> VectorDbResponse:
|
|
|
60
61
|
|
|
61
62
|
@router.post("/rules/task")
|
|
62
63
|
async def _rules_task(rq: RulesRequest, headers: Annotated[TaskHeader, Header()]) -> IdentifiableEntity:
|
|
63
|
-
return task_manager.create_task(rules(rq),headers)
|
|
64
|
+
return task_manager.create_task(lambda: rules(rq), headers, queue="fast")
|
|
64
65
|
|
|
65
66
|
@router.get("/kb/file/{filename}")
|
|
66
67
|
async def _kb_get_file(filename: str) -> StreamingResponse:
|
|
@@ -115,3 +116,64 @@ async def _send_feedback(feedback: FeedbackConfig):
|
|
|
115
116
|
strategy: FeedbackInterface = strategy_cls(feedback)
|
|
116
117
|
result = strategy.send_feedback()
|
|
117
118
|
return {"result": result}
|
|
119
|
+
|
|
120
|
+
#region evaluate
|
|
121
|
+
@router.get("/evaluation/datasets", tags=["evaluation"])
|
|
122
|
+
async def _evaluation_datasets():
|
|
123
|
+
from ws_bom_robot_app.llm.evaluator import EvaluatorDataSets
|
|
124
|
+
return [ds for ds in EvaluatorDataSets.all()]
|
|
125
|
+
|
|
126
|
+
@router.post("/evaluation/datasets/find", tags=["evaluation"])
|
|
127
|
+
async def _evaluation_find_datasets(project: str):
|
|
128
|
+
from ws_bom_robot_app.llm.evaluator import EvaluatorDataSets
|
|
129
|
+
return [ds for ds in EvaluatorDataSets.find(project)]
|
|
130
|
+
|
|
131
|
+
@router.get("/evaluation/datasets/{id}", tags=["evaluation"])
|
|
132
|
+
async def _evaluation_datasets_by_id(id: str):
|
|
133
|
+
from ws_bom_robot_app.llm.evaluator import EvaluatorDataSets
|
|
134
|
+
return EvaluatorDataSets.example(id)
|
|
135
|
+
|
|
136
|
+
@router.get("/evaluation/evaluators", tags=["evaluation"])
|
|
137
|
+
async def _evaluation_evaluators() -> list:
|
|
138
|
+
from ws_bom_robot_app.llm.evaluator import EvaluatorType
|
|
139
|
+
return EvaluatorType.all()
|
|
140
|
+
|
|
141
|
+
@router.post("/evaluation/run", tags=["evaluation"])
|
|
142
|
+
async def _evaluate(rq: EvaluatorRunRequest):
|
|
143
|
+
from ws_bom_robot_app.llm.evaluator import Evaluator, EvaluatorType
|
|
144
|
+
from langsmith.schemas import Dataset, Example
|
|
145
|
+
|
|
146
|
+
_data: Union[Dataset, list[Example]] = None
|
|
147
|
+
if rq.example and any(rq.example):
|
|
148
|
+
_examples: list[Example] = filter(lambda ex: str(ex.id) in [str(e.get("id")) for e in rq.example],
|
|
149
|
+
await _evaluation_datasets_by_id(rq.example[0].get("dataset_id"))
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
_data = list(_examples)
|
|
153
|
+
else:
|
|
154
|
+
_data = Dataset(**rq.dataset)
|
|
155
|
+
evaluator = Evaluator(
|
|
156
|
+
rq=rq.rq,
|
|
157
|
+
data=_data,
|
|
158
|
+
judge_model=rq.judge
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
if not rq.evaluators is None and any(rq.evaluators):
|
|
162
|
+
def __convert_evaluator_type(evaluator: str) -> EvaluatorType:
|
|
163
|
+
try:
|
|
164
|
+
return EvaluatorType[evaluator.upper()]
|
|
165
|
+
except KeyError:
|
|
166
|
+
pass
|
|
167
|
+
_evaluators = []
|
|
168
|
+
_evaluators.extend(__convert_evaluator_type(evaluator) for evaluator in rq.evaluators)
|
|
169
|
+
if not any(_evaluators):
|
|
170
|
+
_evaluators = None
|
|
171
|
+
else:
|
|
172
|
+
_evaluators = None
|
|
173
|
+
result = await evaluator.run(evaluators=_evaluators)
|
|
174
|
+
return result
|
|
175
|
+
|
|
176
|
+
@router.post("/evaluation/run/task", tags=["evaluation"])
|
|
177
|
+
async def _evaluate_task(rq: EvaluatorRunRequest, headers: Annotated[TaskHeader, Header()]) -> IdentifiableEntity:
|
|
178
|
+
return task_manager.create_task(lambda: _evaluate(rq), headers, queue="fast")
|
|
179
|
+
#endregion evaluate
|