ws-bom-robot-app 0.0.41__tar.gz → 0.0.43__tar.gz

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 (75) hide show
  1. {ws_bom_robot_app-0.0.41/ws_bom_robot_app.egg-info → ws_bom_robot_app-0.0.43}/PKG-INFO +1 -1
  2. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/setup.py +1 -1
  3. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/config.py +2 -0
  4. ws_bom_robot_app-0.0.43/ws_bom_robot_app/llm/agent_handler.py +178 -0
  5. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/agent_lcel.py +6 -12
  6. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/main.py +41 -19
  7. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/providers/llm_manager.py +11 -9
  8. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/tools/utils.py +2 -2
  9. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/utils/print.py +8 -8
  10. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43/ws_bom_robot_app.egg-info}/PKG-INFO +1 -1
  11. ws_bom_robot_app-0.0.41/ws_bom_robot_app/llm/agent_handler.py +0 -180
  12. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/MANIFEST.in +0 -0
  13. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/README.md +0 -0
  14. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/pyproject.toml +0 -0
  15. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/requirements.txt +0 -0
  16. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/setup.cfg +0 -0
  17. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/__init__.py +0 -0
  18. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/auth.py +0 -0
  19. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/cron_manager.py +0 -0
  20. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/__init__.py +0 -0
  21. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/agent_description.py +0 -0
  22. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/api.py +0 -0
  23. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/defaut_prompt.py +0 -0
  24. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/models/__init__.py +0 -0
  25. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/models/api.py +0 -0
  26. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/models/base.py +0 -0
  27. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/models/kb.py +0 -0
  28. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/providers/__init__.py +0 -0
  29. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/settings.py +0 -0
  30. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/tools/__init__.py +0 -0
  31. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/tools/models/__init__.py +0 -0
  32. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/tools/models/main.py +0 -0
  33. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/tools/tool_builder.py +0 -0
  34. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/tools/tool_manager.py +0 -0
  35. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/utils/__init__.py +0 -0
  36. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/utils/agent.py +0 -0
  37. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/utils/chunker.py +0 -0
  38. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/utils/download.py +0 -0
  39. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/utils/kb.py +0 -0
  40. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/utils/secrets.py +0 -0
  41. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/utils/webhooks.py +0 -0
  42. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/vector_store/__init__.py +0 -0
  43. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/vector_store/db/__init__.py +0 -0
  44. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/vector_store/db/base.py +0 -0
  45. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/vector_store/db/chroma.py +0 -0
  46. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/vector_store/db/faiss.py +0 -0
  47. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/vector_store/db/manager.py +0 -0
  48. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/vector_store/db/qdrant.py +0 -0
  49. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/vector_store/generator.py +0 -0
  50. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/vector_store/integration/__init__.py +0 -0
  51. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/vector_store/integration/azure.py +0 -0
  52. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/vector_store/integration/base.py +0 -0
  53. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/vector_store/integration/confluence.py +0 -0
  54. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/vector_store/integration/dropbox.py +0 -0
  55. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/vector_store/integration/gcs.py +0 -0
  56. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/vector_store/integration/github.py +0 -0
  57. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/vector_store/integration/googledrive.py +0 -0
  58. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/vector_store/integration/jira.py +0 -0
  59. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/vector_store/integration/manager.py +0 -0
  60. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/vector_store/integration/s3.py +0 -0
  61. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/vector_store/integration/sftp.py +0 -0
  62. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/vector_store/integration/sharepoint.py +0 -0
  63. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/vector_store/integration/sitemap.py +0 -0
  64. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/vector_store/integration/slack.py +0 -0
  65. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/vector_store/loader/__init__.py +0 -0
  66. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/vector_store/loader/base.py +0 -0
  67. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/vector_store/loader/docling.py +0 -0
  68. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/llm/vector_store/loader/json_loader.py +0 -0
  69. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/main.py +0 -0
  70. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/task_manager.py +0 -0
  71. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app/util.py +0 -0
  72. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app.egg-info/SOURCES.txt +0 -0
  73. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app.egg-info/dependency_links.txt +0 -0
  74. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app.egg-info/requires.txt +0 -0
  75. {ws_bom_robot_app-0.0.41 → ws_bom_robot_app-0.0.43}/ws_bom_robot_app.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: ws_bom_robot_app
