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.
- {ws_bom_robot_app-0.0.42/ws_bom_robot_app.egg-info → ws_bom_robot_app-0.0.44}/PKG-INFO +1 -1
- {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/setup.py +1 -1
- {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/config.py +2 -0
- ws_bom_robot_app-0.0.44/ws_bom_robot_app/llm/agent_handler.py +178 -0
- {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/agent_lcel.py +6 -12
- {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/api.py +6 -4
- {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/main.py +1 -3
- {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
- {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/tools/utils.py +2 -2
- {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/utils/print.py +8 -8
- {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44/ws_bom_robot_app.egg-info}/PKG-INFO +1 -1
- ws_bom_robot_app-0.0.42/ws_bom_robot_app/llm/agent_handler.py +0 -180
- {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/MANIFEST.in +0 -0
- {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/README.md +0 -0
- {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/pyproject.toml +0 -0
- {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/requirements.txt +0 -0
- {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/setup.cfg +0 -0
- {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/__init__.py +0 -0
- {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/auth.py +0 -0
- {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/cron_manager.py +0 -0
- {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/__init__.py +0 -0
- {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/agent_description.py +0 -0
- {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/defaut_prompt.py +0 -0
- {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/models/__init__.py +0 -0
- {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/models/api.py +0 -0
- {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/models/base.py +0 -0
- {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/models/kb.py +0 -0
- {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/providers/__init__.py +0 -0
- {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/settings.py +0 -0
- {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/tools/__init__.py +0 -0
- {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
- {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
- {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
- {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
- {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/utils/__init__.py +0 -0
- {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/utils/agent.py +0 -0
- {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/utils/chunker.py +0 -0
- {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/utils/download.py +0 -0
- {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/utils/kb.py +0 -0
- {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/utils/secrets.py +0 -0
- {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/utils/webhooks.py +0 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/main.py +0 -0
- {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/task_manager.py +0 -0
- {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/util.py +0 -0
- {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app.egg-info/SOURCES.txt +0 -0
- {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
- {ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app.egg-info/requires.txt +0 -0
- {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
|
@@ -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.
|
|
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[
|
|
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
|
|
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
|
-
|
|
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
|
-
{"
|
|
108
|
+
{"chat_history": settings.chat_history},
|
|
111
109
|
{"callbacks": callbacks},
|
|
112
110
|
)
|
|
113
111
|
except Exception as e:
|
{ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/providers/llm_manager.py
RENAMED
|
@@ -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
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
5
|
+
from ws_bom_robot_app.llm.utils.print import print_string
|
|
6
6
|
|
|
7
7
|
def __print_output(data: str) -> str:
|
|
8
|
-
return
|
|
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
|
|
18
|
-
return
|
|
17
|
+
def print_json(data) -> str:
|
|
18
|
+
return print_single_json(data) + ","
|
|
19
19
|
|
|
20
|
-
def
|
|
21
|
-
return
|
|
20
|
+
def print_single_json(data) -> str:
|
|
21
|
+
return json.dumps(data, sort_keys=True)
|
|
22
22
|
|
|
23
|
-
def
|
|
23
|
+
def print_string(data: str) -> str:
|
|
24
24
|
if data != "":
|
|
25
|
-
return
|
|
25
|
+
return print_json(data)
|
|
26
26
|
|
|
27
|
-
def
|
|
27
|
+
def print_single_string(data: str) -> str:
|
|
28
28
|
if data != "":
|
|
29
|
-
return
|
|
29
|
+
return print_single_json(data)
|
|
@@ -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)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/agent_description.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/providers/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/tools/models/__init__.py
RENAMED
|
File without changes
|
{ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/tools/models/main.py
RENAMED
|
File without changes
|
{ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/tools/tool_builder.py
RENAMED
|
File without changes
|
{ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/tools/tool_manager.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/vector_store/__init__.py
RENAMED
|
File without changes
|
{ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/vector_store/db/__init__.py
RENAMED
|
File without changes
|
{ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/vector_store/db/base.py
RENAMED
|
File without changes
|
{ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/vector_store/db/chroma.py
RENAMED
|
File without changes
|
{ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/vector_store/db/faiss.py
RENAMED
|
File without changes
|
{ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/vector_store/db/manager.py
RENAMED
|
File without changes
|
{ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/vector_store/db/qdrant.py
RENAMED
|
File without changes
|
{ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/vector_store/generator.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app/llm/vector_store/loader/base.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ws_bom_robot_app-0.0.42 → ws_bom_robot_app-0.0.44}/ws_bom_robot_app.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|