langroid 0.18.1__py3-none-any.whl → 0.18.3__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 +4 -0
- langroid/agent/xml_tool_message.py +121 -24
- langroid/utils/git_utils.py +3 -2
- langroid/utils/system.py +32 -8
- {langroid-0.18.1.dist-info → langroid-0.18.3.dist-info}/METADATA +2 -2
- {langroid-0.18.1.dist-info → langroid-0.18.3.dist-info}/RECORD +9 -9
- {langroid-0.18.1.dist-info → langroid-0.18.3.dist-info}/WHEEL +1 -1
- pyproject.toml +4 -1
- {langroid-0.18.1.dist-info → langroid-0.18.3.dist-info}/LICENSE +0 -0
langroid/agent/base.py
CHANGED
@@ -91,6 +91,8 @@ class AgentConfig(BaseSettings):
|
|
91
91
|
show_stats: bool = True # show token usage/cost stats?
|
92
92
|
add_to_registry: bool = True # register agent in ObjectRegistry?
|
93
93
|
respond_tools_only: bool = False # respond only to tool messages (not plain text)?
|
94
|
+
# allow multiple tool messages in a single response?
|
95
|
+
allow_multiple_tools: bool = True
|
94
96
|
|
95
97
|
@validator("name")
|
96
98
|
def check_name_alphanum(cls, v: str) -> str:
|
@@ -1120,6 +1122,8 @@ class Agent(ABC):
|
|
1120
1122
|
# as a response to the tool message even though the tool was not intended
|
1121
1123
|
# for this agent.
|
1122
1124
|
return None
|
1125
|
+
if len(tools) > 1 and not self.config.allow_multiple_tools:
|
1126
|
+
return self.to_ChatDocument("ERROR: Use ONE tool at a time!")
|
1123
1127
|
if len(tools) == 0:
|
1124
1128
|
fallback_result = self.handle_message_fallback(msg)
|
1125
1129
|
if fallback_result is None:
|
@@ -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/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
|
|
langroid/utils/system.py
CHANGED
@@ -10,7 +10,7 @@ import socket
|
|
10
10
|
import traceback
|
11
11
|
import uuid
|
12
12
|
from pathlib import Path
|
13
|
-
from typing import Any
|
13
|
+
from typing import Any, Literal
|
14
14
|
|
15
15
|
logger = logging.getLogger(__name__)
|
16
16
|
|
@@ -186,24 +186,48 @@ def generate_unique_id() -> str:
|
|
186
186
|
return str(uuid.uuid4())
|
187
187
|
|
188
188
|
|
189
|
-
def create_file(
|
189
|
+
def create_file(
|
190
|
+
filepath: str | Path,
|
191
|
+
content: str = "",
|
192
|
+
if_exists: Literal["overwrite", "skip", "error", "append"] = "overwrite",
|
193
|
+
) -> None:
|
190
194
|
"""
|
191
|
-
Create a file with the given content
|
195
|
+
Create, overwrite or append to a file, with the given content
|
196
|
+
at the specified filepath.
|
192
197
|
If content is empty, it will simply touch to create an empty file.
|
193
198
|
|
194
199
|
Args:
|
195
200
|
filepath (str|Path): The relative path of the file to be created
|
196
201
|
content (str): The content to be written to the file
|
202
|
+
if_exists (Literal["overwrite", "skip", "error", "append"]):
|
203
|
+
Action to take if file exists
|
197
204
|
"""
|
198
|
-
Path(filepath)
|
199
|
-
|
200
|
-
|
205
|
+
filepath = Path(filepath)
|
206
|
+
filepath.parent.mkdir(parents=True, exist_ok=True)
|
207
|
+
|
208
|
+
if filepath.exists():
|
209
|
+
if if_exists == "skip":
|
210
|
+
logger.warning(f"File already exists, skipping: {filepath}")
|
211
|
+
return
|
212
|
+
elif if_exists == "error":
|
213
|
+
raise FileExistsError(f"File already exists: {filepath}")
|
214
|
+
elif if_exists == "append":
|
215
|
+
mode = "a"
|
216
|
+
else: # overwrite
|
217
|
+
mode = "w"
|
218
|
+
else:
|
219
|
+
mode = "w"
|
220
|
+
|
221
|
+
if content == "" and mode in ["a", "w"]:
|
222
|
+
filepath.touch()
|
223
|
+
logger.warning(f"Empty file created: {filepath}")
|
201
224
|
else:
|
202
225
|
# the newline = '\n` argument is used to ensure that
|
203
226
|
# newlines in the content are written as actual line breaks
|
204
|
-
with open(filepath,
|
227
|
+
with open(filepath, mode, newline="\n") as f:
|
205
228
|
f.write(content)
|
206
|
-
|
229
|
+
action = "appended to" if mode == "a" else "created/updated in"
|
230
|
+
logger.warning(f"Content {action}: {filepath}")
|
207
231
|
|
208
232
|
|
209
233
|
def read_file(path: str, line_numbers: bool = False) -> str:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: langroid
|
3
|
-
Version: 0.18.
|
3
|
+
Version: 0.18.3
|
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,6 +1,6 @@
|
|
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=tJFil49Us2wRv2oKbikqrmcBv6KAeM-UdZ7Psrr-jjE,64282
|
4
4
|
langroid/agent/batch.py,sha256=QZdlt1563hx4l3AXrCaGovE-PNG93M3DsvQAbDzdiS8,13705
|
5
5
|
langroid/agent/callbacks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
6
|
langroid/agent/callbacks/chainlit.py,sha256=oiZYfPyfkUfFqhVFBiobN3hAJEaYn0FBSglvNE7t17Q,22302
|
@@ -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
|
@@ -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
|
@@ -125,7 +125,7 @@ langroid/utils/output/printing.py,sha256=yzPJZN-8_jyOJmI9N_oLwEDfjMwVgk3IDiwnZ4e
|
|
125
125
|
langroid/utils/output/status.py,sha256=rzbE7mDJcgNNvdtylCseQcPGCGghtJvVq3lB-OPJ49E,1049
|
126
126
|
langroid/utils/pandas_utils.py,sha256=UctS986Jtl_MvU5rA7-GfrjEHXP7MNu8ePhepv0bTn0,755
|
127
127
|
langroid/utils/pydantic_utils.py,sha256=iRy7uQhHhQmIDZTTPNX5jXb6fqefMe9N67p3fPfOmTI,20624
|
128
|
-
langroid/utils/system.py,sha256=
|
128
|
+
langroid/utils/system.py,sha256=AiEehQy0K9c9qHdKsZRCscRrazDzuh5Tv3GRQsA0Cxg,8455
|
129
129
|
langroid/utils/types.py,sha256=4GrOnU3HLWh-UwaUPp7LlB3V413q3K5OSzc0ggDoQ6A,2510
|
130
130
|
langroid/utils/web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
131
131
|
langroid/utils/web/login.py,sha256=1iz9eUAHa87vpKIkzwkmFa00avwFWivDSAr7QUhK7U0,2528
|
@@ -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.18.
|
142
|
-
langroid-0.18.
|
143
|
-
langroid-0.18.
|
144
|
-
langroid-0.18.
|
140
|
+
pyproject.toml,sha256=Ox9dOuITyUORtt923h4uu74ZWh9d3fkN0lBjQAGdvZc,7251
|
141
|
+
langroid-0.18.3.dist-info/LICENSE,sha256=EgVbvA6VSYgUlvC3RvPKehSg7MFaxWDsFuzLOsPPfJg,1065
|
142
|
+
langroid-0.18.3.dist-info/METADATA,sha256=YWVqIJ_x-Y6duar3ohcC1SH9ErDDkcjzs6oZ0fXhSF0,56484
|
143
|
+
langroid-0.18.3.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
144
|
+
langroid-0.18.3.dist-info/RECORD,,
|
pyproject.toml
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "langroid"
|
3
|
-
version = "0.18.
|
3
|
+
version = "0.18.3"
|
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
|