langroid 0.18.2__py3-none-any.whl → 0.19.0__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.
- langroid/agent/base.py +5 -0
- langroid/agent/callbacks/chainlit.py +27 -0
- langroid/agent/chat_agent.py +5 -5
- langroid/agent/xml_tool_message.py +121 -24
- langroid/language_models/base.py +6 -0
- langroid/language_models/openai_gpt.py +98 -9
- langroid/utils/git_utils.py +3 -2
- {langroid-0.18.2.dist-info → langroid-0.19.0.dist-info}/METADATA +2 -2
- {langroid-0.18.2.dist-info → langroid-0.19.0.dist-info}/RECORD +12 -12
- {langroid-0.18.2.dist-info → langroid-0.19.0.dist-info}/WHEEL +1 -1
- pyproject.toml +4 -1
- {langroid-0.18.2.dist-info → langroid-0.19.0.dist-info}/LICENSE +0 -0
langroid/agent/base.py
CHANGED
@@ -108,6 +108,10 @@ def noop_fn(*args: List[Any], **kwargs: Dict[str, Any]) -> None:
|
|
108
108
|
pass
|
109
109
|
|
110
110
|
|
111
|
+
async def async_noop_fn(*args: List[Any], **kwargs: Dict[str, Any]) -> None:
|
112
|
+
pass
|
113
|
+
|
114
|
+
|
111
115
|
class Agent(ABC):
|
112
116
|
"""
|
113
117
|
An Agent is an abstraction that encapsulates mainly two components:
|
@@ -154,6 +158,7 @@ class Agent(ABC):
|
|
154
158
|
|
155
159
|
self.callbacks = SimpleNamespace(
|
156
160
|
start_llm_stream=lambda: noop_fn,
|
161
|
+
start_llm_stream_async=async_noop_fn,
|
157
162
|
cancel_llm_stream=noop_fn,
|
158
163
|
finish_llm_stream=noop_fn,
|
159
164
|
show_llm_response=noop_fn,
|
@@ -234,6 +234,7 @@ class ChainlitAgentCallbacks:
|
|
234
234
|
so we can alter the display of the first user message.
|
235
235
|
"""
|
236
236
|
agent.callbacks.start_llm_stream = self.start_llm_stream
|
237
|
+
agent.callbacks.start_llm_stream_async = self.start_llm_stream_async
|
237
238
|
agent.callbacks.cancel_llm_stream = self.cancel_llm_stream
|
238
239
|
agent.callbacks.finish_llm_stream = self.finish_llm_stream
|
239
240
|
agent.callbacks.show_llm_response = self.show_llm_response
|
@@ -304,6 +305,32 @@ class ChainlitAgentCallbacks:
|
|
304
305
|
|
305
306
|
return stream_token
|
306
307
|
|
308
|
+
async def start_llm_stream_async(self) -> Callable[[str], None]:
|
309
|
+
"""Returns a streaming fn that can be passed to the LLM class"""
|
310
|
+
self.stream = cl.Step(
|
311
|
+
id=self.curr_step.id if self.curr_step is not None else None,
|
312
|
+
name=self._entity_name("llm"),
|
313
|
+
type="llm",
|
314
|
+
parent_id=self._get_parent_id(),
|
315
|
+
)
|
316
|
+
self.last_step = self.stream
|
317
|
+
self.curr_step = None
|
318
|
+
logger.info(
|
319
|
+
f"""
|
320
|
+
Starting LLM stream for {self.agent.config.name}
|
321
|
+
id = {self.stream.id}
|
322
|
+
under parent {self._get_parent_id()}
|
323
|
+
"""
|
324
|
+
)
|
325
|
+
await self.stream.send() # type: ignore
|
326
|
+
|
327
|
+
async def stream_token(t: str) -> None:
|
328
|
+
if self.stream is None:
|
329
|
+
raise ValueError("Stream not initialized")
|
330
|
+
await self.stream.stream_token(t)
|
331
|
+
|
332
|
+
return stream_token
|
333
|
+
|
307
334
|
def cancel_llm_stream(self) -> None:
|
308
335
|
"""Called when cached response found."""
|
309
336
|
self.last_step = None
|
langroid/agent/chat_agent.py
CHANGED
@@ -9,7 +9,7 @@ from rich import print
|
|
9
9
|
from rich.console import Console
|
10
10
|
from rich.markup import escape
|
11
11
|
|
12
|
-
from langroid.agent.base import Agent, AgentConfig, noop_fn
|
12
|
+
from langroid.agent.base import Agent, AgentConfig, async_noop_fn, noop_fn
|
13
13
|
from langroid.agent.chat_document import ChatDocument
|
14
14
|
from langroid.agent.tool_message import ToolMessage
|
15
15
|
from langroid.agent.xml_tool_message import XMLToolMessage
|
@@ -969,10 +969,10 @@ class ChatAgent(Agent):
|
|
969
969
|
functions, fun_call, tools, force_tool = self._function_args()
|
970
970
|
assert self.llm is not None
|
971
971
|
|
972
|
-
|
972
|
+
streamer_async = async_noop_fn
|
973
973
|
if self.llm.get_stream():
|
974
|
-
|
975
|
-
self.llm.config.
|
974
|
+
streamer_async = await self.callbacks.start_llm_stream_async()
|
975
|
+
self.llm.config.streamer_async = streamer_async
|
976
976
|
|
977
977
|
response = await self.llm.achat(
|
978
978
|
messages,
|
@@ -989,7 +989,7 @@ class ChatAgent(Agent):
|
|
989
989
|
ChatDocument.from_LLMResponse(response, displayed=True),
|
990
990
|
),
|
991
991
|
)
|
992
|
-
self.llm.config.
|
992
|
+
self.llm.config.streamer_async = async_noop_fn
|
993
993
|
if response.cached:
|
994
994
|
self.callbacks.cancel_llm_stream()
|
995
995
|
self._render_llm_response(response)
|
@@ -3,12 +3,12 @@ from typing import Any, Dict, List, Optional
|
|
3
3
|
from lxml import etree
|
4
4
|
|
5
5
|
from langroid.agent.tool_message import ToolMessage
|
6
|
+
from langroid.pydantic_v1 import BaseModel
|
6
7
|
|
7
8
|
|
8
9
|
class XMLToolMessage(ToolMessage):
|
9
10
|
"""
|
10
11
|
Abstract class for tools formatted using XML instead of JSON.
|
11
|
-
Mainly tested for non-nested tool structures.
|
12
12
|
|
13
13
|
When a subclass defines a field with the attribute `verbatim=True`,
|
14
14
|
instructions are sent to the LLM to ensure the field's content is:
|
@@ -50,8 +50,11 @@ class XMLToolMessage(ToolMessage):
|
|
50
50
|
parser = etree.XMLParser(strip_cdata=False)
|
51
51
|
root = etree.fromstring(formatted_string.encode("utf-8"), parser=parser)
|
52
52
|
|
53
|
-
def parse_element(element: etree._Element) -> Any
|
53
|
+
def parse_element(element: etree._Element) -> Any:
|
54
54
|
# Skip elements starting with underscore
|
55
|
+
if element.tag.startswith("_"):
|
56
|
+
return {}
|
57
|
+
|
55
58
|
field_info = cls.__fields__.get(element.tag)
|
56
59
|
is_verbatim = field_info and field_info.field_info.extra.get(
|
57
60
|
"verbatim", False
|
@@ -72,8 +75,18 @@ class XMLToolMessage(ToolMessage):
|
|
72
75
|
# For non-code leaf elements, strip whitespace
|
73
76
|
return element.text.strip() if element.text else ""
|
74
77
|
else:
|
75
|
-
# For branch elements,
|
76
|
-
|
78
|
+
# For branch elements, handle potential lists or nested structures
|
79
|
+
children = [parse_element(child) for child in element]
|
80
|
+
if all(child.tag == element[0].tag for child in element):
|
81
|
+
# If all children have the same tag, treat as a list
|
82
|
+
return children
|
83
|
+
else:
|
84
|
+
# Otherwise, treat as a dictionary
|
85
|
+
result = {child.tag: parse_element(child) for child in element}
|
86
|
+
# Check if this corresponds to a nested Pydantic model
|
87
|
+
if field_info and issubclass(field_info.type_, BaseModel):
|
88
|
+
return field_info.type_(**result)
|
89
|
+
return result
|
77
90
|
|
78
91
|
result = parse_element(root)
|
79
92
|
if not isinstance(result, dict):
|
@@ -100,6 +113,24 @@ class XMLToolMessage(ToolMessage):
|
|
100
113
|
# Use Pydantic's parse_obj to create and validate the instance
|
101
114
|
return cls.parse_obj(parsed_data)
|
102
115
|
|
116
|
+
@classmethod
|
117
|
+
def find_verbatim_fields(
|
118
|
+
cls, prefix: str = "", parent_cls: Optional["BaseModel"] = None
|
119
|
+
) -> List[str]:
|
120
|
+
verbatim_fields = []
|
121
|
+
for field_name, field_info in (parent_cls or cls).__fields__.items():
|
122
|
+
full_name = f"{prefix}.{field_name}" if prefix else field_name
|
123
|
+
if (
|
124
|
+
field_info.field_info.extra.get("verbatim", False)
|
125
|
+
or field_name == "code"
|
126
|
+
):
|
127
|
+
verbatim_fields.append(full_name)
|
128
|
+
if issubclass(field_info.type_, BaseModel):
|
129
|
+
verbatim_fields.extend(
|
130
|
+
cls.find_verbatim_fields(full_name, field_info.type_)
|
131
|
+
)
|
132
|
+
return verbatim_fields
|
133
|
+
|
103
134
|
@classmethod
|
104
135
|
def format_instructions(cls, tool: bool = False) -> str:
|
105
136
|
"""
|
@@ -123,14 +154,69 @@ class XMLToolMessage(ToolMessage):
|
|
123
154
|
"""
|
124
155
|
|
125
156
|
preamble = "Placeholders:\n"
|
157
|
+
xml_format = f"Formatting example:\n\n<{cls.Config.root_element}>\n"
|
158
|
+
|
159
|
+
def format_field(
|
160
|
+
field_name: str,
|
161
|
+
field_type: type,
|
162
|
+
indent: str = "",
|
163
|
+
path: str = "",
|
164
|
+
) -> None:
|
165
|
+
nonlocal preamble, xml_format
|
166
|
+
current_path = f"{path}.{field_name}" if path else field_name
|
167
|
+
|
168
|
+
if issubclass(field_type, BaseModel):
|
169
|
+
preamble += (
|
170
|
+
f"{field_name.upper()} = [nested structure for {field_name}]\n"
|
171
|
+
)
|
172
|
+
xml_format += f"{indent}<{field_name}>\n"
|
173
|
+
for sub_field, sub_field_info in field_type.__fields__.items():
|
174
|
+
format_field(
|
175
|
+
sub_field, sub_field_info.type_, indent + " ", current_path
|
176
|
+
)
|
177
|
+
xml_format += f"{indent}</{field_name}>\n"
|
178
|
+
elif issubclass(field_type, List):
|
179
|
+
item_type = getattr(field_type, "__args__", [Any])[0]
|
180
|
+
preamble += f"{field_name.upper()} = [list of {item_type.__name__}]\n"
|
181
|
+
xml_format += f"{indent}<{field_name}>\n"
|
182
|
+
xml_format += f"{indent} <item>[{item_type.__name__} value]</item>\n"
|
183
|
+
xml_format += f"{indent} ...\n"
|
184
|
+
xml_format += f"{indent}</{field_name}>\n"
|
185
|
+
elif issubclass(field_type, Dict):
|
186
|
+
key_type, value_type = getattr(field_type, "__args__", [Any, Any])
|
187
|
+
preamble += (
|
188
|
+
f"{field_name.upper()} = "
|
189
|
+
f"[dictionary with {key_type.__name__} keys and "
|
190
|
+
f"{value_type.__name__} values]\n"
|
191
|
+
)
|
192
|
+
xml_format += f"{indent}<{field_name}>\n"
|
193
|
+
xml_format += (
|
194
|
+
f"{indent} <{key_type.__name__}>"
|
195
|
+
f"[{value_type.__name__} value]"
|
196
|
+
f"</{key_type.__name__}>\n"
|
197
|
+
)
|
198
|
+
xml_format += f"{indent} ...\n"
|
199
|
+
xml_format += f"{indent}</{field_name}>\n"
|
200
|
+
else:
|
201
|
+
preamble += f"{field_name.upper()} = [value for {field_name}]\n"
|
202
|
+
if current_path in verbatim_fields:
|
203
|
+
xml_format += (
|
204
|
+
f"{indent}<{field_name}>"
|
205
|
+
f"<![CDATA[{{{field_name.upper()}}}]]></{field_name}>\n"
|
206
|
+
)
|
207
|
+
else:
|
208
|
+
xml_format += (
|
209
|
+
f"{indent}<{field_name}>"
|
210
|
+
f"{{{field_name.upper()}}}</{field_name}>\n"
|
211
|
+
)
|
212
|
+
|
213
|
+
verbatim_fields = cls.find_verbatim_fields()
|
214
|
+
|
126
215
|
for field in fields:
|
127
|
-
|
216
|
+
field_type = cls.__fields__[field].type_
|
217
|
+
format_field(field, field_type)
|
128
218
|
|
129
|
-
|
130
|
-
field
|
131
|
-
for field, field_info in cls.__fields__.items()
|
132
|
-
if field_info.field_info.extra.get("verbatim", False)
|
133
|
-
]
|
219
|
+
xml_format += f"</{cls.Config.root_element}>"
|
134
220
|
|
135
221
|
verbatim_alert = ""
|
136
222
|
if len(verbatim_fields) > 0:
|
@@ -141,13 +227,6 @@ class XMLToolMessage(ToolMessage):
|
|
141
227
|
must be written verbatim WITHOUT any modifications or escaping,
|
142
228
|
such as spaces, tabs, indents, newlines, quotes, etc.
|
143
229
|
"""
|
144
|
-
xml_format = f"Formatting example:\n\n<{cls.Config.root_element}>\n"
|
145
|
-
for field in fields:
|
146
|
-
if field == "code":
|
147
|
-
xml_format += f" <{field}><![CDATA[{{{field.upper()}}}]]></{field}>\n"
|
148
|
-
else:
|
149
|
-
xml_format += f" <{field}>{{{field.upper()}}}</{field}>\n"
|
150
|
-
xml_format += f"</{cls.Config.root_element}>"
|
151
230
|
|
152
231
|
examples_str = ""
|
153
232
|
if cls.examples():
|
@@ -177,17 +256,35 @@ class XMLToolMessage(ToolMessage):
|
|
177
256
|
Raises:
|
178
257
|
ValueError: If the result from etree.tostring is not a string.
|
179
258
|
"""
|
259
|
+
|
260
|
+
def create_element(
|
261
|
+
parent: etree._Element, name: str, value: Any, path: str = ""
|
262
|
+
) -> None:
|
263
|
+
elem = etree.SubElement(parent, name)
|
264
|
+
current_path = f"{path}.{name}" if path else name
|
265
|
+
|
266
|
+
if isinstance(value, list):
|
267
|
+
for item in value:
|
268
|
+
create_element(elem, "item", item, current_path)
|
269
|
+
elif isinstance(value, dict):
|
270
|
+
for k, v in value.items():
|
271
|
+
create_element(elem, k, v, current_path)
|
272
|
+
elif isinstance(value, BaseModel):
|
273
|
+
# Handle nested Pydantic models
|
274
|
+
for field_name, field_value in value.dict().items():
|
275
|
+
create_element(elem, field_name, field_value, current_path)
|
276
|
+
else:
|
277
|
+
if current_path in self.__class__.find_verbatim_fields():
|
278
|
+
elem.text = etree.CDATA(str(value))
|
279
|
+
else:
|
280
|
+
elem.text = str(value)
|
281
|
+
|
180
282
|
root = etree.Element(self.Config.root_element)
|
181
283
|
exclude_fields = self.Config.schema_extra.get("exclude", set())
|
182
284
|
for name, value in self.dict().items():
|
183
285
|
if name not in exclude_fields:
|
184
|
-
|
185
|
-
|
186
|
-
is_verbatim = field_info.field_info.extra.get("verbatim", False)
|
187
|
-
if is_verbatim:
|
188
|
-
elem.text = etree.CDATA(str(value))
|
189
|
-
else:
|
190
|
-
elem.text = str(value)
|
286
|
+
create_element(root, name, value)
|
287
|
+
|
191
288
|
result = etree.tostring(root, encoding="unicode", pretty_print=True)
|
192
289
|
if not isinstance(result, str):
|
193
290
|
raise ValueError("Unexpected non-string result from etree.tostring")
|
langroid/language_models/base.py
CHANGED
@@ -6,6 +6,7 @@ from datetime import datetime
|
|
6
6
|
from enum import Enum
|
7
7
|
from typing import (
|
8
8
|
Any,
|
9
|
+
Awaitable,
|
9
10
|
Callable,
|
10
11
|
Dict,
|
11
12
|
List,
|
@@ -33,6 +34,10 @@ def noop_fn(*args: List[Any], **kwargs: Dict[str, Any]) -> None:
|
|
33
34
|
pass
|
34
35
|
|
35
36
|
|
37
|
+
async def async_noop_fn(*args: List[Any], **kwargs: Dict[str, Any]) -> None:
|
38
|
+
pass
|
39
|
+
|
40
|
+
|
36
41
|
FunctionCallTypes = Literal["none", "auto"]
|
37
42
|
ToolChoiceTypes = Literal["none", "auto", "required"]
|
38
43
|
ToolTypes = Literal["function"]
|
@@ -45,6 +50,7 @@ class LLMConfig(BaseSettings):
|
|
45
50
|
|
46
51
|
type: str = "openai"
|
47
52
|
streamer: Optional[Callable[[Any], None]] = noop_fn
|
53
|
+
streamer_async: Optional[Callable[..., Awaitable[None]]] = async_noop_fn
|
48
54
|
api_base: str | None = None
|
49
55
|
formatter: None | str = None
|
50
56
|
timeout: int = 20 # timeout for API requests
|
@@ -688,7 +688,6 @@ class OpenAIGPT(LanguageModel):
|
|
688
688
|
completion: str = "",
|
689
689
|
function_args: str = "",
|
690
690
|
function_name: str = "",
|
691
|
-
is_async: bool = False,
|
692
691
|
) -> Tuple[bool, bool, str, str]:
|
693
692
|
"""Process state vars while processing a streaming API response.
|
694
693
|
Returns a tuple consisting of:
|
@@ -708,7 +707,98 @@ class OpenAIGPT(LanguageModel):
|
|
708
707
|
event_args = ""
|
709
708
|
event_fn_name = ""
|
710
709
|
event_tool_deltas: Optional[List[Dict[str, Any]]] = None
|
711
|
-
|
710
|
+
# The first two events in the stream of Azure OpenAI is useless.
|
711
|
+
# In the 1st: choices list is empty, in the 2nd: the dict delta has null content
|
712
|
+
if chat:
|
713
|
+
delta = choices[0].get("delta", {})
|
714
|
+
event_text = delta.get("content", "")
|
715
|
+
if "function_call" in delta and delta["function_call"] is not None:
|
716
|
+
if "name" in delta["function_call"]:
|
717
|
+
event_fn_name = delta["function_call"]["name"]
|
718
|
+
if "arguments" in delta["function_call"]:
|
719
|
+
event_args = delta["function_call"]["arguments"]
|
720
|
+
if "tool_calls" in delta and delta["tool_calls"] is not None:
|
721
|
+
# it's a list of deltas, usually just one
|
722
|
+
event_tool_deltas = delta["tool_calls"]
|
723
|
+
tool_deltas += event_tool_deltas
|
724
|
+
else:
|
725
|
+
event_text = choices[0]["text"]
|
726
|
+
if event_text:
|
727
|
+
completion += event_text
|
728
|
+
sys.stdout.write(Colors().GREEN + event_text)
|
729
|
+
sys.stdout.flush()
|
730
|
+
self.config.streamer(event_text)
|
731
|
+
if event_fn_name:
|
732
|
+
function_name = event_fn_name
|
733
|
+
has_function = True
|
734
|
+
sys.stdout.write(Colors().GREEN + "FUNC: " + event_fn_name + ": ")
|
735
|
+
sys.stdout.flush()
|
736
|
+
self.config.streamer(event_fn_name)
|
737
|
+
|
738
|
+
if event_args:
|
739
|
+
function_args += event_args
|
740
|
+
sys.stdout.write(Colors().GREEN + event_args)
|
741
|
+
sys.stdout.flush()
|
742
|
+
self.config.streamer(event_args)
|
743
|
+
|
744
|
+
if event_tool_deltas is not None:
|
745
|
+
# print out streaming tool calls, if not async
|
746
|
+
for td in event_tool_deltas:
|
747
|
+
if td["function"]["name"] is not None:
|
748
|
+
tool_fn_name = td["function"]["name"]
|
749
|
+
sys.stdout.write(
|
750
|
+
Colors().GREEN + "OAI-TOOL: " + tool_fn_name + ": "
|
751
|
+
)
|
752
|
+
sys.stdout.flush()
|
753
|
+
self.config.streamer(tool_fn_name)
|
754
|
+
if td["function"]["arguments"] != "":
|
755
|
+
tool_fn_args = td["function"]["arguments"]
|
756
|
+
sys.stdout.write(Colors().GREEN + tool_fn_args)
|
757
|
+
sys.stdout.flush()
|
758
|
+
self.config.streamer(tool_fn_args)
|
759
|
+
|
760
|
+
# show this delta in the stream
|
761
|
+
if choices[0].get("finish_reason", "") in [
|
762
|
+
"stop",
|
763
|
+
"function_call",
|
764
|
+
"tool_calls",
|
765
|
+
]:
|
766
|
+
# for function_call, finish_reason does not necessarily
|
767
|
+
# contain "function_call" as mentioned in the docs.
|
768
|
+
# So we check for "stop" or "function_call" here.
|
769
|
+
return True, has_function, function_name, function_args, completion
|
770
|
+
return False, has_function, function_name, function_args, completion
|
771
|
+
|
772
|
+
@no_type_check
|
773
|
+
async def _process_stream_event_async(
|
774
|
+
self,
|
775
|
+
event,
|
776
|
+
chat: bool = False,
|
777
|
+
tool_deltas: List[Dict[str, Any]] = [],
|
778
|
+
has_function: bool = False,
|
779
|
+
completion: str = "",
|
780
|
+
function_args: str = "",
|
781
|
+
function_name: str = "",
|
782
|
+
) -> Tuple[bool, bool, str, str]:
|
783
|
+
"""Process state vars while processing a streaming API response.
|
784
|
+
Returns a tuple consisting of:
|
785
|
+
- is_break: whether to break out of the loop
|
786
|
+
- has_function: whether the response contains a function_call
|
787
|
+
- function_name: name of the function
|
788
|
+
- function_args: args of the function
|
789
|
+
"""
|
790
|
+
# convert event obj (of type ChatCompletionChunk) to dict so rest of code,
|
791
|
+
# which expects dicts, works as it did before switching to openai v1.x
|
792
|
+
if not isinstance(event, dict):
|
793
|
+
event = event.model_dump()
|
794
|
+
|
795
|
+
choices = event.get("choices", [{}])
|
796
|
+
if len(choices) == 0:
|
797
|
+
choices = [{}]
|
798
|
+
event_args = ""
|
799
|
+
event_fn_name = ""
|
800
|
+
event_tool_deltas: Optional[List[Dict[str, Any]]] = None
|
801
|
+
silent = self.config.async_stream_quiet
|
712
802
|
# The first two events in the stream of Azure OpenAI is useless.
|
713
803
|
# In the 1st: choices list is empty, in the 2nd: the dict delta has null content
|
714
804
|
if chat:
|
@@ -730,21 +820,21 @@ class OpenAIGPT(LanguageModel):
|
|
730
820
|
if not silent:
|
731
821
|
sys.stdout.write(Colors().GREEN + event_text)
|
732
822
|
sys.stdout.flush()
|
733
|
-
self.config.
|
823
|
+
await self.config.streamer_async(event_text)
|
734
824
|
if event_fn_name:
|
735
825
|
function_name = event_fn_name
|
736
826
|
has_function = True
|
737
827
|
if not silent:
|
738
828
|
sys.stdout.write(Colors().GREEN + "FUNC: " + event_fn_name + ": ")
|
739
829
|
sys.stdout.flush()
|
740
|
-
self.config.
|
830
|
+
await self.config.streamer_async(event_fn_name)
|
741
831
|
|
742
832
|
if event_args:
|
743
833
|
function_args += event_args
|
744
834
|
if not silent:
|
745
835
|
sys.stdout.write(Colors().GREEN + event_args)
|
746
836
|
sys.stdout.flush()
|
747
|
-
self.config.
|
837
|
+
await self.config.streamer_async(event_args)
|
748
838
|
|
749
839
|
if event_tool_deltas is not None and not silent:
|
750
840
|
# print out streaming tool calls, if not async
|
@@ -755,12 +845,12 @@ class OpenAIGPT(LanguageModel):
|
|
755
845
|
Colors().GREEN + "OAI-TOOL: " + tool_fn_name + ": "
|
756
846
|
)
|
757
847
|
sys.stdout.flush()
|
758
|
-
self.config.
|
848
|
+
await self.config.streamer_async(tool_fn_name)
|
759
849
|
if td["function"]["arguments"] != "":
|
760
850
|
tool_fn_args = td["function"]["arguments"]
|
761
851
|
sys.stdout.write(Colors().GREEN + tool_fn_args)
|
762
852
|
sys.stdout.flush()
|
763
|
-
self.config.
|
853
|
+
await self.config.streamer_async(tool_fn_args)
|
764
854
|
|
765
855
|
# show this delta in the stream
|
766
856
|
if choices[0].get("finish_reason", "") in [
|
@@ -862,7 +952,7 @@ class OpenAIGPT(LanguageModel):
|
|
862
952
|
function_name,
|
863
953
|
function_args,
|
864
954
|
completion,
|
865
|
-
) = self.
|
955
|
+
) = await self._process_stream_event_async(
|
866
956
|
event,
|
867
957
|
chat=chat,
|
868
958
|
tool_deltas=tool_deltas,
|
@@ -870,7 +960,6 @@ class OpenAIGPT(LanguageModel):
|
|
870
960
|
completion=completion,
|
871
961
|
function_args=function_args,
|
872
962
|
function_name=function_name,
|
873
|
-
is_async=True,
|
874
963
|
)
|
875
964
|
if is_break:
|
876
965
|
break
|
langroid/utils/git_utils.py
CHANGED
@@ -127,8 +127,9 @@ def git_commit_file(repo: git.Repo, filepath: str, msg: str) -> None:
|
|
127
127
|
"""
|
128
128
|
try:
|
129
129
|
repo.index.add([filepath])
|
130
|
-
|
131
|
-
|
130
|
+
commit_msg = msg or f"Updated {filepath}"
|
131
|
+
repo.index.commit(commit_msg)
|
132
|
+
logger.info(f"Successfully committed {filepath}: {commit_msg}")
|
132
133
|
except git.GitCommandError as e:
|
133
134
|
logger.error(f"An error occurred while committing: {e}")
|
134
135
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: langroid
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.19.0
|
4
4
|
Summary: Harness LLMs with Multi-Agent Programming
|
5
5
|
License: MIT
|
6
6
|
Author: Prasad Chalasani
|
@@ -170,7 +170,7 @@ blog post from the LanceDB team
|
|
170
170
|
pharmacovigilance, see [blog post](https://langroid.github.io/langroid/blog/2024/08/12/malade-multi-agent-architecture-for-pharmacovigilance/)
|
171
171
|
|
172
172
|
|
173
|
-
We welcome contributions
|
173
|
+
We welcome contributions: See the [contributions](./CONTRIBUTING.md) document
|
174
174
|
for ideas on what to contribute.
|
175
175
|
|
176
176
|
Are you building LLM Applications, or want help with Langroid for your company,
|
@@ -1,10 +1,10 @@
|
|
1
1
|
langroid/__init__.py,sha256=z_fCOLQJPOw3LLRPBlFB5-2HyCjpPgQa4m4iY5Fvb8Y,1800
|
2
2
|
langroid/agent/__init__.py,sha256=ll0Cubd2DZ-fsCMl7e10hf9ZjFGKzphfBco396IKITY,786
|
3
|
-
langroid/agent/base.py,sha256=
|
3
|
+
langroid/agent/base.py,sha256=bk_N8J7yKdkuopM8z86MkngAfHt5z0qh1HWdcI4tbPw,64420
|
4
4
|
langroid/agent/batch.py,sha256=QZdlt1563hx4l3AXrCaGovE-PNG93M3DsvQAbDzdiS8,13705
|
5
5
|
langroid/agent/callbacks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
|
-
langroid/agent/callbacks/chainlit.py,sha256=
|
7
|
-
langroid/agent/chat_agent.py,sha256=
|
6
|
+
langroid/agent/callbacks/chainlit.py,sha256=JJXI3UGTyTDg2FFath4rqY1GyUo_0pbVBt8CZpvdtn4,23289
|
7
|
+
langroid/agent/chat_agent.py,sha256=GVuKXAwHACeTc1gC0y7Ywj8CmJfqu7InJ0AXtmz8nRw,50000
|
8
8
|
langroid/agent/chat_document.py,sha256=FZ_PkeKU5OVp1IUlMvspfqxIXzlyd7J_F32DSYrxQ7E,17651
|
9
9
|
langroid/agent/helpers.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
10
|
langroid/agent/junk,sha256=LxfuuW7Cijsg0szAzT81OjWWv1PMNI-6w_-DspVIO2s,339
|
@@ -46,7 +46,7 @@ langroid/agent/tools/retrieval_tool.py,sha256=2q2pfoYbZNfbWQ0McxrtmfF0ekGglIgRl-
|
|
46
46
|
langroid/agent/tools/rewind_tool.py,sha256=XAXL3BpNhCmBGYq_qi_sZfHJuIw7NY2jp4wnojJ7WRs,5606
|
47
47
|
langroid/agent/tools/segment_extract_tool.py,sha256=__srZ_VGYLVOdPrITUM8S0HpmX4q7r5FHWMDdHdEv8w,1440
|
48
48
|
langroid/agent/typed_task.py,sha256=oxja0Z3uLTv0BcR1xIMqDpo85MIGOruz4XsZ4ghjsW4,689
|
49
|
-
langroid/agent/xml_tool_message.py,sha256=
|
49
|
+
langroid/agent/xml_tool_message.py,sha256=WcwCWe9Ad3t4D0cExKOb6dhjL5YM6Cz3gsg3nnVoVh0,13347
|
50
50
|
langroid/agent_config.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
51
51
|
langroid/cachedb/__init__.py,sha256=icAT2s7Vhf-ZGUeqpDQGNU6ob6o0aFEyjwcxxUGRFjg,225
|
52
52
|
langroid/cachedb/base.py,sha256=ztVjB1DtN6pLCujCWnR6xruHxwVj3XkYniRTYAKKqk0,1354
|
@@ -67,10 +67,10 @@ langroid/language_models/.chainlit/config.toml,sha256=1t5lHORGzc2E6dkaO9P15jYHu2
|
|
67
67
|
langroid/language_models/.chainlit/translations/en-US.json,sha256=DAFz2HjOFFfboCStrUfKFg2BpplJPK_OOtixwF_GivY,9931
|
68
68
|
langroid/language_models/__init__.py,sha256=1sUGobooTqq77XC7LxKsvME0RgSd5GGmeyrPo9SMh4U,940
|
69
69
|
langroid/language_models/azure_openai.py,sha256=G4le3j4YLHV7IwgB2C37hO3MKijZ1KjynbYlEvpIF7Y,6214
|
70
|
-
langroid/language_models/base.py,sha256=
|
70
|
+
langroid/language_models/base.py,sha256=xMFg8syIHiA7ABRNWPXeFM1vGeY_1EN84ki8C3dycfw,22722
|
71
71
|
langroid/language_models/config.py,sha256=9Q8wk5a7RQr8LGMT_0WkpjY8S4ywK06SalVRjXlfCiI,378
|
72
72
|
langroid/language_models/mock_lm.py,sha256=HuiAvjHiCfffYF5xjFJUq945HVTW0QPbeUUctOnNCzQ,3868
|
73
|
-
langroid/language_models/openai_gpt.py,sha256=
|
73
|
+
langroid/language_models/openai_gpt.py,sha256=MwhdyF5RNfTupGL__jU1rx0LJQe2UvoKnnK4vTBHWDs,68799
|
74
74
|
langroid/language_models/prompt_formatter/__init__.py,sha256=2-5cdE24XoFDhifOLl8yiscohil1ogbP1ECkYdBlBsk,372
|
75
75
|
langroid/language_models/prompt_formatter/base.py,sha256=eDS1sgRNZVnoajwV_ZIha6cba5Dt8xjgzdRbPITwx3Q,1221
|
76
76
|
langroid/language_models/prompt_formatter/hf_formatter.py,sha256=PVJppmjRvD-2DF-XNC6mE05vTZ9wbu37SmXwZBQhad0,5055
|
@@ -113,7 +113,7 @@ langroid/utils/algorithms/graph.py,sha256=JbdpPnUOhw4-D6O7ou101JLA3xPCD0Lr3qaPoF
|
|
113
113
|
langroid/utils/configuration.py,sha256=LgjHGB0qgKKTwBaVt84APiqvJbz6pLwylUvHWYmzyP0,3303
|
114
114
|
langroid/utils/constants.py,sha256=vKIdkAJwyPT-bRA5MDPiOl7-EppBRmewRBIOcdXi4I4,959
|
115
115
|
langroid/utils/docker.py,sha256=kJQOLTgM0x9j9pgIIqp0dZNZCTvoUDhp6i8tYBq1Jr0,1105
|
116
|
-
langroid/utils/git_utils.py,sha256=
|
116
|
+
langroid/utils/git_utils.py,sha256=WnflJ3R3owhlD0LNdSJakcKhExcEehE1UW5jYVQl8JY,7955
|
117
117
|
langroid/utils/globals.py,sha256=Az9dOFqR6n9CoTYSqa2kLikQWS0oCQ9DFQIQAnG-2q8,1355
|
118
118
|
langroid/utils/llms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
119
119
|
langroid/utils/llms/strings.py,sha256=CSAX9Z6FQOLXOzbLMe_Opqtc3ruDAKTTk7cPqc6Blh0,263
|
@@ -137,8 +137,8 @@ langroid/vector_store/meilisearch.py,sha256=6frB7GFWeWmeKzRfLZIvzRjllniZ1cYj3Hmh
|
|
137
137
|
langroid/vector_store/momento.py,sha256=qR-zBF1RKVHQZPZQYW_7g-XpTwr46p8HJuYPCkfJbM4,10534
|
138
138
|
langroid/vector_store/qdrant_cloud.py,sha256=3im4Mip0QXLkR6wiqVsjV1QvhSElfxdFSuDKddBDQ-4,188
|
139
139
|
langroid/vector_store/qdrantdb.py,sha256=v88lqFkepADvlN6lByUj9I4NEKa9X9lWH16uTPPbYrE,17457
|
140
|
-
pyproject.toml,sha256=
|
141
|
-
langroid-0.
|
142
|
-
langroid-0.
|
143
|
-
langroid-0.
|
144
|
-
langroid-0.
|
140
|
+
pyproject.toml,sha256=XuRvlqpTV_ElysTBe0HFQQ18fa8kQ0PxA4Rb-LqQzZY,7251
|
141
|
+
langroid-0.19.0.dist-info/LICENSE,sha256=EgVbvA6VSYgUlvC3RvPKehSg7MFaxWDsFuzLOsPPfJg,1065
|
142
|
+
langroid-0.19.0.dist-info/METADATA,sha256=SVEU3MdLLeCzg4dzcVt9vrAIzujlCSTM3b9dosHuBug,56484
|
143
|
+
langroid-0.19.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
144
|
+
langroid-0.19.0.dist-info/RECORD,,
|
pyproject.toml
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "langroid"
|
3
|
-
version = "0.
|
3
|
+
version = "0.19.0"
|
4
4
|
description = "Harness LLMs with Multi-Agent Programming"
|
5
5
|
authors = ["Prasad Chalasani <pchalasani@gmail.com>"]
|
6
6
|
readme = "README.md"
|
@@ -163,6 +163,9 @@ pytest-asyncio = "^0.21.1"
|
|
163
163
|
pytest-postgresql = "^5.0.0"
|
164
164
|
pytest-mysql = "^2.4.2"
|
165
165
|
coverage = "^7.2.5"
|
166
|
+
pytest-xdist = "^3.6.1"
|
167
|
+
pytest-timeout = "^2.3.1"
|
168
|
+
pytest-cov = "^5.0.0"
|
166
169
|
|
167
170
|
[tool.poetry.group.docs]
|
168
171
|
optional = true
|
File without changes
|