ws-bom-robot-app 0.0.42__tar.gz → 0.0.44__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.42/ws_bom_robot_app.egg-info → ws_bom_robot_app-0.0.44}/PKG-INFO +1 -1
  2. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/setup.py +1 -1
  3. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/config.py +2 -0
  4. ws_bom_robot_app-0.0.44/ws_bom_robot_app/llm/agent_handler.py +178 -0
  5. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/agent_lcel.py +6 -12
  6. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/api.py +6 -4
  7. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/main.py +1 -3
  8. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/providers/llm_manager.py +4 -4
  9. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/tools/utils.py +2 -2
  10. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/utils/print.py +8 -8
  11. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44/ws_bom_robot_app.egg-info}/PKG-INFO +1 -1
  12. ws_bom_robot_app-0.0.42/ws_bom_robot_app/llm/agent_handler.py +0 -180
  13. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/MANIFEST.in +0 -0
  14. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/README.md +0 -0
  15. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/pyproject.toml +0 -0
  16. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/requirements.txt +0 -0
  17. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/setup.cfg +0 -0
  18. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/__init__.py +0 -0
  19. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/auth.py +0 -0
  20. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/cron_manager.py +0 -0
  21. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/__init__.py +0 -0
  22. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/agent_description.py +0 -0
  23. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/defaut_prompt.py +0 -0
  24. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/models/__init__.py +0 -0
  25. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/models/api.py +0 -0
  26. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/models/base.py +0 -0
  27. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/models/kb.py +0 -0
  28. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/providers/__init__.py +0 -0
  29. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/settings.py +0 -0
  30. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/tools/__init__.py +0 -0
  31. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/tools/models/__init__.py +0 -0
  32. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/tools/models/main.py +0 -0
  33. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/tools/tool_builder.py +0 -0
  34. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/tools/tool_manager.py +0 -0
  35. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/utils/__init__.py +0 -0
  36. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/utils/agent.py +0 -0
  37. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/utils/chunker.py +0 -0
  38. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/utils/download.py +0 -0
  39. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/utils/kb.py +0 -0
  40. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/utils/secrets.py +0 -0
  41. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/utils/webhooks.py +0 -0
  42. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/vector_store/__init__.py +0 -0
  43. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/vector_store/db/__init__.py +0 -0
  44. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/vector_store/db/base.py +0 -0
  45. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/vector_store/db/chroma.py +0 -0
  46. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/vector_store/db/faiss.py +0 -0
  47. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/vector_store/db/manager.py +0 -0
  48. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/vector_store/db/qdrant.py +0 -0
  49. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/vector_store/generator.py +0 -0
  50. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/vector_store/integration/__init__.py +0 -0
  51. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/vector_store/integration/azure.py +0 -0
  52. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/vector_store/integration/base.py +0 -0
  53. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/vector_store/integration/confluence.py +0 -0
  54. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/vector_store/integration/dropbox.py +0 -0
  55. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/vector_store/integration/gcs.py +0 -0
  56. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/vector_store/integration/github.py +0 -0
  57. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/vector_store/integration/googledrive.py +0 -0
  58. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/vector_store/integration/jira.py +0 -0
  59. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/vector_store/integration/manager.py +0 -0
  60. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/vector_store/integration/s3.py +0 -0
  61. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/vector_store/integration/sftp.py +0 -0
  62. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/vector_store/integration/sharepoint.py +0 -0
  63. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/vector_store/integration/sitemap.py +0 -0
  64. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/vector_store/integration/slack.py +0 -0
  65. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/vector_store/loader/__init__.py +0 -0
  66. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/vector_store/loader/base.py +0 -0
  67. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/vector_store/loader/docling.py +0 -0
  68. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/vector_store/loader/json_loader.py +0 -0
  69. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/main.py +0 -0
  70. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/task_manager.py +0 -0
  71. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/util.py +0 -0
  72. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app.egg-info/SOURCES.txt +0 -0
  73. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app.egg-info/dependency_links.txt +0 -0
  74. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app.egg-info/requires.txt +0 -0
  75. {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/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.42
3
+ Version: 0.0.44
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.42",
7
+ version="0.0.44",
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 # 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)
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,4 +1,4 @@
1
- from typing import Annotated, Any
1
+ from typing import Annotated, Any, Mapping
2
2
  from fastapi import APIRouter, HTTPException, Request, Header
3
3
  from fastapi.responses import StreamingResponse
4
4
  from ws_bom_robot_app.llm.agent_description import AgentDescriptor
@@ -9,7 +9,7 @@ from ws_bom_robot_app.llm.vector_store.generator import kb, rules, kb_stream_fil
9
9
  from ws_bom_robot_app.llm.tools.tool_manager import ToolManager
10
10
  from ws_bom_robot_app.llm.vector_store.integration.manager import IntegrationManager
11
11
  from ws_bom_robot_app.task_manager import task_manager, TaskHeader
12
-
12
+ from uuid import uuid4
13
13
  router = APIRouter(prefix="/api/llm", tags=["llm"])
14
14
 
15
15
  @router.get("/")
@@ -20,13 +20,15 @@ async def root():
20
20
  async def _invoke(rq: InvokeRequest):
21
21
  return await invoke(rq)
22
22
 
23
+ def _stream_headers(rq: StreamRequest) -> Mapping[str, str]:
24
+ return {"X-thread-id": rq.thread_id or str(uuid4())}
23
25
  @router.post("/stream")
24
26
  async def _stream(rq: StreamRequest, ctx: Request) -> StreamingResponse:
25
- return StreamingResponse(stream(rq, ctx), media_type="application/json")
27
+ return StreamingResponse(stream(rq, ctx), media_type="application/json", headers=_stream_headers(rq))
26
28
 
27
29
  @router.post("/stream/raw")
28
30
  async def _stream_raw(rq: StreamRequest, ctx: Request) -> StreamingResponse:
29
- return StreamingResponse(stream(rq, ctx, formatted=False), media_type="application/json")
31
+ return StreamingResponse(stream(rq, ctx, formatted=False), media_type="application/json", headers=_stream_headers(rq))
30
32
 
31
33
  @router.post("/kb")
32
34
  async def _kb(rq: KbRequest) -> VectorDbResponse:
@@ -103,11 +103,9 @@ async def __stream(rq: StreamRequest, ctx: Request, queue: Queue,formatted: bool
103
103
  )
104
104
  callbacks.append(nebuly_callback)
105
105
 
106
- #with warnings.catch_warnings():
107
- # warnings.simplefilter("ignore", UserWarning)
108
106
  try:
109
107
  await processor.executor.ainvoke(
110
- {"input": rq.messages[-1], "chat_history": settings.chat_history},
108
+ {"chat_history": settings.chat_history},
111
109
  {"callbacks": callbacks},
112
110
  )
113
111
  except Exception as e:
@@ -64,7 +64,7 @@ class DeepSeek(LlmInterface):
64
64
  return ChatOpenAI(
65
65
  api_key=self.config.api_key or os.getenv("DEEPSEEK_API_KEY"),
66
66
  model=self.config.model,
67
- base_url="https://api.deepseek.com/v1",
67
+ base_url="https://api.deepseek.com",
68
68
  max_tokens=8192,
69
69
  temperature=self.config.temperature,
70
70
  streaming=True,
@@ -91,7 +91,7 @@ class Google(LlmInterface):
91
91
  from langchain_google_genai.embeddings import GoogleGenerativeAIEmbeddings
92
92
  return GoogleGenerativeAIEmbeddings(
93
93
  google_api_key=self.config.api_key,
94
- model="models/text-embedding-004")
94
+ model="models/text-embedding-005")
95
95
 
96
96
  def get_models(self):
97
97
  import google.generativeai as genai
@@ -114,7 +114,7 @@ class Gvertex(LlmInterface):
114
114
  )
115
115
  def get_embeddings(self):
116
116
  from langchain_google_vertexai import VertexAIEmbeddings
117
- return VertexAIEmbeddings(model_name="text-embedding-004")
117
+ return VertexAIEmbeddings(model_name="text-embedding-005")
118
118
  def get_models(self):
119
119
  #from google.cloud import aiplatform
120
120
  #aiplatform.init()
@@ -125,7 +125,7 @@ class Gvertex(LlmInterface):
125
125
  #see https://cloud.google.com/vertex-ai/generative-ai/docs/learn/locations#united-states for available models
126
126
  return [
127
127
  {"id":"gemini-2.0-flash-001"},
128
- {"id":"gemini-1.5-pro-001"},
128
+ {"id":"gemini-2.0-flash-lite-001"},
129
129
  {"id":"gemini-1.5-pro-002"}
130
130
  ]
131
131
 
@@ -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.42
3
+ Version: 0.0.44
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)