ws-bom-robot-app 0.0.99__py3-none-any.whl → 0.0.101__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. ws_bom_robot_app/llm/agent_description.py +123 -123
  2. ws_bom_robot_app/llm/agent_handler.py +176 -176
  3. ws_bom_robot_app/llm/agent_lcel.py +111 -50
  4. ws_bom_robot_app/llm/defaut_prompt.py +15 -15
  5. ws_bom_robot_app/llm/feedbacks/feedback_manager.py +66 -66
  6. ws_bom_robot_app/llm/main.py +159 -158
  7. ws_bom_robot_app/llm/models/api.py +2 -1
  8. ws_bom_robot_app/llm/models/feedback.py +30 -30
  9. ws_bom_robot_app/llm/nebuly_handler.py +185 -185
  10. ws_bom_robot_app/llm/tools/tool_builder.py +68 -68
  11. ws_bom_robot_app/llm/tools/tool_manager.py +332 -332
  12. ws_bom_robot_app/llm/tools/utils.py +41 -41
  13. ws_bom_robot_app/llm/utils/agent.py +34 -34
  14. ws_bom_robot_app/llm/utils/cms.py +114 -114
  15. ws_bom_robot_app/llm/utils/download.py +183 -183
  16. ws_bom_robot_app/llm/utils/print.py +29 -29
  17. ws_bom_robot_app/llm/vector_store/generator.py +137 -137
  18. ws_bom_robot_app/llm/vector_store/integration/shopify.py +143 -143
  19. ws_bom_robot_app/llm/vector_store/integration/thron.py +236 -236
  20. ws_bom_robot_app/llm/vector_store/loader/base.py +7 -1
  21. ws_bom_robot_app/llm/vector_store/loader/docling.py +20 -12
  22. ws_bom_robot_app/llm/vector_store/loader/json_loader.py +25 -25
  23. {ws_bom_robot_app-0.0.99.dist-info → ws_bom_robot_app-0.0.101.dist-info}/METADATA +364 -364
  24. {ws_bom_robot_app-0.0.99.dist-info → ws_bom_robot_app-0.0.101.dist-info}/RECORD +26 -26
  25. {ws_bom_robot_app-0.0.99.dist-info → ws_bom_robot_app-0.0.101.dist-info}/WHEEL +0 -0
  26. {ws_bom_robot_app-0.0.99.dist-info → ws_bom_robot_app-0.0.101.dist-info}/top_level.txt +0 -0
