ws-bom-robot-app 0.0.37__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 +35 -7
- ws_bom_robot_app/cron_manager.py +15 -14
- ws_bom_robot_app/llm/agent_context.py +26 -0
- ws_bom_robot_app/llm/agent_description.py +123 -123
- ws_bom_robot_app/llm/agent_handler.py +176 -180
- ws_bom_robot_app/llm/agent_lcel.py +107 -54
- ws_bom_robot_app/llm/api.py +100 -7
- ws_bom_robot_app/llm/defaut_prompt.py +15 -15
- ws_bom_robot_app/llm/evaluator.py +319 -0
- ws_bom_robot_app/llm/feedbacks/__init__.py +0 -0
- ws_bom_robot_app/llm/feedbacks/feedback_manager.py +66 -0
- ws_bom_robot_app/llm/main.py +159 -110
- ws_bom_robot_app/llm/models/api.py +70 -5
- ws_bom_robot_app/llm/models/feedback.py +30 -0
- ws_bom_robot_app/llm/nebuly_handler.py +185 -0
- ws_bom_robot_app/llm/providers/llm_manager.py +244 -80
- ws_bom_robot_app/llm/tools/models/main.py +8 -0
- ws_bom_robot_app/llm/tools/tool_builder.py +68 -23
- ws_bom_robot_app/llm/tools/tool_manager.py +343 -133
- ws_bom_robot_app/llm/tools/utils.py +41 -25
- ws_bom_robot_app/llm/utils/agent.py +34 -0
- 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 +123 -0
- ws_bom_robot_app/llm/utils/download.py +183 -79
- ws_bom_robot_app/llm/utils/print.py +29 -29
- ws_bom_robot_app/llm/vector_store/db/__init__.py +0 -0
- ws_bom_robot_app/llm/vector_store/db/base.py +193 -0
- ws_bom_robot_app/llm/vector_store/db/chroma.py +97 -0
- ws_bom_robot_app/llm/vector_store/db/faiss.py +91 -0
- ws_bom_robot_app/llm/vector_store/db/manager.py +15 -0
- ws_bom_robot_app/llm/vector_store/db/qdrant.py +73 -0
- ws_bom_robot_app/llm/vector_store/generator.py +137 -137
- 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 +41 -11
- 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 +112 -75
- 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 +9 -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/llm/vector_store/loader/json_loader.py +25 -25
- ws_bom_robot_app/main.py +148 -146
- ws_bom_robot_app/subprocess_runner.py +106 -0
- ws_bom_robot_app/task_manager.py +207 -54
- ws_bom_robot_app/util.py +65 -20
- ws_bom_robot_app-0.0.103.dist-info/METADATA +364 -0
- ws_bom_robot_app-0.0.103.dist-info/RECORD +76 -0
- {ws_bom_robot_app-0.0.37.dist-info → ws_bom_robot_app-0.0.103.dist-info}/WHEEL +1 -1
- ws_bom_robot_app/llm/settings.py +0 -4
- ws_bom_robot_app/llm/utils/agent_utils.py +0 -17
- ws_bom_robot_app/llm/utils/kb.py +0 -34
- ws_bom_robot_app-0.0.37.dist-info/METADATA +0 -277
- ws_bom_robot_app-0.0.37.dist-info/RECORD +0 -60
- {ws_bom_robot_app-0.0.37.dist-info → ws_bom_robot_app-0.0.103.dist-info}/top_level.txt +0 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import json
|
|
2
1
|
from typing import Optional
|
|
2
|
+
from urllib.parse import urlparse
|
|
3
3
|
from langchain_core.embeddings import Embeddings
|
|
4
4
|
from langchain_core.language_models import BaseChatModel
|
|
5
5
|
from pydantic import BaseModel, ConfigDict, Field
|
|
6
6
|
import os
|
|
7
|
+
from ws_bom_robot_app.llm.utils.download import Base64File
|
|
7
8
|
|
|
8
9
|
class LlmConfig(BaseModel):
|
|
9
10
|
api_url: Optional[str] = None
|
|
@@ -36,6 +37,76 @@ class LlmInterface:
|
|
|
36
37
|
def get_parser(self):
|
|
37
38
|
from langchain.agents.output_parsers.openai_tools import OpenAIToolsAgentOutputParser
|
|
38
39
|
return OpenAIToolsAgentOutputParser()
|
|
40
|
+
async def _format_multimodal_image_message(self, message: dict) -> dict:
|
|
41
|
+
return {
|
|
42
|
+
"type": "image_url",
|
|
43
|
+
"image_url": {
|
|
44
|
+
"url": message.get("url")
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async def _format_multimodal_file_message(self, message: dict, file: Base64File = None) -> dict:
|
|
48
|
+
_file = file or await Base64File.from_url(message.get("url"))
|
|
49
|
+
return {"type": "text", "text": f"Here's a file attachment named `{_file.name}` of type `{_file.mime_type}` in base64: `{_file.base64_content}`"}
|
|
50
|
+
async def format_multimodal_content(self, content: list) -> list:
|
|
51
|
+
_content = []
|
|
52
|
+
for message in content:
|
|
53
|
+
if isinstance(message, dict):
|
|
54
|
+
if message.get("type") == "image" and "url" in message:
|
|
55
|
+
_content.append(await self._format_multimodal_image_message(message))
|
|
56
|
+
elif message.get("type") == "file" and "url" in message:
|
|
57
|
+
_content.append(await self._format_multimodal_file_message(message))
|
|
58
|
+
else:
|
|
59
|
+
# pass through text or other formats unchanged
|
|
60
|
+
_content.append(message)
|
|
61
|
+
else:
|
|
62
|
+
_content.append(message)
|
|
63
|
+
return _content
|
|
64
|
+
|
|
65
|
+
class Anthropic(LlmInterface):
|
|
66
|
+
def get_llm(self):
|
|
67
|
+
from langchain_anthropic import ChatAnthropic
|
|
68
|
+
return ChatAnthropic(
|
|
69
|
+
api_key=self.config.api_key or os.getenv("ANTHROPIC_API_KEY"),
|
|
70
|
+
model=self.config.model,
|
|
71
|
+
temperature=self.config.temperature,
|
|
72
|
+
max_tokens=8192,
|
|
73
|
+
streaming=True,
|
|
74
|
+
#betas=["files-api-2025-04-14"] #https://docs.anthropic.com/en/docs/build-with-claude/files
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
"""
|
|
78
|
+
def get_embeddings(self):
|
|
79
|
+
from langchain_voyageai import VoyageAIEmbeddings
|
|
80
|
+
return VoyageAIEmbeddings(
|
|
81
|
+
api_key=self.config.embedding_api_key, #voyage api key
|
|
82
|
+
model="voyage-3")
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
def get_models(self):
|
|
86
|
+
import anthropic
|
|
87
|
+
client = anthropic.Client(api_key=self.config.api_key or os.getenv("ANTHROPIC_API_KEY"))
|
|
88
|
+
response = client.models.list()
|
|
89
|
+
return response.data
|
|
90
|
+
|
|
91
|
+
"""
|
|
92
|
+
async def _format_multimodal_image_message(self, message: dict) -> dict:
|
|
93
|
+
file = await Base64File.from_url(message.get("url"))
|
|
94
|
+
return { "type": "image_url", "image_url": { "url": file.base64_url }}
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
#https://python.langchain.com/docs/integrations/chat/anthropic/
|
|
98
|
+
#https://python.langchain.com/docs/how_to/multimodal_inputs/
|
|
99
|
+
async def _format_multimodal_file_message(self, message: dict, file: Base64File = None) -> dict:
|
|
100
|
+
_url = str(message.get("url", ""))
|
|
101
|
+
_url_lower = _url.lower()
|
|
102
|
+
if _url_lower.startswith("http") and any(urlparse(_url_lower).path.endswith(ext) for ext in [".pdf"]):
|
|
103
|
+
return {"type": "file", "source_type": "url", "url": _url}
|
|
104
|
+
else:
|
|
105
|
+
_file = file or await Base64File.from_url(_url)
|
|
106
|
+
if _file.extension in ["pdf"]:
|
|
107
|
+
return {"type": "document", "source": {"type": "base64", "media_type": _file.mime_type, "data": _file.base64_content}}
|
|
108
|
+
else:
|
|
109
|
+
return await super()._format_multimodal_file_message(message, _file)
|
|
39
110
|
|
|
40
111
|
class OpenAI(LlmInterface):
|
|
41
112
|
def __init__(self, config: LlmConfig):
|
|
@@ -44,8 +115,12 @@ class OpenAI(LlmInterface):
|
|
|
44
115
|
|
|
45
116
|
def get_llm(self):
|
|
46
117
|
from langchain_openai import ChatOpenAI
|
|
47
|
-
chat = ChatOpenAI(
|
|
48
|
-
|
|
118
|
+
chat = ChatOpenAI(
|
|
119
|
+
api_key=self.config.api_key or os.getenv("OPENAI_API_KEY"),
|
|
120
|
+
model=self.config.model,
|
|
121
|
+
streaming=True
|
|
122
|
+
)
|
|
123
|
+
if not (any(self.config.model.startswith(prefix) for prefix in ["gpt-5", "o1", "o3"]) or "search" in self.config.model):
|
|
49
124
|
chat.temperature = self.config.temperature
|
|
50
125
|
chat.streaming = True
|
|
51
126
|
return chat
|
|
@@ -56,54 +131,76 @@ class OpenAI(LlmInterface):
|
|
|
56
131
|
response = openai.models.list()
|
|
57
132
|
return response.data
|
|
58
133
|
|
|
134
|
+
async def _format_multimodal_file_message(self, message: dict, file: Base64File = None) -> dict:
|
|
135
|
+
_file = file or await Base64File.from_url(message.get("url"))
|
|
136
|
+
if _file.extension in ["pdf"]:
|
|
137
|
+
return {"type": "file", "file": { "source_type": "base64", "file_data": _file.base64_url, "mime_type": _file.mime_type, "filename": _file.name}}
|
|
138
|
+
else:
|
|
139
|
+
return await super()._format_multimodal_file_message(message, _file)
|
|
140
|
+
|
|
59
141
|
class DeepSeek(LlmInterface):
|
|
60
142
|
def get_llm(self):
|
|
61
143
|
from langchain_openai import ChatOpenAI
|
|
62
144
|
return ChatOpenAI(
|
|
63
|
-
api_key=self.config.api_key,
|
|
145
|
+
api_key=self.config.api_key or os.getenv("DEEPSEEK_API_KEY"),
|
|
64
146
|
model=self.config.model,
|
|
65
|
-
base_url="https://api.deepseek.com
|
|
147
|
+
base_url="https://api.deepseek.com",
|
|
66
148
|
max_tokens=8192,
|
|
67
149
|
temperature=self.config.temperature,
|
|
68
|
-
streaming=True
|
|
150
|
+
streaming=True
|
|
69
151
|
)
|
|
70
152
|
|
|
71
153
|
def get_models(self):
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
154
|
+
import openai
|
|
155
|
+
openai.api_key = self.config.api_key or os.getenv("DEEPSEEK_API_KEY")
|
|
156
|
+
openai.base_url = "https://api.deepseek.com"
|
|
157
|
+
response = openai.models.list()
|
|
158
|
+
return response.data
|
|
159
|
+
|
|
160
|
+
async def _format_multimodal_image_message(self, message: dict) -> dict:
|
|
161
|
+
print(f"{DeepSeek.__name__} does not support image messages")
|
|
162
|
+
return None
|
|
163
|
+
|
|
164
|
+
async def _format_multimodal_file_message(self, message: dict, file: Base64File = None) -> dict:
|
|
165
|
+
print(f"{DeepSeek.__name__} does not support file messages")
|
|
166
|
+
return None
|
|
76
167
|
|
|
77
168
|
class Google(LlmInterface):
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
"
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
169
|
+
def get_llm(self):
|
|
170
|
+
from langchain_google_genai.chat_models import ChatGoogleGenerativeAI
|
|
171
|
+
return ChatGoogleGenerativeAI(
|
|
172
|
+
model=self.config.model,
|
|
173
|
+
google_api_key=self.config.api_key or os.getenv("GOOGLE_API_KEY"),
|
|
174
|
+
temperature=self.config.temperature,
|
|
175
|
+
disable_streaming=False,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
def get_embeddings(self):
|
|
179
|
+
from langchain_google_genai.embeddings import GoogleGenerativeAIEmbeddings
|
|
180
|
+
return GoogleGenerativeAIEmbeddings(
|
|
181
|
+
google_api_key=self.config.api_key or os.getenv("GOOGLE_API_KEY"),
|
|
182
|
+
model="models/gemini-embedding-001")
|
|
183
|
+
|
|
184
|
+
def get_models(self):
|
|
185
|
+
import google.generativeai as genai
|
|
186
|
+
genai.configure(api_key=self.config.api_key or os.getenv("GOOGLE_API_KEY"))
|
|
187
|
+
response = genai.list_models()
|
|
188
|
+
return [{
|
|
189
|
+
"id": model.name,
|
|
190
|
+
"name": model.display_name,
|
|
191
|
+
"description": model.description,
|
|
192
|
+
"input_token_limit": model.input_token_limit,
|
|
193
|
+
"output_token_limit": model.output_token_limit
|
|
194
|
+
} for model in response if "gemini" in model.name.lower()]
|
|
195
|
+
|
|
196
|
+
async def _format_multimodal_file_message(self, message: dict, file: Base64File = None) -> dict:
|
|
197
|
+
_file = file or await Base64File.from_url(message.get("url"))
|
|
198
|
+
if _file.extension in ["pdf", "csv"]:
|
|
199
|
+
return {"type": "media", "mime_type": _file.mime_type, "data": _file.base64_content }
|
|
200
|
+
else:
|
|
201
|
+
return await super()._format_multimodal_file_message(message, _file)
|
|
202
|
+
|
|
203
|
+
class GoogleVertex(LlmInterface):
|
|
107
204
|
def get_llm(self):
|
|
108
205
|
from langchain_google_vertexai import ChatVertexAI
|
|
109
206
|
return ChatVertexAI(
|
|
@@ -111,52 +208,43 @@ class Gvertex(LlmInterface):
|
|
|
111
208
|
temperature=self.config.temperature
|
|
112
209
|
)
|
|
113
210
|
def get_embeddings(self):
|
|
114
|
-
from langchain_google_vertexai import VertexAIEmbeddings
|
|
115
|
-
|
|
211
|
+
from langchain_google_vertexai.embeddings import VertexAIEmbeddings
|
|
212
|
+
embeddings = VertexAIEmbeddings(model_name="gemini-embedding-001")
|
|
213
|
+
return embeddings
|
|
116
214
|
def get_models(self):
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
215
|
+
_models = [
|
|
216
|
+
{"id":"gemini-2.5-pro"},
|
|
217
|
+
{"id":"gemini-2.5-flash"},
|
|
218
|
+
{"id":"gemini-2.0-flash"},
|
|
219
|
+
{"id":"gemini-2.0-flash-lite"}
|
|
220
|
+
]
|
|
221
|
+
try:
|
|
222
|
+
from google.cloud import aiplatform
|
|
223
|
+
aiplatform.init()
|
|
224
|
+
_list = aiplatform.Model.list()
|
|
225
|
+
if _list:
|
|
226
|
+
_models = list([{"id": model.name} for model in _list])
|
|
120
227
|
# removed due issue: https://github.com/langchain-ai/langchain-google/issues/733
|
|
121
228
|
# Message type "google.cloud.aiplatform.v1beta1.GenerateContentResponse" has no field named "createTime" at "GenerateContentResponse". Available Fields(except extensions): "['candidates', 'modelVersion', 'promptFeedback', 'usageMetadata']"
|
|
229
|
+
except Exception as e:
|
|
230
|
+
print(f"Error fetching models from Gvertex: {e}")
|
|
231
|
+
# fallback to hardcoded models
|
|
232
|
+
#see https://cloud.google.com/vertex-ai/generative-ai/docs/learn/locations#united-states for available models
|
|
233
|
+
finally:
|
|
234
|
+
return _models
|
|
122
235
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
class Anthropic(LlmInterface):
|
|
131
|
-
def get_llm(self):
|
|
132
|
-
from langchain_anthropic import ChatAnthropic
|
|
133
|
-
return ChatAnthropic(
|
|
134
|
-
api_key=self.config.api_key,
|
|
135
|
-
model=self.config.model,
|
|
136
|
-
temperature=self.config.temperature,
|
|
137
|
-
streaming=True,
|
|
138
|
-
stream_usage=False
|
|
139
|
-
)
|
|
140
|
-
|
|
141
|
-
"""
|
|
142
|
-
def get_embeddings(self):
|
|
143
|
-
from langchain_voyageai import VoyageAIEmbeddings
|
|
144
|
-
return VoyageAIEmbeddings(
|
|
145
|
-
api_key=self.config.embedding_api_key, #voyage api key
|
|
146
|
-
model="voyage-3")
|
|
147
|
-
"""
|
|
148
|
-
|
|
149
|
-
def get_models(self):
|
|
150
|
-
import anthropic
|
|
151
|
-
client = anthropic.Client(api_key=self.config.api_key or os.getenv("ANTHROPIC_API_KEY"))
|
|
152
|
-
response = client.models.list()
|
|
153
|
-
return response.data
|
|
236
|
+
async def _format_multimodal_file_message(self, message: dict, file: Base64File = None) -> dict:
|
|
237
|
+
_file = file or await Base64File.from_url(message.get("url"))
|
|
238
|
+
if _file.extension in ["pdf", "csv"]:
|
|
239
|
+
return {"type": "media", "mime_type": _file.mime_type, "data": _file.base64_content }
|
|
240
|
+
else:
|
|
241
|
+
return await super()._format_multimodal_file_message(message, _file)
|
|
154
242
|
|
|
155
243
|
class Groq(LlmInterface):
|
|
156
244
|
def get_llm(self):
|
|
157
245
|
from langchain_groq import ChatGroq
|
|
158
246
|
return ChatGroq(
|
|
159
|
-
api_key=self.config.api_key,
|
|
247
|
+
api_key=self.config.api_key or os.getenv("GROQ_API_KEY"),
|
|
160
248
|
model=self.config.model,
|
|
161
249
|
#max_tokens=8192,
|
|
162
250
|
temperature=self.config.temperature,
|
|
@@ -173,10 +261,74 @@ class Groq(LlmInterface):
|
|
|
173
261
|
response = requests.get(url, headers=headers)
|
|
174
262
|
return response.json().get("data", [])
|
|
175
263
|
|
|
264
|
+
class IBM(LlmInterface):
|
|
265
|
+
def __init__(self, config: LlmConfig):
|
|
266
|
+
from ibm_watsonx_ai import APIClient,Credentials
|
|
267
|
+
super().__init__(config)
|
|
268
|
+
self.__base_url = self.config.api_url or os.getenv("WATSONX_URL") or "https://us-south.ml.cloud.ibm.com"
|
|
269
|
+
self.__api_key = self.config.api_key or os.getenv("WATSONX_APIKEY")
|
|
270
|
+
self.__client = APIClient(
|
|
271
|
+
credentials=Credentials(url=self.__base_url,api_key=self.__api_key),
|
|
272
|
+
project_id=os.getenv("WATSONX_PROJECTID") or "default"
|
|
273
|
+
)
|
|
274
|
+
def get_llm(self):
|
|
275
|
+
from langchain_ibm import ChatWatsonx
|
|
276
|
+
return ChatWatsonx(
|
|
277
|
+
model_id=self.config.model,
|
|
278
|
+
watsonx_client=self.__client
|
|
279
|
+
)
|
|
280
|
+
def get_models(self):
|
|
281
|
+
import requests
|
|
282
|
+
from datetime import date
|
|
283
|
+
try:
|
|
284
|
+
# https://cloud.ibm.com/apidocs/watsonx-ai#list-foundation-model-specs
|
|
285
|
+
today = date.today().strftime("%Y-%m-%d")
|
|
286
|
+
url = f"{self.__base_url}/ml/v1/foundation_model_specs?version={today}&filters=task_generation,task_summarization:and"
|
|
287
|
+
headers = {
|
|
288
|
+
"Authorization": f"Bearer {self.__api_key}",
|
|
289
|
+
"Content-Type": "application/json"
|
|
290
|
+
}
|
|
291
|
+
response = requests.get(url, headers=headers)
|
|
292
|
+
models = response.json().get("resources", [])
|
|
293
|
+
return [{
|
|
294
|
+
"id": model['model_id'],
|
|
295
|
+
"provider": model['provider'],
|
|
296
|
+
"tasks": model['task_ids'],
|
|
297
|
+
"limits": model.get('model_limits', {}),
|
|
298
|
+
} for model in models]
|
|
299
|
+
except Exception as e:
|
|
300
|
+
print(f"Error fetching models from IBM WatsonX: {e}")
|
|
301
|
+
# https://www.ibm.com/products/watsonx-ai/foundation-models
|
|
302
|
+
return [
|
|
303
|
+
{"id":"ibm/granite-13b-instruct-v2"},
|
|
304
|
+
{"id":"ibm/granite-3-2b-instruct"},
|
|
305
|
+
{"id":"ibm/granite-3-8b-instruct"},
|
|
306
|
+
{"id":"meta-llama/llama-2-13b-chat"},
|
|
307
|
+
{"id":"meta-llama/llama-3-3-70b-instruct"},
|
|
308
|
+
{"id":"meta-llama/llama-4-maverick-17b-128e-instruct-fp8"},
|
|
309
|
+
{"id":"mistralai/mistral-large"},
|
|
310
|
+
{"id":"mistralai/mixtral-8x7b-instruct-v01"},
|
|
311
|
+
{"id":"mistralai/pixtral-12b"}
|
|
312
|
+
]
|
|
313
|
+
|
|
314
|
+
def get_embeddings(self):
|
|
315
|
+
from langchain_ibm import WatsonxEmbeddings
|
|
316
|
+
from ibm_watsonx_ai.metanames import EmbedTextParamsMetaNames
|
|
317
|
+
# https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/fm-models-embed.html?context=wx&audience=wdp#embed
|
|
318
|
+
embed_params = {
|
|
319
|
+
EmbedTextParamsMetaNames.TRUNCATE_INPUT_TOKENS: 512,
|
|
320
|
+
#EmbedTextParamsMetaNames.RETURN_OPTIONS: {"input_text": True},
|
|
321
|
+
}
|
|
322
|
+
return WatsonxEmbeddings(
|
|
323
|
+
model_id="ibm/granite-embedding-107m-multilingual", #https://www.ibm.com/products/watsonx-ai/foundation-models
|
|
324
|
+
watsonx_client=self.__client,
|
|
325
|
+
params=embed_params
|
|
326
|
+
)
|
|
327
|
+
|
|
176
328
|
class Ollama(LlmInterface):
|
|
177
329
|
def __init__(self, config: LlmConfig):
|
|
178
330
|
super().__init__(config)
|
|
179
|
-
self.__base_url = self.config.api_url or os.getenv("OLLAMA_API_URL")
|
|
331
|
+
self.__base_url = self.config.api_url or os.getenv("OLLAMA_API_URL") or "http://localhost:11434"
|
|
180
332
|
def get_llm(self):
|
|
181
333
|
from langchain_ollama.chat_models import ChatOllama
|
|
182
334
|
return ChatOllama(
|
|
@@ -189,7 +341,7 @@ class Ollama(LlmInterface):
|
|
|
189
341
|
from langchain_ollama.embeddings import OllamaEmbeddings
|
|
190
342
|
return OllamaEmbeddings(
|
|
191
343
|
base_url=self.__base_url,
|
|
192
|
-
model="
|
|
344
|
+
model="mxbai-embed-large" #nomic-embed-text
|
|
193
345
|
)
|
|
194
346
|
def get_models(self):
|
|
195
347
|
import requests
|
|
@@ -206,15 +358,27 @@ class Ollama(LlmInterface):
|
|
|
206
358
|
"details": model['details']
|
|
207
359
|
} for model in models]
|
|
208
360
|
|
|
361
|
+
async def _format_multimodal_image_message(self, message: dict) -> dict:
|
|
362
|
+
file = await Base64File.from_url(message.get("url"))
|
|
363
|
+
return { "type": "image_url", "image_url": { "url": file.base64_url }}
|
|
364
|
+
|
|
209
365
|
class LlmManager:
|
|
366
|
+
"""
|
|
367
|
+
Expose the available LLM providers.
|
|
368
|
+
Names are aligned with the LangChain documentation:
|
|
369
|
+
https://python.langchain.com/api_reference/langchain/chat_models/langchain.chat_models.base.init_chat_model.html
|
|
370
|
+
"""
|
|
210
371
|
|
|
211
372
|
#class variables (static)
|
|
212
373
|
_list: dict[str,LlmInterface] = {
|
|
213
374
|
"anthropic": Anthropic,
|
|
214
375
|
"deepseek": DeepSeek,
|
|
215
|
-
"google": Google,
|
|
216
|
-
"
|
|
376
|
+
"google": Google, #deprecated
|
|
377
|
+
"google_genai": Google,
|
|
378
|
+
"gvertex": GoogleVertex,#deprecated
|
|
379
|
+
"google_vertexai": GoogleVertex,
|
|
217
380
|
"groq": Groq,
|
|
381
|
+
"ibm": IBM,
|
|
218
382
|
"openai": OpenAI,
|
|
219
383
|
"ollama": Ollama
|
|
220
384
|
}
|
|
@@ -7,3 +7,11 @@ class DocumentRetrieverInput(BaseModel):
|
|
|
7
7
|
class ImageGeneratorInput(BaseModel):
|
|
8
8
|
query: str = Field(description="description of the image to generate.")
|
|
9
9
|
language: str = Field(description="Language of the query. Default is 'it'", default="it")
|
|
10
|
+
class LlmChainInput(BaseModel):
|
|
11
|
+
input: str = Field(description="Input to the LLM chain")
|
|
12
|
+
class SearchOnlineInput(BaseModel):
|
|
13
|
+
query: str = Field(description="The search query string")
|
|
14
|
+
class EmailSenderInput(BaseModel):
|
|
15
|
+
email_subject: str = Field(description="The subject of the email to send")
|
|
16
|
+
body: str = Field(description="The body of the email to send")
|
|
17
|
+
to_email: str = Field(description="The recipient email address")
|
|
@@ -1,23 +1,68 @@
|
|
|
1
|
-
|
|
2
|
-
from
|
|
3
|
-
from
|
|
4
|
-
from ws_bom_robot_app.llm.
|
|
5
|
-
from ws_bom_robot_app.llm.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
1
|
+
import asyncio
|
|
2
|
+
from asyncio import Queue
|
|
3
|
+
from langchain.tools import StructuredTool
|
|
4
|
+
from ws_bom_robot_app.llm.models.api import LlmAppTool
|
|
5
|
+
from ws_bom_robot_app.llm.tools.tool_manager import ToolManager
|
|
6
|
+
from ws_bom_robot_app.llm.providers.llm_manager import LlmInterface
|
|
7
|
+
|
|
8
|
+
async def __process_proxy_tool(proxy_tool: LlmAppTool) -> LlmAppTool | None:
|
|
9
|
+
import os
|
|
10
|
+
from ws_bom_robot_app.llm.utils.cms import CmsApp, get_app_by_id
|
|
11
|
+
from ws_bom_robot_app.config import config
|
|
12
|
+
try:
|
|
13
|
+
secrets = proxy_tool.secrets_to_dict()
|
|
14
|
+
app_id = secrets.get("appId")
|
|
15
|
+
if not app_id:
|
|
16
|
+
raise ValueError("Tool configuration is invalid. 'appId' is required.")
|
|
17
|
+
app: CmsApp = await get_app_by_id(app_id)
|
|
18
|
+
if not app:
|
|
19
|
+
raise ValueError(f"App with id {app_id} not found.")
|
|
20
|
+
tool_id = secrets.get("toolId")
|
|
21
|
+
tool = next((t for t in app.rq.app_tools if app.rq.app_tools and t.id == tool_id), None)
|
|
22
|
+
if not tool:
|
|
23
|
+
raise ValueError(f"Tool with function_id {tool_id} not found in app {app.name}.")
|
|
24
|
+
#override derived tool with proxy tool props
|
|
25
|
+
tool.name = proxy_tool.name if proxy_tool.name else tool.name
|
|
26
|
+
tool.description = proxy_tool.description if proxy_tool.description else tool.description
|
|
27
|
+
tool.function_id = proxy_tool.function_id if proxy_tool.function_id else tool.function_id
|
|
28
|
+
tool.function_description = proxy_tool.function_description if proxy_tool.function_description else tool.function_description
|
|
29
|
+
#normalize vector_db
|
|
30
|
+
if tool.vector_db:
|
|
31
|
+
tool.vector_db = os.path.join(
|
|
32
|
+
os.path.join(config.robot_data_folder,config.robot_data_db_folder,config.robot_data_db_folder_store),
|
|
33
|
+
os.path.splitext(os.path.basename(tool.vector_db))[0]) if tool.vector_db else None
|
|
34
|
+
return tool
|
|
35
|
+
except Exception as e:
|
|
36
|
+
print(f"[!] Error in proxy_app_tool: {e}")
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
def get_structured_tools(llm: LlmInterface, tools: list[LlmAppTool], callbacks:list, queue: Queue) -> list[StructuredTool]:
|
|
40
|
+
_structured_tools :list[StructuredTool] = []
|
|
41
|
+
for tool in [tool for tool in tools if tool.is_active]:
|
|
42
|
+
if tool.function_name == "proxy_app_tool":
|
|
43
|
+
# override the tool
|
|
44
|
+
loop = asyncio.get_event_loop()
|
|
45
|
+
if loop.is_running():
|
|
46
|
+
import nest_asyncio
|
|
47
|
+
nest_asyncio.apply()
|
|
48
|
+
processed_tool = loop.run_until_complete(__process_proxy_tool(tool))
|
|
49
|
+
if processed_tool is None:
|
|
50
|
+
continue
|
|
51
|
+
tool = processed_tool
|
|
52
|
+
if _tool_config := ToolManager._list.get(tool.function_name):
|
|
53
|
+
_tool_instance = ToolManager(llm, tool, callbacks, queue)
|
|
54
|
+
_structured_tool = StructuredTool.from_function(
|
|
55
|
+
coroutine=_tool_instance.get_coroutine(),
|
|
56
|
+
name=tool.function_id if tool.function_id else tool.function_name,
|
|
57
|
+
description=tool.function_description,
|
|
58
|
+
args_schema=_tool_config.model
|
|
59
|
+
#infer_schema=True,
|
|
60
|
+
#parse_docstring=True,
|
|
61
|
+
#error_on_invalid_docstring=True
|
|
62
|
+
)
|
|
63
|
+
_structured_tool.tags = [tool.function_id if tool.function_id else tool.function_name]
|
|
64
|
+
secrets = tool.secrets_to_dict()
|
|
65
|
+
if secrets and secrets.get("stream") == "true":
|
|
66
|
+
_structured_tool.tags.append("stream")
|
|
67
|
+
_structured_tools.append(_structured_tool)
|
|
68
|
+
return _structured_tools
|