ws-bom-robot-app 0.0.40__py3-none-any.whl → 0.0.42__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.
@@ -1,5 +1,5 @@
1
1
  from asyncio import Queue
2
- import asyncio, json, logging, os, traceback
2
+ import asyncio, json, logging, os, traceback, re
3
3
  from fastapi import Request
4
4
  from langchain.callbacks.tracers import LangChainTracer
5
5
  from langchain_core.callbacks.base import AsyncCallbackHandler
@@ -28,6 +28,18 @@ async def invoke(rq: InvokeRequest) -> str:
28
28
  result: AIMessage = await processor.run_agent(_msg)
29
29
  return {"result": result.content}
30
30
 
31
+ def _parse_formatted_message(message: str) -> str:
32
+ try:
33
+ text_fragments = []
34
+ quoted_strings = re.findall(r'"([^"\\]*(?:\\.[^"\\]*)*)"', message)
35
+ for string in quoted_strings:
36
+ if not string.startswith(('threadId', 'type')) and len(string) > 1:
37
+ text_fragments.append(string)
38
+ result = ''.join(text_fragments)
39
+ result = result.replace('\\n', '\n')
40
+ except:
41
+ result = message
42
+ return result
31
43
  async def __stream(rq: StreamRequest, ctx: Request, queue: Queue,formatted: bool = True) -> None:
32
44
  await rq.initialize()
33
45
  #os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"
