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.
Files changed (51) hide show
  1. ws_bom_robot_app/config.py +30 -8
  2. ws_bom_robot_app/cron_manager.py +13 -12
  3. ws_bom_robot_app/llm/agent_context.py +1 -1
  4. ws_bom_robot_app/llm/agent_handler.py +11 -12
  5. ws_bom_robot_app/llm/agent_lcel.py +80 -18
  6. ws_bom_robot_app/llm/api.py +69 -7
  7. ws_bom_robot_app/llm/evaluator.py +319 -0
  8. ws_bom_robot_app/llm/main.py +51 -28
  9. ws_bom_robot_app/llm/models/api.py +40 -6
  10. ws_bom_robot_app/llm/nebuly_handler.py +18 -15
  11. ws_bom_robot_app/llm/providers/llm_manager.py +233 -75
  12. ws_bom_robot_app/llm/tools/tool_builder.py +4 -1
  13. ws_bom_robot_app/llm/tools/tool_manager.py +48 -22
  14. ws_bom_robot_app/llm/utils/chunker.py +6 -1
  15. ws_bom_robot_app/llm/utils/cleanup.py +81 -0
  16. ws_bom_robot_app/llm/utils/cms.py +60 -14
  17. ws_bom_robot_app/llm/utils/download.py +112 -8
  18. ws_bom_robot_app/llm/vector_store/db/base.py +50 -0
  19. ws_bom_robot_app/llm/vector_store/db/chroma.py +28 -8
  20. ws_bom_robot_app/llm/vector_store/db/faiss.py +35 -8
  21. ws_bom_robot_app/llm/vector_store/db/qdrant.py +29 -14
  22. ws_bom_robot_app/llm/vector_store/integration/api.py +216 -0
  23. ws_bom_robot_app/llm/vector_store/integration/azure.py +1 -1
  24. ws_bom_robot_app/llm/vector_store/integration/base.py +58 -15
  25. ws_bom_robot_app/llm/vector_store/integration/confluence.py +33 -5
  26. ws_bom_robot_app/llm/vector_store/integration/dropbox.py +1 -1
  27. ws_bom_robot_app/llm/vector_store/integration/gcs.py +1 -1
  28. ws_bom_robot_app/llm/vector_store/integration/github.py +22 -22
  29. ws_bom_robot_app/llm/vector_store/integration/googledrive.py +46 -17
  30. ws_bom_robot_app/llm/vector_store/integration/jira.py +93 -60
  31. ws_bom_robot_app/llm/vector_store/integration/manager.py +6 -2
  32. ws_bom_robot_app/llm/vector_store/integration/s3.py +1 -1
  33. ws_bom_robot_app/llm/vector_store/integration/sftp.py +1 -1
  34. ws_bom_robot_app/llm/vector_store/integration/sharepoint.py +7 -14
  35. ws_bom_robot_app/llm/vector_store/integration/shopify.py +143 -0
  36. ws_bom_robot_app/llm/vector_store/integration/sitemap.py +6 -1
  37. ws_bom_robot_app/llm/vector_store/integration/slack.py +3 -2
  38. ws_bom_robot_app/llm/vector_store/integration/thron.py +236 -0
  39. ws_bom_robot_app/llm/vector_store/loader/base.py +52 -8
  40. ws_bom_robot_app/llm/vector_store/loader/docling.py +71 -33
  41. ws_bom_robot_app/main.py +148 -146
  42. ws_bom_robot_app/subprocess_runner.py +106 -0
  43. ws_bom_robot_app/task_manager.py +204 -53
  44. ws_bom_robot_app/util.py +6 -0
  45. {ws_bom_robot_app-0.0.63.dist-info → ws_bom_robot_app-0.0.103.dist-info}/METADATA +158 -75
  46. ws_bom_robot_app-0.0.103.dist-info/RECORD +76 -0
  47. ws_bom_robot_app/llm/settings.py +0 -4
  48. ws_bom_robot_app/llm/utils/kb.py +0 -34
  49. ws_bom_robot_app-0.0.63.dist-info/RECORD +0 -72
  50. {ws_bom_robot_app-0.0.63.dist-info → ws_bom_robot_app-0.0.103.dist-info}/WHEEL +0 -0
  51. {ws_bom_robot_app-0.0.63.dist-info → ws_bom_robot_app-0.0.103.dist-info}/top_level.txt +0 -0
@@ -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 _get_number_of_workers() -> int:
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, arg in enumerate(sys.argv):
70
- if arg == "--workers" and i + 1 < len(sys.argv):
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 1
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 = _get_number_of_workers()
79
- is_multi_process: bool = _get_number_of_workers() > 1
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_
@@ -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.task_manager import task_manager
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:///.data/db/jobs.sqlite"):
26
- _log.info(f"Using persistent crob jobstore with database URL: {db_url}.")
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',task_manager.cleanup_task, interval=5 * 60),
60
- Job('cleanup-data',kb_cleanup_data_file, interval=180 * 60),
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 True or config.runtime_options().is_multi_process:
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.job_func()
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, HumanMessage, AIMessage
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
- return ChatPromptTemplate([
30
- ("system", system),
31
- MessagesPlaceholder(variable_name=self.memory_key),
32
- MessagesPlaceholder(variable_name="agent_scratchpad"),
33
- ])
34
-
35
- def __create_agent(self) -> AgentExecutor:
36
- agent: Any = (
37
- {
38
- "agent_scratchpad": lambda x: self.__llm.get_formatter(x["intermediate_steps"]),
39
- str(self.memory_key): lambda x: x[self.memory_key],
40
- }
41
- | RunnableLambda(self.__create_prompt)
42
- | self.__llm_with_tools
43
- | self.__llm.get_parser()
44
- )
45
- return AgentExecutor(agent=agent,tools=self.__tools,verbose=False)
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)
@@ -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 _stream_headers(rq: StreamRequest) -> Mapping[str, str]:
25
+ def _rs_stream_headers(rq: StreamRequest) -> Mapping[str, str]:
25
26
  return {
26
- "X-thread-id": rq.msg_id or str(uuid4()),
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=_stream_headers(rq))
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=_stream_headers(rq))
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