@@ -1,123 +1,123 @@
1
- import json, requests, re
2
- from typing import Any
3
- from abc import ABC, abstractmethod
4
- from langchain_core.prompts import ChatPromptTemplate
5
- from langchain_core.messages import AIMessage
6
- from langchain_core.runnables import RunnableSerializable
7
- from langchain_core.runnables import RunnableLambda
8
- from bs4 import BeautifulSoup
9
- from ws_bom_robot_app.llm.models.api import LlmRules
10
- from ws_bom_robot_app.llm.providers.llm_manager import LlmInterface
11
- from ws_bom_robot_app.llm.utils.agent import get_rules
12
-
13
- # SafeDict helper class
14
- class SafeDict(dict):
15
- def __missing__(self, key):
16
- return ''
17
-
18
- # Strategy Interface
19
- class AgentDescriptorStrategy(ABC):
20
- @abstractmethod
21
- def enrich_prompt(self, prompt: str, input: dict) -> str:
22
- pass
23
-
24
- @abstractmethod
25
- def rule_input(self, input: dict) -> str:
26
- pass
27
-
28
- # Concrete Strategy for Default Agent
29
- class DefaultAgentDescriptor(AgentDescriptorStrategy):
30
- def enrich_prompt(self, prompt: str, input: dict) -> str:
31
- # Default enrichment logic (could be minimal or no-op)
32
- return prompt.format_map(SafeDict(input))
33
-
34
- def rule_input(self, input: dict) -> str:
35
- return input.get('content', "")
36
-
37
- # Concrete Strategy for URL2Text Agent
38
- class URL2TextAgentDescriptor(AgentDescriptorStrategy):
39
- def enrich_prompt(self, prompt: str, input: dict) -> str:
40
- input["context"] = self._get_page_text(input)
41
- return prompt.format_map(SafeDict(input))
42
-
43
- def rule_input(self, input: dict) -> str:
44
- return input.get('context', "")
45
-
46
- def _get_page_text(self, input: dict) -> str:
47
- url = input.get("content", "")
48
- exclusions = input.get("exclude", {})
49
- response = requests.get(url)
50
- response.raise_for_status()
51
- soup = BeautifulSoup(response.content, 'html5lib')
52
- classes_to_exclude = exclusions.get("classes", [])
53
- ids_to_exclude = exclusions.get("ids", [])
54
- for class_name in classes_to_exclude:
55
- for element in soup.find_all(class_=class_name):
56
- element.extract()
57
- for id_name in ids_to_exclude:
58
- for element in soup.find_all(id=id_name):
59
- element.extract()
60
- for script in soup(["script", "noscript", "style", "head", "footer", "iframe"]):
61
- script.extract()
62
- return re.sub(' +', ' ', soup.get_text())
63
-
64
-
65
- class AgentDescriptor:
66
- # Dictionary to hold all agent strategies
67
- _list: dict[str,AgentDescriptorStrategy] = {
68
- "default": DefaultAgentDescriptor(),
69
- "url2text": URL2TextAgentDescriptor(),
70
- }
71
-
72
- # Functions to manage strategies
73
- @staticmethod
74
- def add_strategy(name: str, strategy: AgentDescriptorStrategy):
75
- """_summary_
76
- add a new strategy to the dictionary
77
- Args:
78
- name (str): name of the strategy, in lowercase
79
- strategy (AgentDescriptorStrategy): class implementing the strategy
80
- Examples:
81
- AgentDescriptor.add_strategy("custom_agent_descriptor", CustomAgentDescriptor())
82
- """
83
- AgentDescriptor._list[name.lower()] = strategy
84
-
85
- @staticmethod
86
- def get_strategy(name: str) -> AgentDescriptorStrategy:
87
- return AgentDescriptor._list.get(name.lower(), DefaultAgentDescriptor())
88
-
89
- def __init__(self, llm: LlmInterface, prompt: str, mode: str, rules: LlmRules = None):
90
- self.__prompt = prompt
91
- self.__llm = llm
92
- self.rules= rules
93
- self.strategy = self.get_strategy(mode) # Selects the strategy from the dictionary
94
-
95
- async def __create_prompt(self, input_dict: dict):
96
- input_data = json.loads(input_dict.get("input", {}))
97
- system = self.strategy.enrich_prompt(self.__prompt, input_data)
98
- if self.rules:
99
- rule_input = self.strategy.rule_input(input_data)
100
- rules_prompt = await get_rules(self.__llm.get_embeddings(), self.rules, rule_input)
101
- system += rules_prompt
102
- return ChatPromptTemplate.from_messages(
103
- [
104
- ("system", system),
105
- ("user", input_data.get("content", ""))
106
- ]
107
- )
108
-
109
- def __create_agent_descriptor(self, content) -> RunnableSerializable[Any, Any]:
110
- content = json.loads(content)
111
- agent = (
112
- {
113
- "input": lambda x: x["input"],
114
- }
115
- | RunnableLambda(self.__create_prompt)
116
- | self.__llm.get_llm()
117
- )
118
- return agent
119
-
120
- async def run_agent(self, content) -> Any:
121
- agent_descriptor = self.__create_agent_descriptor(content)
122
- response: AIMessage = await agent_descriptor.ainvoke({"input": content})
123
- return response
1
+ import json, requests, re
2
+ from typing import Any
3
+ from abc import ABC, abstractmethod
4
+ from langchain_core.prompts import ChatPromptTemplate
5
+ from langchain_core.messages import AIMessage
6
+ from langchain_core.runnables import RunnableSerializable
7
+ from langchain_core.runnables import RunnableLambda
8
+ from bs4 import BeautifulSoup
9
+ from ws_bom_robot_app.llm.models.api import LlmRules
10
+ from ws_bom_robot_app.llm.providers.llm_manager import LlmInterface
11
+ from ws_bom_robot_app.llm.utils.agent import get_rules
12
+
13
+ # SafeDict helper class
14
+ class SafeDict(dict):
15
+ def __missing__(self, key):
16
+ return ''
17
+
18
+ # Strategy Interface
19
+ class AgentDescriptorStrategy(ABC):
20
+ @abstractmethod
21
+ def enrich_prompt(self, prompt: str, input: dict) -> str:
22
+ pass
23
+
24
+ @abstractmethod
25
+ def rule_input(self, input: dict) -> str:
26
+ pass
27
+
28
+ # Concrete Strategy for Default Agent
29
+ class DefaultAgentDescriptor(AgentDescriptorStrategy):
30
+ def enrich_prompt(self, prompt: str, input: dict) -> str:
31
+ # Default enrichment logic (could be minimal or no-op)
32
+ return prompt.format_map(SafeDict(input))
33
+
34
+ def rule_input(self, input: dict) -> str:
35
+ return input.get('content', "")
36
+
37
+ # Concrete Strategy for URL2Text Agent
38
+ class URL2TextAgentDescriptor(AgentDescriptorStrategy):
39
+ def enrich_prompt(self, prompt: str, input: dict) -> str:
40
+ input["context"] = self._get_page_text(input)
41
+ return prompt.format_map(SafeDict(input))
42
+
43
+ def rule_input(self, input: dict) -> str:
44
+ return input.get('context', "")
45
+
46
+ def _get_page_text(self, input: dict) -> str:
47
+ url = input.get("content", "")
48
+ exclusions = input.get("exclude", {})
49
+ response = requests.get(url)
50
+ response.raise_for_status()
51
+ soup = BeautifulSoup(response.content, 'html5lib')
52
+ classes_to_exclude = exclusions.get("classes", [])
53
+ ids_to_exclude = exclusions.get("ids", [])
54
+ for class_name in classes_to_exclude:
55
+ for element in soup.find_all(class_=class_name):
56
+ element.extract()
57
+ for id_name in ids_to_exclude:
58
+ for element in soup.find_all(id=id_name):
59
+ element.extract()
60
+ for script in soup(["script", "noscript", "style", "head", "footer", "iframe"]):
61
+ script.extract()
62
+ return re.sub(' +', ' ', soup.get_text())
63
+
64
+
65
+ class AgentDescriptor:
66
+ # Dictionary to hold all agent strategies
67
+ _list: dict[str,AgentDescriptorStrategy] = {
68
+ "default": DefaultAgentDescriptor(),
69
+ "url2text": URL2TextAgentDescriptor(),
70
+ }
71
+
72
+ # Functions to manage strategies
73
+ @staticmethod
74
+ def add_strategy(name: str, strategy: AgentDescriptorStrategy):
75
+ """_summary_
76
+ add a new strategy to the dictionary
77
+ Args:
78
+ name (str): name of the strategy, in lowercase
79
+ strategy (AgentDescriptorStrategy): class implementing the strategy
80
+ Examples:
81
+ AgentDescriptor.add_strategy("custom_agent_descriptor", CustomAgentDescriptor())
82
+ """
83
+ AgentDescriptor._list[name.lower()] = strategy
84
+
85
+ @staticmethod
86
+ def get_strategy(name: str) -> AgentDescriptorStrategy:
87
+ return AgentDescriptor._list.get(name.lower(), DefaultAgentDescriptor())
88
+
89
+ def __init__(self, llm: LlmInterface, prompt: str, mode: str, rules: LlmRules = None):
90
+ self.__prompt = prompt
91
+ self.__llm = llm
92
+ self.rules= rules
93
+ self.strategy = self.get_strategy(mode) # Selects the strategy from the dictionary
94
+
95
+ async def __create_prompt(self, input_dict: dict):
96
+ input_data = json.loads(input_dict.get("input", {}))
97
+ system = self.strategy.enrich_prompt(self.__prompt, input_data)
98
+ if self.rules:
99
+ rule_input = self.strategy.rule_input(input_data)
100
+ rules_prompt = await get_rules(self.__llm.get_embeddings(), self.rules, rule_input)
101
+ system += rules_prompt
102
+ return ChatPromptTemplate.from_messages(
103
+ [
104
+ ("system", system),
105
+ ("user", input_data.get("content", ""))
106
+ ]
107
+ )
108
+
109
+ def __create_agent_descriptor(self, content) -> RunnableSerializable[Any, Any]:
110
+ content = json.loads(content)
111
+ agent = (
112
+ {
113
+ "input": lambda x: x["input"],
114
+ }
115
+ | RunnableLambda(self.__create_prompt)
116
+ | self.__llm.get_llm()
117
+ )
118
+ return agent
119
+
120
+ async def run_agent(self, content) -> Any:
121
+ agent_descriptor = self.__create_agent_descriptor(content)
122
+ response: AIMessage = await agent_descriptor.ainvoke({"input": content})
123
+ return response
@@ -1,176 +1,176 @@
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
- from langchain_core.callbacks.base import AsyncCallbackHandler
9
- from langchain_core.outputs import ChatGenerationChunk, GenerationChunk
10
- from langchain_core.messages import BaseMessage, AIMessage
11
- import json, logging, re
12
-
13
- # Here is a custom handler that will print the tokens to stdout.
14
- # Instead of printing to stdout you can send the data elsewhere; e.g., to a streaming API response
15
-
16
-
17
- def _parse_token(llm:str,token: str) -> str:
18
- """Parses the token based on the LLM provider."""
19
- if llm == "anthropic" and isinstance(token, list):
20
- first = token[0]
21
- if 'text' in first:
22
- token = first['text']
23
- else:
24
- #[{'id': 'toolu_01GGLwJcrQ8PvFMUkQPGu8n7', 'input': {}, 'name': 'document_retriever_xxx', 'type': 'tool_use', 'index': 1}]
25
- token = ""
26
- return token
27
-
28
- class AgentHandler(AsyncCallbackHandler):
29
-
30
- def __init__(self, queue: Queue, llm:str, threadId: str = None) -> None:
31
- super().__init__()
32
- self._threadId = threadId
33
- self.queue = queue
34
- self.llm = llm
35
- self.__started: bool = False
36
- # on new token event
37
- self.stream_buffer = "" # accumulates text that hasn't been processed yet
38
- self.in_json_block = False
39
- self.json_buffer = ""
40
- self.json_start_regex = re.compile(r'(`{1,3}\s*json\b)') # detect a potential json start fence.
41
- self.json_end_regex = re.compile(r'(`{1,3})') # an end fence (one to three backticks).
42
- self.stream_cut_last_output_chunk_size = 16 # safe cut last chunk size to output if no markers are found
43
- async def on_chat_model_start(self, serialized, messages, *, run_id, parent_run_id = None, tags = None, metadata = None, **kwargs):
44
- if not self.__started:
45
- self.__started = True
46
- firstChunk = {
47
- "type": "info",
48
- "threadId": self._threadId,
49
- }
50
- await self.queue.put(print_json(firstChunk))
51
-
52
- async def on_llm_new_token(
53
- self,
54
- token: str,
55
- *,
56
- chunk: Optional[Union[GenerationChunk, ChatGenerationChunk]] = None,
57
- run_id: UUID,
58
- parent_run_id: Optional[UUID] = None,
59
- tags: Optional[List[str]] = None,
60
- **kwargs: Any,
61
- ) -> None:
62
- if token and "llm_chain" not in (tags or []):
63
- token = _parse_token(self.llm,token)
64
- if token:
65
- self.stream_buffer += token # append new data to pending buffer
66
- if not self.in_json_block:
67
- # search for the start of a json block.
68
- start_match = self.json_start_regex.search(self.stream_buffer)
69
- if start_match:
70
- start_index = start_match.start()
71
- # everything before the start marker is normal content.
72
- if start_index > 0:
73
- _before = self.stream_buffer[:start_index].replace('`','').strip() # remove eventual preceding backticks.
74
- if _before:
75
- await self.queue.put(print_string(_before))
76
- # remove the start marker from pending.
77
- self.stream_buffer = self.stream_buffer[start_match.end():]
78
- # switch into json mode.
79
- self.in_json_block = True
80
- self.json_buffer = ""
81
- else:
82
- # no json start marker found. It might be because the marker is split between chunks.
83
- # to avoid losing potential marker fragments, output what we can safely process:
84
- # if the pending text is long, we output most of it except the last few characters.
85
- if len(self.stream_buffer) > self.stream_cut_last_output_chunk_size:
86
- safe_cut = self.stream_buffer[:-3]
87
- if safe_cut.startswith('`'):
88
- safe_cut = safe_cut[1:]
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():].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_tool_end(self, output, *, run_id, parent_run_id = None, tags = None, **kwargs):
114
- if "stream" in (tags or []):
115
- await self.queue.put(print_json(output))
116
-
117
- async def on_agent_finish(
118
- self,
119
- finish: AgentFinish,
120
- *,
121
- run_id: UUID,
122
- parent_run_id: UUID = None,
123
- tags: List[str] = None,
124
- **kwargs: Any,
125
- ) -> None:
126
- # end-of-stream: flush any remaining text
127
- if self.in_json_block:
128
- try:
129
- data = json.loads(self.json_buffer)
130
- await self.queue.put(print_json(data))
131
- except json.JSONDecodeError as e :
132
- logging.error(f"on_agent_finish: invalid json: {e} | {self.json_buffer}")
133
- #await self.queue.put(print_string(self.json_buffer))
134
- elif self.stream_buffer:
135
- await self.queue.put(print_string(self.stream_buffer))
136
-
137
- finalChunk = {"type": "end"}
138
- await self.queue.put(print_json(finalChunk))
139
- await self.queue.put(None)
140
-
141
-
142
- class RawAgentHandler(AsyncCallbackHandler):
143
-
144
- def __init__(self,queue: Queue, llm: str) -> None:
145
- super().__init__()
146
- self.queue = queue
147
- self.llm = llm
148
-
149
- async def on_llm_new_token(
150
- self,
151
- token: str,
152
- *,
153
- chunk: Optional[Union[GenerationChunk, ChatGenerationChunk]] = None,
154
- run_id: UUID,
155
- parent_run_id: Optional[UUID] = None,
156
- tags: Optional[List[str]] = None,
157
- **kwargs: Any,
158
- ) -> None:
159
- """Handles new tokens during streaming."""
160
- if token: # only process non-empty tokens
161
- await self.queue.put(_parse_token(self.llm,token))
162
-
163
- async def on_tool_end(self, output, *, run_id, parent_run_id = None, tags = None, **kwargs):
164
- if "stream" in (tags or []):
165
- await self.queue.put(print_json(output))
166
-
167
- async def on_agent_finish(
168
- self,
169
- finish: AgentFinish,
170
- *,
171
- run_id: UUID,
172
- parent_run_id: UUID = None,
173
- tags: List[str] = None,
174
- **kwargs: Any,
175
- ) -> None:
176
- await self.queue.put(None)
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
+ from langchain_core.callbacks.base import AsyncCallbackHandler
9
+ from langchain_core.outputs import ChatGenerationChunk, GenerationChunk
10
+ from langchain_core.messages import BaseMessage, AIMessage
11
+ import json, logging, re
12
+
13
+ # Here is a custom handler that will print the tokens to stdout.
14
+ # Instead of printing to stdout you can send the data elsewhere; e.g., to a streaming API response
15
+
16
+
17
+ def _parse_token(llm:str,token: str) -> str:
18
+ """Parses the token based on the LLM provider."""
19
+ if llm == "anthropic" and isinstance(token, list):
20
+ first = token[0]
21
+ if 'text' in first:
22
+ token = first['text']
23
+ else:
24
+ #[{'id': 'toolu_01GGLwJcrQ8PvFMUkQPGu8n7', 'input': {}, 'name': 'document_retriever_xxx', 'type': 'tool_use', 'index': 1}]
25
+ token = ""
26
+ return token
27
+
28
+ class AgentHandler(AsyncCallbackHandler):
29
+
30
+ def __init__(self, queue: Queue, llm:str, threadId: str = None) -> None:
31
+ super().__init__()
32
+ self._threadId = threadId
33
+ self.queue = queue
34
+ self.llm = llm
35
+ self.__started: bool = False
36
+ # on new token event
37
+ self.stream_buffer = "" # accumulates text that hasn't been processed yet
38
+ self.in_json_block = False
39
+ self.json_buffer = ""
40
+ self.json_start_regex = re.compile(r'(`{1,3}\s*json\b)') # detect a potential json start fence.
41
+ self.json_end_regex = re.compile(r'(`{1,3})') # an end fence (one to three backticks).
42
+ self.stream_cut_last_output_chunk_size = 16 # safe cut last chunk size to output if no markers are found
43
+ async def on_chat_model_start(self, serialized, messages, *, run_id, parent_run_id = None, tags = None, metadata = None, **kwargs):
44
+ if not self.__started:
45
+ self.__started = True
46
+ firstChunk = {
47
+ "type": "info",
48
+ "threadId": self._threadId,
49
+ }
50
+ await self.queue.put(print_json(firstChunk))
51
+
52
+ async def on_llm_new_token(
53
+ self,
54
+ token: str,
55
+ *,
56
+ chunk: Optional[Union[GenerationChunk, ChatGenerationChunk]] = None,
57
+ run_id: UUID,
58
+ parent_run_id: Optional[UUID] = None,
59
+ tags: Optional[List[str]] = None,
60
+ **kwargs: Any,
61
+ ) -> None:
62
+ if token and "llm_chain" not in (tags or []):
63
+ token = _parse_token(self.llm,token)
64
+ if token:
65
+ self.stream_buffer += token # append new data to pending buffer
66
+ if not self.in_json_block:
67
+ # search for the start of a json block.
68
+ start_match = self.json_start_regex.search(self.stream_buffer)
69
+ if start_match:
70
+ start_index = start_match.start()
71
+ # everything before the start marker is normal content.
72
+ if start_index > 0:
73
+ _before = self.stream_buffer[:start_index].replace('`','').strip() # remove eventual preceding backticks.
74
+ if _before:
75
+ await self.queue.put(print_string(_before))
76
+ # remove the start marker from pending.
77
+ self.stream_buffer = self.stream_buffer[start_match.end():]
78
+ # switch into json mode.
79
+ self.in_json_block = True
80
+ self.json_buffer = ""
81
+ else:
82
+ # no json start marker found. It might be because the marker is split between chunks.
83
+ # to avoid losing potential marker fragments, output what we can safely process:
84
+ # if the pending text is long, we output most of it except the last few characters.
85
+ if len(self.stream_buffer) > self.stream_cut_last_output_chunk_size:
86
+ safe_cut = self.stream_buffer[:-3]
87
+ if safe_cut.startswith('`'):
88
+ safe_cut = safe_cut[1:]
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():].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_tool_end(self, output, *, run_id, parent_run_id = None, tags = None, **kwargs):
114
+ if "stream" in (tags or []):
115
+ await self.queue.put(print_json(output))
116
+
117
+ async def on_agent_finish(
118
+ self,
119
+ finish: AgentFinish,
120
+ *,
121
+ run_id: UUID,
122
+ parent_run_id: UUID = None,
123
+ tags: List[str] = None,
124
+ **kwargs: Any,
125
+ ) -> None:
126
+ # end-of-stream: flush any remaining text
127
+ if self.in_json_block:
128
+ try:
129
+ data = json.loads(self.json_buffer)
130
+ await self.queue.put(print_json(data))
131
+ except json.JSONDecodeError as e :
132
+ logging.error(f"on_agent_finish: invalid json: {e} | {self.json_buffer}")
133
+ #await self.queue.put(print_string(self.json_buffer))
134
+ elif self.stream_buffer:
135
+ await self.queue.put(print_string(self.stream_buffer))
136
+
137
+ finalChunk = {"type": "end"}
138
+ await self.queue.put(print_json(finalChunk))
139
+ await self.queue.put(None)
140
+
141
+
142
+ class RawAgentHandler(AsyncCallbackHandler):
143
+
144
+ def __init__(self,queue: Queue, llm: str) -> None:
145
+ super().__init__()
146
+ self.queue = queue
147
+ self.llm = llm
148
+
149
+ async def on_llm_new_token(
150
+ self,
151
+ token: str,
152
+ *,
153
+ chunk: Optional[Union[GenerationChunk, ChatGenerationChunk]] = None,
154
+ run_id: UUID,
155
+ parent_run_id: Optional[UUID] = None,
156
+ tags: Optional[List[str]] = None,
157
+ **kwargs: Any,
158
+ ) -> None:
159
+ """Handles new tokens during streaming."""
160
+ if token: # only process non-empty tokens
161
+ await self.queue.put(_parse_token(self.llm,token))
162
+
163
+ async def on_tool_end(self, output, *, run_id, parent_run_id = None, tags = None, **kwargs):
164
+ if "stream" in (tags or []):
165
+ await self.queue.put(print_json(output))
166
+
167
+ async def on_agent_finish(
168
+ self,
169
+ finish: AgentFinish,
170
+ *,
171
+ run_id: UUID,
172
+ parent_run_id: UUID = None,
173
+ tags: List[str] = None,
174
+ **kwargs: Any,
175
+ ) -> None:
176
+ await self.queue.put(None)