@@ -41,21 +53,33 @@ async def __stream(rq: StreamRequest, ctx: Request, queue: Queue,formatted: bool
41
53
 
42
54
  #CREATION OF CHAT HISTORY FOR AGENT
43
55
  for message in rq.messages:
44
- if message.role == "user":
45
- settings.chat_history.append(HumanMessage(content=message.content))
46
- elif message.role == "assistant":
47
- message_content = ""
48
- if formatted and '{\"type\":\"text\"' in message.content:
49
- try:
50
- json_msg = json.loads('[' + message.content[:-1] + ']')
51
- for msg in json_msg:
52
- if msg.get("content"):
53
- message_content += msg["content"]
54
- except:
55
- message_content = message.content
56
- else:
57
- message_content = message.content
58
- settings.chat_history.append(AIMessage(content=message_content))
56
+ if message.role in ["human","user"]:
57
+ settings.chat_history.append(HumanMessage(content=message.content))
58
+ elif message.role in ["ai","assistant"]:
59
+ message_content = ""
60
+ if formatted:
61
+ if '{\"type\":\"string\"' in message.content:
62
+ try:
63
+ json_msg = json.loads('[' + message.content[:-1] + ']')
64
+ for msg in json_msg:
65
+ if msg.get("content"):
66
+ message_content += msg["content"]
67
+ except:
68
+ message_content = _parse_formatted_message(message.content)
69
+ elif '{\"type\":\"text\"' in message.content:
70
+ try:
71
+ json_msg = json.loads('[' + message.content[:-1] + ']')
72
+ for msg in json_msg:
73
+ if msg.get("text"):
74
+ message_content += msg["text"]
75
+ except:
76
+ message_content = _parse_formatted_message(message.content)
77
+ else:
78
+ message_content = _parse_formatted_message(message.content)
79
+ else:
80
+ message_content = message.content
81
+ if message_content:
82
+ settings.chat_history.append(AIMessage(content=message_content))
59
83
 
60
84
  if rq.lang_chain_tracing:
61
85
  client = LangSmithClient(
@@ -44,7 +44,9 @@ class OpenAI(LlmInterface):
44
44
 
45
45
  def get_llm(self):
46
46
  from langchain_openai import ChatOpenAI
47
- chat = ChatOpenAI(api_key=self.config.api_key, model=self.config.model)
47
+ chat = ChatOpenAI(
48
+ api_key=self.config.api_key or os.getenv("OPENAI_API_KEY"),
49
+ model=self.config.model)
48
50
  if not any(self.config.model.startswith(prefix) for prefix in ["o1", "o3"]):
49
51
  chat.temperature = self.config.temperature
50
52
  chat.streaming = True
@@ -60,7 +62,7 @@ class DeepSeek(LlmInterface):
60
62
  def get_llm(self):
61
63
  from langchain_openai import ChatOpenAI
62
64
  return ChatOpenAI(
63
- api_key=self.config.api_key,
65
+ api_key=self.config.api_key or os.getenv("DEEPSEEK_API_KEY"),
64
66
  model=self.config.model,
65
67
  base_url="https://api.deepseek.com/v1",
66
68
  max_tokens=8192,
@@ -79,7 +81,7 @@ class Google(LlmInterface):
79
81
  from langchain_google_genai.chat_models import ChatGoogleGenerativeAI
80
82
  return ChatGoogleGenerativeAI(
81
83
  name="chat",
82
- api_key=self.config.api_key,
84
+ api_key=self.config.api_key or os.getenv("GOOGLE_API_KEY"),
83
85
  model=self.config.model,
84
86
  temperature=self.config.temperature,
85
87
  disable_streaming=False
@@ -131,7 +133,7 @@ class Anthropic(LlmInterface):
131
133
  def get_llm(self):
132
134
  from langchain_anthropic import ChatAnthropic
133
135
  return ChatAnthropic(
134
- api_key=self.config.api_key,
136
+ api_key=self.config.api_key or os.getenv("ANTHROPIC_API_KEY"),
135
137
  model=self.config.model,
136
138
  temperature=self.config.temperature,
137
139
  streaming=True,
@@ -156,7 +158,7 @@ class Groq(LlmInterface):
156
158
  def get_llm(self):
157
159
  from langchain_groq import ChatGroq
158
160
  return ChatGroq(
159
- api_key=self.config.api_key,
161
+ api_key=self.config.api_key or os.getenv("GROQ_API_KEY"),
160
162
  model=self.config.model,
161
163
  #max_tokens=8192,
162
164
  temperature=self.config.temperature,
@@ -1,14 +1,13 @@
1
- import asyncio
1
+ import asyncio, os
2
2
  from ws_bom_robot_app.llm.vector_store.integration.base import IntegrationStrategy
3
- from unstructured_ingest.interfaces import ProcessorConfig, ReadConfig
4
- from unstructured_ingest.connector.jira import SimpleJiraConfig, JiraAccessConfig
5
- from unstructured_ingest.runner import JiraRunner
6
3
  from langchain_core.documents import Document
7
4
  from ws_bom_robot_app.llm.vector_store.loader.base import Loader
8
5
  from pydantic import BaseModel, Field, AliasChoices
9
- from typing import Optional, Union
10
- import requests
11
- import unstructured_ingest.connector.jira
6
+ from typing import Any, Optional, Union
7
+ from unstructured_ingest.interfaces import ProcessorConfig, ReadConfig
8
+ from unstructured_ingest.connector.jira import SimpleJiraConfig, JiraAccessConfig, JiraSourceConnector, JiraIngestDoc, nested_object_to_field_getter, _get_id_fields_for_issue, _get_project_fields_for_issue
9
+ from unstructured_ingest.runner import JiraRunner
10
+
12
11
 
13
12
  class JiraParams(BaseModel):
14
13
  """
@@ -22,24 +21,20 @@ class JiraParams(BaseModel):
22
21
  boards (Optional[list[str]]): An optional list of board IDs to interact with. Defaults to None, e.g., ['1', '2'].
23
22
  issues (Optional[list[str]]): An optional list of issue keys or IDs to interact with. Defaults to None, e.g., ['SCRUM-1', 'PROJ1-1'].
24
23
  """
25
- url: str
26
- access_token: str = Field(validation_alias=AliasChoices("accessToken","access_token"))
27
- user_email: str = Field(validation_alias=AliasChoices("userEmail","user_email"))
24
+ url: str = Field(..., pattern=r'^https?:\/\/.+')
25
+ access_token: str = Field(..., validation_alias=AliasChoices("accessToken","access_token"), min_length=1)
26
+ user_email: str = Field(validation_alias=AliasChoices("userEmail","user_email"), min_length=1)
28
27
  projects: list[str]
29
28
  boards: Optional[list[str]] | None = None
30
29
  issues: Optional[list[str]] | None = None
31
- fieldsMappingUrl: Optional[str] | None = None
32
30
 
33
31
  class Jira(IntegrationStrategy):
34
- DEFAULT_C_SEP = " " * 5
35
- DEFAULT_R_SEP = "\n"
36
32
  def __init__(self, knowledgebase_path: str, data: dict[str, Union[str,int,list]]):
37
33
  super().__init__(knowledgebase_path, data)
38
34
  self.__data = JiraParams.model_validate(self.data)
39
35
  def working_subdirectory(self) -> str:
40
36
  return 'jira'
41
37
  def run(self) -> None:
42
- unstructured_ingest.connector.jira._get_dropdown_fields_for_issue = self._get_dropdown_fields_for_issue
43
38
  access_config = JiraAccessConfig(
44
39
  api_token=self.__data.access_token
45
40
  )
@@ -51,7 +46,8 @@ class Jira(IntegrationStrategy):
51
46
  boards=self.__data.boards,
52
47
  issues=self.__data.issues
53
48
  )
54
- runner = JiraRunner(
49
+ # runner override: waiting for v2 migration https://github.com/Unstructured-IO/unstructured-ingest/issues/106
50
+ runner = _JiraRunner(
55
51
  connector_config=config,
56
52
  processor_config=ProcessorConfig(reprocess=False,verbose=False,num_processes=2,raise_on_error=False),
57
53
  read_config=ReadConfig(download_dir=self.working_directory,re_download=True,preserve_downloads=True,download_only=True),
@@ -64,51 +60,59 @@ class Jira(IntegrationStrategy):
64
60
  await asyncio.sleep(1)
65
61
  return await Loader(self.working_directory).load()
66
62
 
67
- def _remap_custom_fields(self, field_list):
68
- auth = (self.__data.user_email, self.__data.access_token)
69
- response = requests.get(self.__data.fieldsMappingUrl, auth=auth)
70
-
71
- if response.status_code == 200:
72
- mapper: dict = response.json()
73
- remapped_field_list = {}
74
- for field_key, field_value in field_list.items():
75
- new_key = None
76
- for map_item in mapper:
77
- if field_key == map_item["id"]:
78
- # Usa il nome mappato come nuova chiave
79
- new_key = map_item["name"]
80
- break
81
-
82
- if new_key is None:
83
- new_key = field_key
84
63
 
85
- remapped_field_list[new_key] = field_value
64
+ # region override
65
+ class _JiraIngestDoc(JiraIngestDoc):
66
+ def _get_dropdown_custom_fields_for_issue(issue: dict, c_sep=" " * 5, r_sep="\n") -> str:
67
+ def _parse_value(value: Any) -> Any:
68
+ if isinstance(value, dict):
69
+ _candidate = ["displayName", "name", "value"]
70
+ for item in _candidate:
71
+ if item in value:
72
+ return value[item]
73
+ return value
74
+ def _remap_custom_fields(fields: dict):
75
+ remapped_fields = {}
76
+ for field_key, field_value in fields.items():
77
+ new_key = next((map_item["name"] for map_item in _JiraSourceConnector.CUSTOM_FIELDS if field_key == map_item["id"]), field_key)
78
+ if new_key != field_value:
79
+ remapped_fields[new_key] = field_value
80
+ return remapped_fields
81
+ filtered_fields = {key: _parse_value(value) for key, value in issue.items() if value is not None and type(value) not in [list]}
82
+ custom_fields =_remap_custom_fields(filtered_fields)
83
+ return (r_sep + c_sep ).join([f"{key}: {value}{r_sep}" for key, value in custom_fields.items()])
84
+ def __init__(self, *args, **kwargs):
85
+ super().__init__(*args, **kwargs)
86
+ _issue = self.issue
87
+ _nested: dict = nested_object_to_field_getter(_issue["fields"])
88
+ document = "\n\n\n".join(
89
+ [
90
+ _get_id_fields_for_issue(_issue),
91
+ _get_project_fields_for_issue(_nested),
92
+ _JiraIngestDoc._get_dropdown_custom_fields_for_issue(_nested)
93
+ ],
94
+ )
95
+ _full_filename = str(self.filename)
96
+ _file_extension = _full_filename.split(".")[-1]
97
+ _file_without_extension = _full_filename.replace(f".{_file_extension}","")
98
+ os.makedirs(os.path.dirname(_file_without_extension), exist_ok=True)
99
+ with open(f"{_file_without_extension}_extra.{_file_extension}", "w", encoding="utf8") as f:
100
+ f.write(document)
86
101
 
87
- return remapped_field_list
102
+ class _JiraSourceConnector(JiraSourceConnector):
103
+ CUSTOM_FIELDS: list | None = None
104
+ def __set_custom_fields(self) -> None:
105
+ _custom_fields = self.jira.get_all_custom_fields()
106
+ _JiraSourceConnector.CUSTOM_FIELDS = [{"id":item["id"],"name":item["name"]} for item in _custom_fields]
107
+ self._jira = None # fix serialization
108
+ def __init__(self, *args, **kwargs):
109
+ super().__init__(*args, **kwargs)
110
+ if not _JiraSourceConnector.CUSTOM_FIELDS:
111
+ self.__set_custom_fields()
112
+ def get_ingest_docs(self) -> list[_JiraIngestDoc]:
113
+ return [_JiraIngestDoc(**item.__dict__) for item in super().get_ingest_docs()]
88
114
 
89
- def _get_dropdown_fields_for_issue(self, issue, c_sep=DEFAULT_C_SEP, r_sep=DEFAULT_R_SEP):
90
- all_fields = {}
91
- for key, value in issue.items():
92
- if value is not None:
93
- if isinstance(value, list) and (len(value) > 0):
94
- all_fields[key] = value
95
- else:
96
- all_fields[key] = value
97
- mapped_fields = self._remap_custom_fields(all_fields)
98
- return f"""
99
- IssueType:{issue["issuetype"]["name"]}
100
- {r_sep}
101
- Status:{issue["status"]["name"]}
102
- {r_sep}
103
- Priority:{issue["priority"]}
104
- {r_sep}
105
- AssigneeID_Name:{issue["assignee"]["accountId"]}{c_sep}{issue["assignee"]["displayName"]}
106
- {r_sep}
107
- ReporterAdr_Name:{issue["reporter"]["emailAddress"]}{c_sep}{issue["reporter"]["displayName"]}
108
- {r_sep}
109
- Labels:{c_sep.join(issue["labels"])}
110
- {r_sep}
111
- Components:{c_sep.join([component["name"] for component in issue["components"]])}
112
- {r_sep}
113
- {(r_sep + c_sep ).join([f"{key}:{value}{r_sep}" for key, value in mapped_fields.items()])}
114
- """
115
+ class _JiraRunner(JiraRunner):
116
+ def get_source_connector_cls(self):
117
+ return _JiraSourceConnector
118
+ # endregion
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: ws_bom_robot_app
3
- Version: 0.0.40
3
+ Version: 0.0.42
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
@@ -11,14 +11,14 @@ ws_bom_robot_app/llm/agent_handler.py,sha256=4zdpSf5iVLxMZ90c_vUl_k-O9SF6u_h7GOB
11
11
  ws_bom_robot_app/llm/agent_lcel.py,sha256=Yt-hbKarLktv5BBiTp9OHYRdwOTRu5ogyTrYapFdiTU,2389
12
12
  ws_bom_robot_app/llm/api.py,sha256=UaD1oJyAOe7ASoXxPNJcth3kDuWcjk1xqUNEjuPWbR4,3759
13
13
  ws_bom_robot_app/llm/defaut_prompt.py,sha256=LlCd_nSMkMmHESfiiiQYfnJyB6Pp-LSs4CEKdYW4vFk,1106
14
- ws_bom_robot_app/llm/main.py,sha256=fVXyS9TOu22ZC7M8o2mRCya9vTmMFf5jRgs9V0K_4cw,4189
14
+ ws_bom_robot_app/llm/main.py,sha256=YcfenQkBFpXGgFdU2NgHutBs6saPzJzcFMmJ6nwdg0Q,5084
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
17
  ws_bom_robot_app/llm/models/api.py,sha256=mLbPG7jHh1EjgQG-xpBhEgiTIHpK35HZ51obgqQSfq4,8890
18
18
  ws_bom_robot_app/llm/models/base.py,sha256=1TqxuTK3rjJEALn7lvgoen_1ba3R2brAgGx6EDTtDZo,152
19
19
  ws_bom_robot_app/llm/models/kb.py,sha256=oVSw6_dmNxikAHrPqcfxDXz9M0ezLIYuxpgvzfs_Now,9514
20
20
  ws_bom_robot_app/llm/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
- ws_bom_robot_app/llm/providers/llm_manager.py,sha256=GUi5aHQX_4A4pRgv-XEBH42wGPBYW2HUeQeiU_j044E,7924
21
+ ws_bom_robot_app/llm/providers/llm_manager.py,sha256=XQpTmJ-ChituFyKMv_UOPwNr1aJgihAlsuq0kw2YLVo,8109
22
22
  ws_bom_robot_app/llm/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
23
  ws_bom_robot_app/llm/tools/tool_builder.py,sha256=OaA0jReNUpjfe7c8TVLM86acQ4w0cQaR3NE22hGKJb0,1165
24
24
  ws_bom_robot_app/llm/tools/tool_manager.py,sha256=RZcJVPyWT9D3HUxSO1d5kSfTQtJB2CG5hocuFa01AzY,5816
@@ -49,7 +49,7 @@ ws_bom_robot_app/llm/vector_store/integration/dropbox.py,sha256=yhGvHTN0TEpUfhdv
49
49
  ws_bom_robot_app/llm/vector_store/integration/gcs.py,sha256=fFDVDUR6eNB7FVTzDSEpMHFEWMgG16GLnpSf_mqGDdE,3184
50
50
  ws_bom_robot_app/llm/vector_store/integration/github.py,sha256=18PO30AZcgTn6PHhid3MwImVAdmKBNkr0kmAPgOetGw,2663
51
51
  ws_bom_robot_app/llm/vector_store/integration/googledrive.py,sha256=R6hr8iEgrR3QMOzIj5jY6w1x8pZ1LGdh4xM_q7g_ttc,3738
52
- ws_bom_robot_app/llm/vector_store/integration/jira.py,sha256=uiNkCOSqxOgm3AoO6k5TN_bsYXiO60I_wF0i_z8Yp8Y,4874
52
+ ws_bom_robot_app/llm/vector_store/integration/jira.py,sha256=Sf6nPongo53vG4jhlzFQZj3foNzvUvAZFr7pXAJfZvM,5795
53
53
  ws_bom_robot_app/llm/vector_store/integration/manager.py,sha256=5Fl3XML6f1wmgraigpUwIFIXh7QFPX0RI0YFgFxBAvg,1700
54
54
  ws_bom_robot_app/llm/vector_store/integration/s3.py,sha256=3kh-VmH84IW7DdSLvOk6td1VBJ9aohlVJsk5F3cYj0U,3320
55
55
  ws_bom_robot_app/llm/vector_store/integration/sftp.py,sha256=WNzjjS1EUykgFB-8e7QkecSa1r1jTJqKyGzR25uJCtM,2848
@@ -60,7 +60,7 @@ ws_bom_robot_app/llm/vector_store/loader/__init__.py,sha256=47DEQpj8HBSa-_TImW-5
60
60
  ws_bom_robot_app/llm/vector_store/loader/base.py,sha256=L_ugekNuAq0N9O-24wtlHSNHkqSeD-KsJrfGt_FX9Oc,5340
61
61
  ws_bom_robot_app/llm/vector_store/loader/docling.py,sha256=yP0zgXLeFAlByaYuj-6cYariuknckrFds0dxdRcnVz8,3456
62
62
  ws_bom_robot_app/llm/vector_store/loader/json_loader.py,sha256=qo9ejRZyKv_k6jnGgXnu1W5uqsMMtgqK_uvPpZQ0p74,833
63
- ws_bom_robot_app-0.0.40.dist-info/METADATA,sha256=0kGu7SHsL99RrZAA_ZBAYoa8Ao_CSy4Np9CPZdGGSyk,8348
64
- ws_bom_robot_app-0.0.40.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
65
- ws_bom_robot_app-0.0.40.dist-info/top_level.txt,sha256=Yl0akyHVbynsBX_N7wx3H3ZTkcMLjYyLJs5zBMDAKcM,17
66
- ws_bom_robot_app-0.0.40.dist-info/RECORD,,
63
+ ws_bom_robot_app-0.0.42.dist-info/METADATA,sha256=oqyzo0YlOL6buOYsrDYoRSj0-SkyD3hJU0uwJ5gtcQA,8348
64
+ ws_bom_robot_app-0.0.42.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
65
+ ws_bom_robot_app-0.0.42.dist-info/top_level.txt,sha256=Yl0akyHVbynsBX_N7wx3H3ZTkcMLjYyLJs5zBMDAKcM,17
66
+ ws_bom_robot_app-0.0.42.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: setuptools (75.8.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5