3
- Version: 0.0.41
3
+ Version: 0.0.43
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
@@ -4,7 +4,7 @@ _requirements = [line.split('#')[0].strip() for line in open("requirements.txt")
4
4
 
5
5
  setup(
6
6
  name="ws_bom_robot_app",
7
- version="0.0.41",
7
+ version="0.0.43",
8
8
  description="A FastAPI application serving ws bom/robot/llm platform ai.",
9
9
  long_description=open("README.md", encoding='utf-8').read(),
10
10
  long_description_content_type="text/markdown",
@@ -21,6 +21,7 @@ class Settings(BaseSettings):
21
21
  robot_cms_db_folder: str = 'llmVectorDb'
22
22
  robot_cms_kb_folder: str ='llmKbFile'
23
23
  ANTHROPIC_API_KEY: str = ''
24
+ DEEPSEEK_API_KEY: str = ''
24
25
  OPENAI_API_KEY: str = '' # used also for saas dall-e api
25
26
  OLLAMA_API_URL: str = 'http://localhost:11434'
26
27
  GROQ_API_KEY: str = ''
@@ -36,6 +37,7 @@ class Settings(BaseSettings):
36
37
  os.environ["OPENAI_API_KEY"] = self.OPENAI_API_KEY
37
38
  os.environ["OLLAMA_API_URL"] = self.OLLAMA_API_URL
38
39
  os.environ["ANTHROPIC_API_KEY"] = self.ANTHROPIC_API_KEY
40
+ os.environ["DEEPSEEK_API_KEY"] = self.DEEPSEEK_API_KEY
39
41
  os.environ["GROQ_API_KEY"] = self.GROQ_API_KEY
40
42
  os.environ["GOOGLE_API_KEY"] = self.GOOGLE_API_KEY
41
43
  os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = self.GOOGLE_APPLICATION_CREDENTIALS
@@ -0,0 +1,178 @@
1
+ from asyncio import Queue
2
+ from langchain_core.agents import AgentFinish
3
+ from langchain_core.outputs import ChatGenerationChunk, GenerationChunk
4
+ from langchain.callbacks.base import AsyncCallbackHandler
5
+ from ws_bom_robot_app.llm.utils.print import print_json, print_string
6
+ from typing import Any, Dict, List, Optional, Union
7
+ from uuid import UUID
8
+ import ws_bom_robot_app.llm.settings as settings
9
+ from langchain_core.callbacks.base import AsyncCallbackHandler
10
+ from langchain_core.outputs import ChatGenerationChunk, GenerationChunk
11
+ from langchain_core.messages import BaseMessage, HumanMessage, AIMessage
12
+ import json, logging, re
13
+
14
+ # Here is a custom handler that will print the tokens to stdout.
15
+ # Instead of printing to stdout you can send the data elsewhere; e.g., to a streaming API response
16
+
17
+
18
+ def _parse_token(llm:str,token: str) -> str:
19
+ """Parses the token based on the LLM provider."""
20
+ if llm == "anthropic" and isinstance(token, list):
21
+ first = token[0]
22
+ if 'text' in first:
23
+ token = first['text']
24
+ else:
25
+ #[{'id': 'toolu_01GGLwJcrQ8PvFMUkQPGu8n7', 'input': {}, 'name': 'document_retriever_xxx', 'type': 'tool_use', 'index': 1}]
26
+ token = ""
27
+ return token
28
+
29
+ class AgentHandler(AsyncCallbackHandler):
30
+
31
+ def __init__(self, queue: Queue, llm:str, threadId: str = None) -> None:
32
+ super().__init__()
33
+ self._threadId = threadId
34
+ self.queue = queue
35
+ self.llm = llm
36
+ self.__started: bool = False
37
+ # on new token event
38
+ self.stream_buffer = "" # accumulates text that hasn't been processed yet
39
+ self.in_json_block = False
40
+ self.json_buffer = ""
41
+ self.json_start_regex = re.compile(r'(`{1,3}\s*json\b)') # detect a potential json start fence.
42
+ self.json_end_regex = re.compile(r'(`{1,3})') # an end fence (one to three backticks).
43
+ self.stream_cut_last_output_chunk_size = 16 # safe cut last chunk size to output if no markers are found
44
+
45
+ async def on_chat_model_start(self, serialized, messages, *, run_id, parent_run_id = None, tags = None, metadata = None, **kwargs):
46
+ if not self.__started:
47
+ self.__started = True
48
+ firstChunk = {
49
+ "type": "info",
50
+ "threadId": self._threadId,
51
+ }
52
+ await self.queue.put(print_json(firstChunk))
53
+
54
+ async def on_llm_new_token(
55
+ self,
56
+ token: str,
57
+ *,
58
+ chunk: Optional[Union[GenerationChunk, ChatGenerationChunk]] = None,
59
+ run_id: UUID,
60
+ parent_run_id: Optional[UUID] = None,
61
+ tags: Optional[List[str]] = None,
62
+ **kwargs: Any,
63
+ ) -> None:
64
+ if token:
65
+ token = _parse_token(self.llm,token)
66
+ if token:
67
+ self.stream_buffer += token.replace('\n','') # append new data to pending buffer
68
+ if not self.in_json_block:
69
+ # search for the start of a json block.
70
+ start_match = self.json_start_regex.search(self.stream_buffer)
71
+ if start_match:
72
+ start_index = start_match.start()
73
+ # everything before the start marker is normal content.
74
+ if start_index > 0:
75
+ _before = self.stream_buffer[:start_index].replace('`','').strip() # remove eventual preceding backticks.
76
+ if _before:
77
+ await self.queue.put(print_string(_before))
78
+ # remove the start marker from pending.
79
+ self.stream_buffer = self.stream_buffer[start_match.end():]
80
+ # switch into json mode.
81
+ self.in_json_block = True
82
+ self.json_buffer = ""
83
+ else:
84
+ # no json start marker found. It might be because the marker is split between chunks.
85
+ # to avoid losing potential marker fragments, output what we can safely process:
86
+ # if the pending text is long, we output most of it except the last few characters.
87
+ if len(self.stream_buffer) > self.stream_cut_last_output_chunk_size:
88
+ safe_cut = self.stream_buffer[:-3]
89
+ await self.queue.put(print_string(safe_cut))
90
+ self.stream_buffer = self.stream_buffer[-3:]
91
+ else:
92
+ # in json block: look for an end fence.
93
+ end_match = self.json_end_regex.search(self.stream_buffer,endpos=3)
94
+ if end_match:
95
+ end_index = end_match.start()
96
+ self.json_buffer += self.stream_buffer[:end_index]
97
+ try:
98
+ data = json.loads(self.json_buffer.replace('`',''))
99
+ await self.queue.put(print_json(data))
100
+ except json.JSONDecodeError as e:
101
+ logging.error(f"on_token: invalid json: {e} | {self.json_buffer}")
102
+ finally:
103
+ self.json_buffer = ""
104
+ # remove the end fence from pending.
105
+ self.stream_buffer = self.stream_buffer[end_match.end():].replace('`','').strip()
106
+ self.in_json_block = False
107
+ else:
108
+ # no end marker found
109
+ # accumulate everything and break to wait for more data.
110
+ self.json_buffer += self.stream_buffer
111
+ self.stream_buffer = ""
112
+
113
+ async def on_agent_finish(
114
+ self,
115
+ finish: AgentFinish,
116
+ *,
117
+ run_id: UUID,
118
+ parent_run_id: UUID = None,
119
+ tags: List[str] = None,
120
+ **kwargs: Any,
121
+ ) -> None:
122
+ settings.chat_history.extend(
123
+ [
124
+ AIMessage(content=_parse_token(self.llm,finish.return_values["output"])),
125
+ ]
126
+ )
127
+ # end-of-stream: flush any remaining text
128
+ if self.in_json_block:
129
+ try:
130
+ data = json.loads(self.json_buffer)
131
+ await self.queue.put(print_json(data))
132
+ except json.JSONDecodeError as e :
133
+ logging.error(f"on_agent_finish: invalid json: {e} | {self.json_buffer}")
134
+ #await self.queue.put(print_string(self.json_buffer))
135
+ elif self.stream_buffer:
136
+ await self.queue.put(print_string(self.stream_buffer))
137
+
138
+ finalChunk = {"type": "end"}
139
+ await self.queue.put(print_json(finalChunk))
140
+ await self.queue.put(None)
141
+
142
+
143
+ class RawAgentHandler(AsyncCallbackHandler):
144
+
145
+ def __init__(self,queue: Queue, llm: str) -> None:
146
+ super().__init__()
147
+ self.queue = queue
148
+ self.llm = llm
149
+
150
+ async def on_llm_new_token(
151
+ self,
152
+ token: str,
153
+ *,
154
+ chunk: Optional[Union[GenerationChunk, ChatGenerationChunk]] = None,
155
+ run_id: UUID,
156
+ parent_run_id: Optional[UUID] = None,
157
+ tags: Optional[List[str]] = None,
158
+ **kwargs: Any,
159
+ ) -> None:
160
+ """Handles new tokens during streaming."""
161
+ if token: # only process non-empty tokens
162
+ await self.queue.put(_parse_token(self.llm,token))
163
+
164
+ async def on_agent_finish(
165
+ self,
166
+ finish: AgentFinish,
167
+ *,
168
+ run_id: UUID,
169
+ parent_run_id: UUID = None,
170
+ tags: List[str] = None,
171
+ **kwargs: Any,
172
+ ) -> None:
173
+ settings.chat_history.extend(
174
+ [
175
+ AIMessage(content=_parse_token(self.llm,finish.return_values["output"]))
176
+ ]
177
+ )
178
+ await self.queue.put(None)
@@ -20,32 +20,26 @@ class AgentLcel:
20
20
  self.__tools = tools
21
21
  self.rules = rules
22
22
  self.embeddings = llm.get_embeddings()
23
- self.memory_key = "chat_history"
23
+ self.memory_key: str = "chat_history"
24
24
  self.__llm_with_tools = llm.get_llm().bind_tools(self.__tools) if len(self.__tools) > 0 else llm.get_llm()
25
25
  self.executor = self.__create_agent()
26
26
 
27
27
  async def __create_prompt(self, input: dict) -> ChatPromptTemplate:
28
- message : LlmMessage = input["input"]
28
+ message : LlmMessage = input[self.memory_key][-1]
29
29
  input = message.content
30
30
  rules_prompt = await get_rules(self.embeddings, self.rules, input) if self.rules else ""
31
31
  system = default_prompt + (tool_prompt(render_text_description(self.__tools)) if len(self.__tools)>0 else "") + self.sys_message + rules_prompt
32
- return ChatPromptTemplate.from_messages(
33
- [
34
- (
35
- "system", system
36
- ),
32
+ return ChatPromptTemplate([
33
+ ("system", system),
37
34
  MessagesPlaceholder(variable_name=self.memory_key),
38
- ("user", "{input}"),
39
35
  MessagesPlaceholder(variable_name="agent_scratchpad"),
40
- ]
41
- )
36
+ ])
42
37
 
43
38
  def __create_agent(self) -> AgentExecutor:
44
39
  agent: Any = (
45
40
  {
46
- "input": lambda x: x["input"],
47
41
  "agent_scratchpad": lambda x: self.__llm.get_formatter(x["intermediate_steps"]),
48
- "chat_history": lambda x: x["chat_history"],
42
+ str(self.memory_key): lambda x: x[self.memory_key],
49
43
  }
50
44
  | RunnableLambda(self.__create_prompt)
51
45
  | self.__llm_with_tools
@@ -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(
@@ -79,11 +103,9 @@ async def __stream(rq: StreamRequest, ctx: Request, queue: Queue,formatted: bool
79
103
  )
80
104
  callbacks.append(nebuly_callback)
81
105
 
82
- #with warnings.catch_warnings():
83
- # warnings.simplefilter("ignore", UserWarning)
84
106
  try:
85
107
  await processor.executor.ainvoke(
86
- {"input": rq.messages[-1], "chat_history": settings.chat_history},
108
+ {"chat_history": settings.chat_history},
87
109
  {"callbacks": callbacks},
88
110
  )
89
111
  except Exception as e:
@@ -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,9 +62,9 @@ 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
- base_url="https://api.deepseek.com/v1",
67
+ base_url="https://api.deepseek.com",
66
68
  max_tokens=8192,
67
69
  temperature=self.config.temperature,
68
70
  streaming=True,
@@ -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
@@ -89,7 +91,7 @@ class Google(LlmInterface):
89
91
  from langchain_google_genai.embeddings import GoogleGenerativeAIEmbeddings
90
92
  return GoogleGenerativeAIEmbeddings(
91
93
  google_api_key=self.config.api_key,
92
- model="models/text-embedding-004")
94
+ model="models/text-embedding-005")
93
95
 
94
96
  def get_models(self):
95
97
  import google.generativeai as genai
@@ -112,7 +114,7 @@ class Gvertex(LlmInterface):
112
114
  )
113
115
  def get_embeddings(self):
114
116
  from langchain_google_vertexai import VertexAIEmbeddings
115
- return VertexAIEmbeddings(model_name="text-embedding-004")
117
+ return VertexAIEmbeddings(model_name="text-embedding-005")
116
118
  def get_models(self):
117
119
  #from google.cloud import aiplatform
118
120
  #aiplatform.init()
@@ -123,7 +125,7 @@ class Gvertex(LlmInterface):
123
125
  #see https://cloud.google.com/vertex-ai/generative-ai/docs/learn/locations#united-states for available models
124
126
  return [
125
127
  {"id":"gemini-2.0-flash-001"},
126
- {"id":"gemini-1.5-pro-001"},
128
+ {"id":"gemini-2.0-flash-lite-001"},
127
129
  {"id":"gemini-1.5-pro-002"}
128
130
  ]
129
131
 
@@ -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,
@@ -2,10 +2,10 @@ import random, os
2
2
  from langchain_openai import ChatOpenAI
3
3
  from langchain_core.prompts import PromptTemplate
4
4
  from ws_bom_robot_app.llm.providers.llm_manager import LlmInterface
5
- from ws_bom_robot_app.llm.utils.print import printString
5
+ from ws_bom_robot_app.llm.utils.print import print_string
6
6
 
7
7
  def __print_output(data: str) -> str:
8
- return printString(data) if os.environ.get("AGENT_HANDLER_FORMATTED") == str(True) else f"{data} "
8
+ return print_string(data) if os.environ.get("AGENT_HANDLER_FORMATTED") == str(True) else f"{data} "
9
9
 
10
10
  def getRandomWaitingMessage(waiting_messages: str, traduction: bool = True) -> str:
11
11
  if not waiting_messages: return ""
@@ -14,16 +14,16 @@ class HiddenPrints:
14
14
  sys.stdout = self._original_stdout
15
15
  sys.stderr = self._original_stderr
16
16
 
17
- def printJson(data) -> str:
18
- return f"{json.dumps(data, indent=2, sort_keys=True)},"
17
+ def print_json(data) -> str:
18
+ return print_single_json(data) + ","
19
19
 
20
- def printSingleJson(data) -> str:
21
- return f"{json.dumps(data, indent=2, sort_keys=True)}"
20
+ def print_single_json(data) -> str:
21
+ return json.dumps(data, sort_keys=True)
22
22
 
23
- def printString(data: str) -> str:
23
+ def print_string(data: str) -> str:
24
24
  if data != "":
25
- return printJson(data)
25
+ return print_json(data)
26
26
 
27
- def printSingleString(data: str) -> str:
27
+ def print_single_string(data: str) -> str:
28
28
  if data != "":
29
- return printSingleJson(data)
29
+ return print_single_json(data)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: ws_bom_robot_app
3
- Version: 0.0.41
3
+ Version: 0.0.43
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
@@ -1,180 +0,0 @@
1
- from asyncio import Queue
2
- from langchain_core.agents import AgentFinish
3
- from langchain_core.outputs import ChatGenerationChunk, GenerationChunk
4
- from langchain.callbacks.base import AsyncCallbackHandler
5
- from ws_bom_robot_app.llm.utils.print import printJson, printString
6
- from typing import Any, Dict, List, Optional, Union
7
- from uuid import UUID
8
- import ws_bom_robot_app.llm.settings as settings
9
- from langchain_core.callbacks.base import AsyncCallbackHandler
10
- from langchain_core.outputs import ChatGenerationChunk, GenerationChunk
11
- from langchain_core.messages import BaseMessage, HumanMessage, AIMessage
12
- import json
13
-
14
- # Here is a custom handler that will print the tokens to stdout.
15
- # Instead of printing to stdout you can send the data elsewhere; e.g., to a streaming API response
16
-
17
-
18
- def _parse_token(llm:str,token: str) -> str:
19
- """Parses the token based on the LLM provider."""
20
- if llm == "anthropic" and isinstance(token, list):
21
- first = token[0]
22
- if 'text' in first:
23
- token = first['text']
24
- else:
25
- #[{'id': 'toolu_01GGLwJcrQ8PvFMUkQPGu8n7', 'input': {}, 'name': 'document_retriever_xxx', 'type': 'tool_use', 'index': 1}]
26
- token = ""
27
- return token
28
-
29
- class AgentHandler(AsyncCallbackHandler):
30
-
31
- def __init__(self, queue: Queue, llm:str, threadId: str = None) -> None:
32
- super().__init__()
33
- self._threadId = threadId
34
- self.json_block = ""
35
- self.is_json_block = False
36
- self.backtick_count = 0 # Conteggio dei backticks per il controllo accurato
37
- self.queue = queue
38
- self.llm = llm
39
-
40
- async def on_llm_start(
41
- self,
42
- serialized: Dict[str, Any],
43
- prompts: List[str],
44
- *,
45
- run_id: UUID,
46
- parent_run_id: UUID = None,
47
- tags: List[str] = None,
48
- metadata: Dict[str, Any] = None,
49
- **kwargs: Any,
50
- ) -> None:
51
- firstChunk = {
52
- "type": "info",
53
- "threadId": self._threadId,
54
- }
55
- await self.queue.put(printString(firstChunk))
56
-
57
- """async def on_chat_model_start(self, serialized: Dict[str, Any], messages: List[List[BaseMessage]], *, run_id: UUID = None, parent_run_id = None, tags = None, metadata = None, **kwargs: Any) -> Any:
58
- pass"""
59
-
60
- async def on_tool_end(self, output: Any, *, run_id: UUID, parent_run_id: UUID = None, tags: List[str] = None, **kwargs: Any) -> None:
61
- pass
62
-
63
- async def on_llm_new_token(
64
- self,
65
- token: str,
66
- *,
67
- chunk: Optional[Union[GenerationChunk, ChatGenerationChunk]] = None,
68
- run_id: UUID,
69
- parent_run_id: Optional[UUID] = None,
70
- tags: Optional[List[str]] = None,
71
- **kwargs: Any,
72
- ) -> None:
73
- """Gestisce i nuovi token durante lo streaming."""
74
-
75
- if token:
76
- token = _parse_token(self.llm,token)
77
- if token:
78
- self.backtick_count += token.count("`")
79
-
80
- if self.backtick_count >= 3:
81
- if not self.is_json_block:
82
- self.is_json_block = True
83
- self.json_block = ""
84
- else:
85
- self.is_json_block = False
86
- self.json_block += token.replace("```json", '')
87
- await self.process_json_block(self.json_block)
88
- self.json_block = ""
89
- self.backtick_count = 0
90
- elif self.is_json_block:
91
- self.json_block += token
92
- else:
93
- await self.queue.put(printString(token))
94
-
95
- async def on_agent_finish(
96
- self,
97
- finish: AgentFinish,
98
- *,
99
- run_id: UUID,
100
- parent_run_id: UUID = None,
101
- tags: List[str] = None,
102
- **kwargs: Any,
103
- ) -> None:
104
- settings.chat_history.extend(
105
- [
106
- AIMessage(content=_parse_token(self.llm,finish.return_values["output"])),
107
- ]
108
- )
109
- finalChunk = {"type": "end"}
110
- await self.queue.put(printJson(finalChunk))
111
- await self.queue.put(None)
112
-
113
- async def process_json_block(self, json_block: str):
114
- """Processa il blocco JSON completo."""
115
- # Rimuove il delimitatore iniziale '```json' se presente, e spazi vuoti
116
- json_block_clean = json_block.replace('```', '').replace('json', '').strip()
117
- # Verifica che il blocco non sia vuoto prima di tentare il parsing
118
- if json_block_clean:
119
- try:
120
- # Prova a fare il parsing del JSON
121
- parsed_json = json.loads(json_block_clean)
122
- await self.queue.put(printJson(parsed_json))
123
- except json.JSONDecodeError as e:
124
- # Se il JSON è malformato, logga l'errore
125
- raise e
126
-
127
- class RawAgentHandler(AsyncCallbackHandler):
128
-
129
- def __init__(self,queue: Queue, llm: str) -> None:
130
- super().__init__()
131
- self.queue = queue
132
- self.llm = llm
133
- async def on_llm_start(
134
- self,
135
- serialized: Dict[str, Any],
136
- prompts: List[str],
137
- *,
138
- run_id: UUID,
139
- parent_run_id: UUID = None,
140
- tags: List[str] = None,
141
- metadata: Dict[str, Any] = None,
142
- **kwargs: Any,
143
- ) -> None:
144
- pass
145
-
146
- """async def on_chat_model_start(self, serialized: Dict[str, Any], messages: List[List[BaseMessage]], *, run_id: UUID = None, parent_run_id = None, tags = None, metadata = None, **kwargs: Any) -> Any:
147
- pass"""
148
-
149
- async def on_tool_end(self, output: Any, *, run_id: UUID, parent_run_id: UUID = None, tags: List[str] = None, **kwargs: Any) -> None:
150
- pass
151
-
152
- async def on_llm_new_token(
153
- self,
154
- token: str,
155
- *,
156
- chunk: Optional[Union[GenerationChunk, ChatGenerationChunk]] = None,
157
- run_id: UUID,
158
- parent_run_id: Optional[UUID] = None,
159
- tags: Optional[List[str]] = None,
160
- **kwargs: Any,
161
- ) -> None:
162
- """Handles new tokens during streaming."""
163
- if token: # Only process non-empty tokens
164
- await self.queue.put(_parse_token(self.llm,token))
165
-
166
- async def on_agent_finish(
167
- self,
168
- finish: AgentFinish,
169
- *,
170
- run_id: UUID,
171
- parent_run_id: UUID = None,
172
- tags: List[str] = None,
173
- **kwargs: Any,
174
- ) -> None:
175
- settings.chat_history.extend(
176
- [
177
- AIMessage(content=_parse_token(self.llm,finish.return_values["output"]))
178
- ]
179
- )
180
- await self.queue.put(None)