langroid 0.8.0__py3-none-any.whl → 0.9.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 +353 -94
- langroid/agent/chat_agent.py +68 -9
- langroid/agent/chat_document.py +16 -7
- langroid/agent/openai_assistant.py +12 -1
- langroid/agent/special/lance_doc_chat_agent.py +25 -18
- langroid/agent/special/lance_rag/critic_agent.py +37 -5
- langroid/agent/special/lance_rag/query_planner_agent.py +102 -63
- langroid/agent/special/lance_tools.py +10 -2
- langroid/agent/task.py +156 -47
- langroid/agent/tool_message.py +12 -3
- langroid/agent/tools/__init__.py +5 -0
- langroid/agent/tools/orchestration.py +216 -0
- langroid/agent/tools/recipient_tool.py +6 -11
- langroid/agent/typed_task.py +19 -0
- langroid/language_models/base.py +3 -2
- langroid/mytypes.py +0 -1
- langroid/parsing/parse_json.py +19 -2
- langroid/utils/pydantic_utils.py +19 -0
- langroid/vector_store/base.py +3 -1
- langroid/vector_store/lancedb.py +2 -0
- {langroid-0.8.0.dist-info → langroid-0.9.0.dist-info}/METADATA +2 -1
- {langroid-0.8.0.dist-info → langroid-0.9.0.dist-info}/RECORD +25 -28
- pyproject.toml +2 -1
- langroid/agent/special/lance_rag_new/__init__.py +0 -9
- langroid/agent/special/lance_rag_new/critic_agent.py +0 -171
- langroid/agent/special/lance_rag_new/lance_rag_task.py +0 -144
- langroid/agent/special/lance_rag_new/query_planner_agent.py +0 -222
- langroid/agent/team.py +0 -1758
- {langroid-0.8.0.dist-info → langroid-0.9.0.dist-info}/LICENSE +0 -0
- {langroid-0.8.0.dist-info → langroid-0.9.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
# from langroid.agent.tool_message import ToolMessage
|
2
|
+
# from langroid.agent.task import Task
|
3
|
+
# from typing import Any
|
4
|
+
#
|
5
|
+
# class TypedTask:
|
6
|
+
# def __init__(self, Task: Task, input_type: Any, output_type: Any):
|
7
|
+
# self.Task = Task
|
8
|
+
# self.input_type = input_type
|
9
|
+
# self.output_type = output_type
|
10
|
+
#
|
11
|
+
# def run(self, input: Any) -> Any:
|
12
|
+
# if not isinstance(input, self.input_type):
|
13
|
+
# raise ValueError(f"Input must be of type {self.input_type}")
|
14
|
+
# output = self.Task.run(input)
|
15
|
+
# if not isinstance(output, self.output_type):
|
16
|
+
# raise ValueError(f"Output must be of type {self.output_type}")
|
17
|
+
# return output
|
18
|
+
#
|
19
|
+
#
|
langroid/language_models/base.py
CHANGED
@@ -294,8 +294,9 @@ class LLMResponse(BaseModel):
|
|
294
294
|
of the recipient name if specified.
|
295
295
|
|
296
296
|
Two cases:
|
297
|
-
(a) `message` contains "TO: <name> <content>", or
|
298
|
-
(b) `message` is empty and
|
297
|
+
(a) `message` contains addressing string "TO: <name> <content>", or
|
298
|
+
(b) `message` is empty and function_call/tool_call with explicit `recipient`
|
299
|
+
|
299
300
|
|
300
301
|
Returns:
|
301
302
|
(str): name of recipient, which may be empty string if no recipient
|
langroid/mytypes.py
CHANGED
langroid/parsing/parse_json.py
CHANGED
@@ -89,11 +89,28 @@ def parse_imperfect_json(json_string: str) -> Union[Dict[str, Any], List[Any]]:
|
|
89
89
|
|
90
90
|
# If ast.literal_eval fails or returns non-dict/list, try json.loads
|
91
91
|
try:
|
92
|
-
|
93
|
-
result = json.loads(
|
92
|
+
json_string = add_quotes(json_string)
|
93
|
+
result = json.loads(json_string)
|
94
94
|
if isinstance(result, (dict, list)):
|
95
95
|
return result
|
96
96
|
except json.JSONDecodeError:
|
97
|
+
try:
|
98
|
+
# fallback on yaml
|
99
|
+
yaml_result = yaml.safe_load(json_string)
|
100
|
+
if isinstance(yaml_result, (dict, list)):
|
101
|
+
return yaml_result
|
102
|
+
except yaml.YAMLError:
|
103
|
+
pass
|
104
|
+
|
105
|
+
try:
|
106
|
+
# last resort: try to repair the json using a lib
|
107
|
+
from json_repair import repair_json
|
108
|
+
|
109
|
+
repaired_json = repair_json(json_string)
|
110
|
+
result = json.loads(repaired_json)
|
111
|
+
if isinstance(result, (dict, list)):
|
112
|
+
return result
|
113
|
+
except Exception:
|
97
114
|
pass
|
98
115
|
|
99
116
|
# If all methods fail, raise ValueError
|
langroid/utils/pydantic_utils.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
import logging
|
2
|
+
from collections.abc import MutableMapping
|
2
3
|
from contextlib import contextmanager
|
3
4
|
from typing import (
|
4
5
|
Any,
|
@@ -21,6 +22,24 @@ from langroid.pydantic_v1 import BaseModel, ValidationError, create_model
|
|
21
22
|
logger = logging.getLogger(__name__)
|
22
23
|
|
23
24
|
|
25
|
+
def flatten_dict(
|
26
|
+
d: MutableMapping[str, Any], parent_key: str = "", sep: str = "."
|
27
|
+
) -> Dict[str, Any]:
|
28
|
+
"""Flatten a nested dictionary, using a separator in the keys.
|
29
|
+
Useful for pydantic_v1 models with nested fields -- first use
|
30
|
+
dct = mdl.model_dump()
|
31
|
+
to get a nested dictionary, then use this function to flatten it.
|
32
|
+
"""
|
33
|
+
items: List[Tuple[str, Any]] = []
|
34
|
+
for k, v in d.items():
|
35
|
+
new_key = f"{parent_key}{sep}{k}" if parent_key else k
|
36
|
+
if isinstance(v, MutableMapping):
|
37
|
+
items.extend(flatten_dict(v, new_key, sep=sep).items())
|
38
|
+
else:
|
39
|
+
items.append((new_key, v))
|
40
|
+
return dict(items)
|
41
|
+
|
42
|
+
|
24
43
|
def has_field(model_class: Type[BaseModel], field_name: str) -> bool:
|
25
44
|
"""Check if a Pydantic model class has a field with the given name."""
|
26
45
|
return field_name in model_class.__fields__
|
langroid/vector_store/base.py
CHANGED
@@ -15,6 +15,7 @@ from langroid.utils.configuration import settings
|
|
15
15
|
from langroid.utils.object_registry import ObjectRegistry
|
16
16
|
from langroid.utils.output.printing import print_long_text
|
17
17
|
from langroid.utils.pandas_utils import stringify
|
18
|
+
from langroid.utils.pydantic_utils import flatten_dict
|
18
19
|
|
19
20
|
logger = logging.getLogger(__name__)
|
20
21
|
|
@@ -136,7 +137,8 @@ class VectorStore(ABC):
|
|
136
137
|
"""Compute a result on a set of documents,
|
137
138
|
using a dataframe calc string like `df.groupby('state')['income'].mean()`.
|
138
139
|
"""
|
139
|
-
|
140
|
+
# convert each doc to a dict, using dotted paths for nested fields
|
141
|
+
dicts = [flatten_dict(doc.dict(by_alias=True)) for doc in docs]
|
140
142
|
df = pd.DataFrame(dicts)
|
141
143
|
|
142
144
|
try:
|
langroid/vector_store/lancedb.py
CHANGED
@@ -147,6 +147,8 @@ class LanceDB(VectorStore):
|
|
147
147
|
|
148
148
|
def _create_lance_schema(self, doc_cls: Type[Document]) -> Type[BaseModel]:
|
149
149
|
"""
|
150
|
+
NOTE: NOT USED, but leaving it here as it may be useful.
|
151
|
+
|
150
152
|
Create a subclass of LanceModel with fields:
|
151
153
|
- id (str)
|
152
154
|
- Vector field that has dims equal to
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: langroid
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.9.0
|
4
4
|
Summary: Harness LLMs with Multi-Agent Programming
|
5
5
|
License: MIT
|
6
6
|
Author: Prasad Chalasani
|
@@ -54,6 +54,7 @@ Requires-Dist: grpcio (>=1.62.1,<2.0.0)
|
|
54
54
|
Requires-Dist: halo (>=0.0.31,<0.0.32)
|
55
55
|
Requires-Dist: huggingface-hub (>=0.21.2,<0.22.0) ; extra == "hf-transformers" or extra == "all" or extra == "transformers"
|
56
56
|
Requires-Dist: jinja2 (>=3.1.2,<4.0.0)
|
57
|
+
Requires-Dist: json-repair (>=0.27.0,<0.28.0)
|
57
58
|
Requires-Dist: lancedb (>=0.8.2,<0.9.0) ; extra == "vecdbs" or extra == "lancedb"
|
58
59
|
Requires-Dist: litellm (>=1.30.1,<2.0.0) ; extra == "all" or extra == "litellm"
|
59
60
|
Requires-Dist: lxml (>=4.9.3,<5.0.0)
|
@@ -1,26 +1,22 @@
|
|
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=dCGYbT2s35OEgbYtuDWOBhpeJzjo2TPnbbG4NrjVv70,58201
|
4
4
|
langroid/agent/batch.py,sha256=feRA_yRG768ElOQjrKEefcRv6Aefd_yY7qktuYUQDwc,10040
|
5
5
|
langroid/agent/callbacks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
6
|
langroid/agent/callbacks/chainlit.py,sha256=Qedk1-CBCgo9PdaIa7AboLBFCTgAMg9q5nGmoqpZ378,22050
|
7
|
-
langroid/agent/chat_agent.py,sha256=
|
8
|
-
langroid/agent/chat_document.py,sha256=
|
7
|
+
langroid/agent/chat_agent.py,sha256=VUuYweOLuH4lokvZLY74WXIG4AlxRU4L907ox_hoRA4,48107
|
8
|
+
langroid/agent/chat_document.py,sha256=Cq-TbPefpxbx2QzGl-CUpIrCcEC34GHDGH6Fd0w7aYs,16693
|
9
9
|
langroid/agent/helpers.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
10
|
langroid/agent/junk,sha256=LxfuuW7Cijsg0szAzT81OjWWv1PMNI-6w_-DspVIO2s,339
|
11
|
-
langroid/agent/openai_assistant.py,sha256=
|
11
|
+
langroid/agent/openai_assistant.py,sha256=2rjCZw45ysNBEGNzQM4uf0bTC4KkatGYAWcVcW4xcek,34337
|
12
12
|
langroid/agent/special/__init__.py,sha256=gik_Xtm_zV7U9s30Mn8UX3Gyuy4jTjQe9zjiE3HWmEo,1273
|
13
13
|
langroid/agent/special/doc_chat_agent.py,sha256=8NPAhMnHkFUolQ8EHos40tz5Vwuz_m33NjUfjheXWXY,54569
|
14
|
-
langroid/agent/special/lance_doc_chat_agent.py,sha256=
|
14
|
+
langroid/agent/special/lance_doc_chat_agent.py,sha256=s8xoRs0gGaFtDYFUSIRchsgDVbS5Q3C2b2mr3V1Fd-Q,10419
|
15
15
|
langroid/agent/special/lance_rag/__init__.py,sha256=QTbs0IVE2ZgDg8JJy1zN97rUUg4uEPH7SLGctFNumk4,174
|
16
|
-
langroid/agent/special/lance_rag/critic_agent.py,sha256=
|
16
|
+
langroid/agent/special/lance_rag/critic_agent.py,sha256=lRHLBU9TWo4ixm_wEPUP9bCjW0iSIw7ZRe6u4D4v7jk,9519
|
17
17
|
langroid/agent/special/lance_rag/lance_rag_task.py,sha256=l_HQgrYY-CX2FwIsS961aEF3bYog3GDYo98fj0C0mSk,2889
|
18
|
-
langroid/agent/special/lance_rag/query_planner_agent.py,sha256
|
19
|
-
langroid/agent/special/
|
20
|
-
langroid/agent/special/lance_rag_new/critic_agent.py,sha256=9TV0_ILQ1jNeO9Pjeirz3VsUsCKeImyVHPu-fJub6cY,8340
|
21
|
-
langroid/agent/special/lance_rag_new/lance_rag_task.py,sha256=mKo4lCC1ff5Jt9BlxwclQ_y3omw2S_f0MVIJfNmXM6w,5267
|
22
|
-
langroid/agent/special/lance_rag_new/query_planner_agent.py,sha256=JqO_5fKW8HPn-zqKsZzX1sl05RgtcT63qpDMR9-q33A,10251
|
23
|
-
langroid/agent/special/lance_tools.py,sha256=BznV_r3LAFyybvBRa9KQ0oU7mPM3uQVfri7PFp7M_qc,1894
|
18
|
+
langroid/agent/special/lance_rag/query_planner_agent.py,sha256=-ZKareApPQD0EbwB1ABeZdhYAA2lJbwSVkfSuhMcKMk,11480
|
19
|
+
langroid/agent/special/lance_tools.py,sha256=qS8x4wi8mrqfbYV2ztFzrcxyhHQ0ZWOc-zkYiH7awj0,2105
|
24
20
|
langroid/agent/special/neo4j/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
25
21
|
langroid/agent/special/neo4j/csv_kg_chat.py,sha256=dRsAgMBa1H_EMI2YYgJR2Xyv1D7e4o3G9M64mTewq_c,6409
|
26
22
|
langroid/agent/special/neo4j/neo4j_chat_agent.py,sha256=dvZ4rrMWwKuOIOjC3iWQIe-cSxNXkaU_6W0rQII_4Q8,13347
|
@@ -36,21 +32,22 @@ langroid/agent/special/sql/utils/populate_metadata.py,sha256=1J22UsyEPKzwK0XlJZt
|
|
36
32
|
langroid/agent/special/sql/utils/system_message.py,sha256=qKLHkvQWRQodTtPLPxr1GSLUYUFASZU8x-ybV67cB68,1885
|
37
33
|
langroid/agent/special/sql/utils/tools.py,sha256=vFYysk6Vi7HJjII8B4RitA3pt_z3gkSglDNdhNVMiFc,1332
|
38
34
|
langroid/agent/special/table_chat_agent.py,sha256=d9v2wsblaRx7oMnKhLV7uO_ujvk9gh59pSGvBXyeyNc,9659
|
39
|
-
langroid/agent/task.py,sha256=
|
40
|
-
langroid/agent/
|
41
|
-
langroid/agent/
|
42
|
-
langroid/agent/tools/__init__.py,sha256=e-63cfwQNk_ftRKQwgDAJQK16QLbRVWDBILeXIc7wLk,402
|
35
|
+
langroid/agent/task.py,sha256=taSP80E_pcqa6OEGi9weOQXx7It8SZvgosfuVv4DQcw,80300
|
36
|
+
langroid/agent/tool_message.py,sha256=pArap87HNODwBVH2Dyu4Oi_OOhK0NH65pDUOHvIKV0E,10101
|
37
|
+
langroid/agent/tools/__init__.py,sha256=SL7oZ0wOO8PzViGhWzjqY2OipcxH3sGfVli2XJ5bpWg,539
|
43
38
|
langroid/agent/tools/duckduckgo_search_tool.py,sha256=NhsCaGZkdv28nja7yveAhSK_w6l_Ftym8agbrdzqgfo,1935
|
44
39
|
langroid/agent/tools/extract_tool.py,sha256=u5lL9rKBzaLBOrRyLnTAZ97pQ1uxyLP39XsWMnpaZpw,3789
|
45
40
|
langroid/agent/tools/generator_tool.py,sha256=y0fB0ZObjA0b3L0uSTtrqRCKHDUR95arBftqiUeKD2o,663
|
46
41
|
langroid/agent/tools/google_search_tool.py,sha256=y7b-3FtgXf0lfF4AYxrZ3K5pH2dhidvibUOAGBE--WI,1456
|
47
42
|
langroid/agent/tools/metaphor_search_tool.py,sha256=qj4gt453cLEX3EGW7nVzVu6X7LCdrwjSlcNY0qJW104,2489
|
48
43
|
langroid/agent/tools/note_tool.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
49
|
-
langroid/agent/tools/
|
44
|
+
langroid/agent/tools/orchestration.py,sha256=MN-y5erSiCnsPtI7aeD7rn5K3kXzeQnJHRlCkenkloY,7324
|
45
|
+
langroid/agent/tools/recipient_tool.py,sha256=0m2kQhYKTeGujAxhSPqH5z6hSAhVB_Dqour6uul2U30,9427
|
50
46
|
langroid/agent/tools/retrieval_tool.py,sha256=2q2pfoYbZNfbWQ0McxrtmfF0ekGglIgRl-6uF26pa-E,871
|
51
47
|
langroid/agent/tools/rewind_tool.py,sha256=XAXL3BpNhCmBGYq_qi_sZfHJuIw7NY2jp4wnojJ7WRs,5606
|
52
48
|
langroid/agent/tools/run_python_code.py,sha256=BvoxYzzHijU-p4703n2iVlt5BCieR1oMSy50w0tQZAg,1787
|
53
49
|
langroid/agent/tools/segment_extract_tool.py,sha256=__srZ_VGYLVOdPrITUM8S0HpmX4q7r5FHWMDdHdEv8w,1440
|
50
|
+
langroid/agent/typed_task.py,sha256=oxja0Z3uLTv0BcR1xIMqDpo85MIGOruz4XsZ4ghjsW4,689
|
54
51
|
langroid/agent_config.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
55
52
|
langroid/cachedb/__init__.py,sha256=icAT2s7Vhf-ZGUeqpDQGNU6ob6o0aFEyjwcxxUGRFjg,225
|
56
53
|
langroid/cachedb/base.py,sha256=ztVjB1DtN6pLCujCWnR6xruHxwVj3XkYniRTYAKKqk0,1354
|
@@ -71,7 +68,7 @@ langroid/language_models/.chainlit/config.toml,sha256=1t5lHORGzc2E6dkaO9P15jYHu2
|
|
71
68
|
langroid/language_models/.chainlit/translations/en-US.json,sha256=DAFz2HjOFFfboCStrUfKFg2BpplJPK_OOtixwF_GivY,9931
|
72
69
|
langroid/language_models/__init__.py,sha256=1sUGobooTqq77XC7LxKsvME0RgSd5GGmeyrPo9SMh4U,940
|
73
70
|
langroid/language_models/azure_openai.py,sha256=G4le3j4YLHV7IwgB2C37hO3MKijZ1KjynbYlEvpIF7Y,6214
|
74
|
-
langroid/language_models/base.py,sha256=
|
71
|
+
langroid/language_models/base.py,sha256=ytJ_0Jw5erbqrqLPp4JMCo_nIkwzUvBqoKUr8Sae9Qg,21792
|
75
72
|
langroid/language_models/config.py,sha256=9Q8wk5a7RQr8LGMT_0WkpjY8S4ywK06SalVRjXlfCiI,378
|
76
73
|
langroid/language_models/mock_lm.py,sha256=2Ka05SVGSUy096bsa2AyjaqC5jmcFoe7HycpdnICTIw,3031
|
77
74
|
langroid/language_models/openai_gpt.py,sha256=Bv-FCva9q0oO85VSqNpaxEYI8zPwVXpHmmfV7O8QRhU,61325
|
@@ -80,7 +77,7 @@ langroid/language_models/prompt_formatter/base.py,sha256=eDS1sgRNZVnoajwV_ZIha6c
|
|
80
77
|
langroid/language_models/prompt_formatter/hf_formatter.py,sha256=TFL6ppmeQWnzr6CKQzRZFYY810zE1mr8DZnhw6i85ok,5217
|
81
78
|
langroid/language_models/prompt_formatter/llama2_formatter.py,sha256=YdcO88qyBeuMENVIVvVqSYuEpvYSTndUe_jd6hVTko4,2899
|
82
79
|
langroid/language_models/utils.py,sha256=o6Zo2cnnvKrfSgF26knVQ1xkSxEoE7yN85296gNdVOw,4858
|
83
|
-
langroid/mytypes.py,sha256=
|
80
|
+
langroid/mytypes.py,sha256=ptAFxEAtiwmIfUnGisNotTe8wT9LKBf22lOfPgZoQIY,2368
|
84
81
|
langroid/parsing/__init__.py,sha256=ZgSAfgTC6VsTLFlRSWT-TwYco7SQeRMeZG-49MnKYGY,936
|
85
82
|
langroid/parsing/agent_chats.py,sha256=sbZRV9ujdM5QXvvuHVjIi2ysYSYlap-uqfMMUKulrW0,1068
|
86
83
|
langroid/parsing/code-parsing.md,sha256=--cyyNiSZSDlIwcjAV4-shKrSiRe2ytF3AdSoS_hD2g,3294
|
@@ -89,7 +86,7 @@ langroid/parsing/config.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
89
86
|
langroid/parsing/document_parser.py,sha256=WGnA5ADwMHliGJt6WW9rc4RiFXQcKU33b5zdPiGrtEY,24265
|
90
87
|
langroid/parsing/image_text.py,sha256=sbLIQ5nHe2UnYUksBaQsmZGaX-X0qgEpPd7CEzi_z5M,910
|
91
88
|
langroid/parsing/para_sentence_split.py,sha256=AJBzZojP3zpB-_IMiiHismhqcvkrVBQ3ZINoQyx_bE4,2000
|
92
|
-
langroid/parsing/parse_json.py,sha256=
|
89
|
+
langroid/parsing/parse_json.py,sha256=sKrYv9-IUqRFaTJA24_rmfjN1E7dQSrTBrtd1jYDE1s,5817
|
93
90
|
langroid/parsing/parser.py,sha256=AgtmlVUvrkSG1l7-YZPX8rlldgXjh_HqXAMqpXkBxUo,11746
|
94
91
|
langroid/parsing/repo_loader.py,sha256=3GjvPJS6Vf5L6gV2zOU8s-Tf1oq_fZm-IB_RL_7CTsY,29373
|
95
92
|
langroid/parsing/routing.py,sha256=-FcnlqldzL4ZoxuDwXjQPNHgBe9F9-F4R6q7b_z9CvI,1232
|
@@ -124,20 +121,20 @@ langroid/utils/output/citations.py,sha256=PSY2cpti8W-ZGFMAgj1lYoEIZy0lsniLpCliMs
|
|
124
121
|
langroid/utils/output/printing.py,sha256=yzPJZN-8_jyOJmI9N_oLwEDfjMwVgk3IDiwnZ4eK_AE,2962
|
125
122
|
langroid/utils/output/status.py,sha256=rzbE7mDJcgNNvdtylCseQcPGCGghtJvVq3lB-OPJ49E,1049
|
126
123
|
langroid/utils/pandas_utils.py,sha256=UctS986Jtl_MvU5rA7-GfrjEHXP7MNu8ePhepv0bTn0,755
|
127
|
-
langroid/utils/pydantic_utils.py,sha256=
|
124
|
+
langroid/utils/pydantic_utils.py,sha256=iRy7uQhHhQmIDZTTPNX5jXb6fqefMe9N67p3fPfOmTI,20624
|
128
125
|
langroid/utils/system.py,sha256=nvKeeUAj4eviR4kYpcr9h-HYdhqUNMTRBTHBOhz0GdU,5182
|
129
126
|
langroid/utils/web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
130
127
|
langroid/utils/web/login.py,sha256=1iz9eUAHa87vpKIkzwkmFa00avwFWivDSAr7QUhK7U0,2528
|
131
128
|
langroid/vector_store/__init__.py,sha256=6xBjb_z4QtUy4vz4RuFbcbSwmHrggHL8-q0DwCf3PMM,972
|
132
|
-
langroid/vector_store/base.py,sha256=
|
129
|
+
langroid/vector_store/base.py,sha256=BDsGl9D8TeepZK8-8SSIxpx9LJRJF9ic_xw4WjBX3sI,13780
|
133
130
|
langroid/vector_store/chromadb.py,sha256=KMfHrgovQEOeJR_LsMpGM8BteJ50wpisDu608RhU3SU,7940
|
134
|
-
langroid/vector_store/lancedb.py,sha256=
|
131
|
+
langroid/vector_store/lancedb.py,sha256=kWIQ9QxFKS8gJ7cSn11empvi7Q6_NvXNkc4f-CmVZMg,14686
|
135
132
|
langroid/vector_store/meilisearch.py,sha256=6frB7GFWeWmeKzRfLZIvzRjllniZ1cYj3HmhHQICXLs,11663
|
136
133
|
langroid/vector_store/momento.py,sha256=qR-zBF1RKVHQZPZQYW_7g-XpTwr46p8HJuYPCkfJbM4,10534
|
137
134
|
langroid/vector_store/qdrant_cloud.py,sha256=3im4Mip0QXLkR6wiqVsjV1QvhSElfxdFSuDKddBDQ-4,188
|
138
135
|
langroid/vector_store/qdrantdb.py,sha256=v88lqFkepADvlN6lByUj9I4NEKa9X9lWH16uTPPbYrE,17457
|
139
|
-
pyproject.toml,sha256=
|
140
|
-
langroid-0.
|
141
|
-
langroid-0.
|
142
|
-
langroid-0.
|
143
|
-
langroid-0.
|
136
|
+
pyproject.toml,sha256=d0X6zblgHm8pq9AAikR8iHVrp0vXpoxFc6kzGIU0DPg,7087
|
137
|
+
langroid-0.9.0.dist-info/LICENSE,sha256=EgVbvA6VSYgUlvC3RvPKehSg7MFaxWDsFuzLOsPPfJg,1065
|
138
|
+
langroid-0.9.0.dist-info/METADATA,sha256=5s4JX52hFBNWyWetUOw7mPC5sjo8Zj7CNxwoXMHyHzs,54564
|
139
|
+
langroid-0.9.0.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
140
|
+
langroid-0.9.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.9.0"
|
4
4
|
description = "Harness LLMs with Multi-Agent Programming"
|
5
5
|
authors = ["Prasad Chalasani <pchalasani@gmail.com>"]
|
6
6
|
readme = "README.md"
|
@@ -88,6 +88,7 @@ nest-asyncio = "^1.6.0"
|
|
88
88
|
async-generator = "^1.10"
|
89
89
|
|
90
90
|
python-magic = "^0.4.27"
|
91
|
+
json-repair = "^0.27.0"
|
91
92
|
|
92
93
|
[tool.poetry.extras]
|
93
94
|
# install these using, e.g.,
|
@@ -1,171 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
QueryPlanCritic is a ChatAgent that is created with a specific document schema.
|
3
|
-
|
4
|
-
Its role is to provide feedback on a Query Plan, which consists of:
|
5
|
-
- filter condition if needed (or empty string if no filter is needed)
|
6
|
-
- query - a possibly rephrased query that can be used to match the `content` field
|
7
|
-
- dataframe_calc - a Pandas-dataframe calculation/aggregation string, possibly empty
|
8
|
-
- original_query - the original query for reference
|
9
|
-
- result - the answer received from an assistant that used this QUERY PLAN.
|
10
|
-
|
11
|
-
This agent has access to two tools:
|
12
|
-
- QueryPlanTool: The handler method for this tool re-writes the query plan
|
13
|
-
in plain text (non-JSON) so the LLM can provide its feedback using the
|
14
|
-
QueryPlanFeedbackTool.
|
15
|
-
- QueryPlanFeedbackTool: LLM uses this tool to provide feedback on the Query Plan
|
16
|
-
"""
|
17
|
-
|
18
|
-
import logging
|
19
|
-
|
20
|
-
from langroid.agent.chat_agent import ChatAgent
|
21
|
-
from langroid.agent.chat_document import ChatDocument
|
22
|
-
from langroid.agent.special.lance_rag.query_planner_agent import (
|
23
|
-
LanceQueryPlanAgentConfig,
|
24
|
-
)
|
25
|
-
from langroid.agent.special.lance_tools import (
|
26
|
-
QueryPlanAnswerTool,
|
27
|
-
QueryPlanFeedbackTool,
|
28
|
-
)
|
29
|
-
from langroid.mytypes import Entity
|
30
|
-
from langroid.utils.constants import DONE, NO_ANSWER
|
31
|
-
|
32
|
-
logger = logging.getLogger(__name__)
|
33
|
-
|
34
|
-
|
35
|
-
class QueryPlanCriticConfig(LanceQueryPlanAgentConfig):
|
36
|
-
name = "QueryPlanCritic"
|
37
|
-
system_message = f"""
|
38
|
-
You are an expert at carefully planning a query that needs to be answered
|
39
|
-
based on a large collection of documents. These docs have a special `content` field
|
40
|
-
and additional FILTERABLE fields in the SCHEMA below, along with the
|
41
|
-
SAMPLE VALUES for each field, and the DTYPE in PANDAS TERMINOLOGY.
|
42
|
-
|
43
|
-
{{doc_schema}}
|
44
|
-
|
45
|
-
The ORIGINAL QUERY is handled by a QUERY PLANNER who sends the PLAN to an ASSISTANT,
|
46
|
-
who returns an ANSWER.
|
47
|
-
|
48
|
-
You will receive a QUERY PLAN consisting of:
|
49
|
-
- ORIGINAL QUERY from the user, which a QUERY PLANNER processes,
|
50
|
-
to create a QUERY PLAN, to be handled by an ASSISTANT.
|
51
|
-
- PANDAS-LIKE FILTER, WHICH CAN BE EMPTY (and it's fine if results sound reasonable)
|
52
|
-
FILTER SHOULD ONLY BE USED IF EXPLICITLY REQUIRED BY THE QUERY.
|
53
|
-
- REPHRASED QUERY (CANNOT BE EMPTY) that will be used to match against the
|
54
|
-
CONTENT (not filterable) of the documents.
|
55
|
-
In general the REPHRASED QUERY should be relied upon to match the CONTENT
|
56
|
-
of the docs. Thus the REPHRASED QUERY itself acts like a
|
57
|
-
SEMANTIC/LEXICAL/FUZZY FILTER since the Assistant is able to use it to match
|
58
|
-
the CONTENT of the docs in various ways (semantic, lexical, fuzzy, etc.).
|
59
|
-
Keep in mind that the ASSISTANT does NOT know anything about the FILTER fields,
|
60
|
-
so the REPHRASED QUERY should NOT mention ANY FILTER fields.
|
61
|
-
The assistant will answer based on documents whose CONTENTS match the QUERY,
|
62
|
-
possibly REPHRASED.
|
63
|
-
!!!!****THE REPHRASED QUERY SHOULD NEVER BE EMPTY****!!!
|
64
|
-
- DATAFRAME CALCULATION, which must be a SINGLE LINE calculation (or empty),
|
65
|
-
[NOTE ==> This calculation is applied AFTER the FILTER and REPHRASED QUERY.],
|
66
|
-
- ANSWER received from an assistant that used this QUERY PLAN.
|
67
|
-
NOTE --the ANSWER will usually NOT contain any references to FILTERING conditions,
|
68
|
-
and this is ALLOWED, since the ANSWER is based on documents AFTER FILTERING.
|
69
|
-
|
70
|
-
In addition to the above SCHEMA fields there is a `content` field which:
|
71
|
-
- CANNOT appear in a FILTER,
|
72
|
-
- CAN appear in the DATAFRAME CALCULATION.
|
73
|
-
THERE ARE NO OTHER FIELDS IN THE DOCUMENTS or in the RESULTING DATAFRAME.
|
74
|
-
|
75
|
-
Your job is to act as a CRITIC and provide feedback,
|
76
|
-
ONLY using the `query_plan_feedback` tool, and DO NOT SAY ANYTHING ELSE.
|
77
|
-
|
78
|
-
Here is how you must examine the QUERY PLAN + ANSWER:
|
79
|
-
- ALL filtering conditions in the original query must be EXPLICITLY
|
80
|
-
mentioned in the FILTER, and the QUERY field should not be used for filtering.
|
81
|
-
- If the ANSWER contains an ERROR message, then this means that the query
|
82
|
-
plan execution FAILED, and your feedback should say INVALID along
|
83
|
-
with the ERROR message, `suggested_fix` that aims to help the assistant
|
84
|
-
fix the problem (or simply equals "address the the error shown in feedback")
|
85
|
-
- Ask yourself, is the ANSWER in the expected form, e.g.
|
86
|
-
if the question is asking for the name of an ENTITY with max SIZE,
|
87
|
-
then the answer should be the ENTITY name, NOT the SIZE!!
|
88
|
-
- It is perfectly FINE if the ANSWER does NOT contain any references to FILTERING
|
89
|
-
conditions, since the ANSWER is obtained from documents AFTER FILTERING!
|
90
|
-
- If the ANSWER is in the expected form, then the QUERY PLAN is likely VALID,
|
91
|
-
and your feedback should say VALID, with empty `suggested_fix`.
|
92
|
-
===> HOWEVER!!! Watch out for a spurious correct-looking answer, for EXAMPLE:
|
93
|
-
the query was to find the ENTITY with a maximum SIZE,
|
94
|
-
but the dataframe calculation is find the SIZE, NOT the ENTITY!!
|
95
|
-
- If the ANSWER is {NO_ANSWER} or of the wrong form,
|
96
|
-
then try to DIAGNOSE the problem IN THE FOLLOWING ORDER:
|
97
|
-
- DATAFRAME CALCULATION -- is it doing the right thing?
|
98
|
-
Is it finding the Index of a row instead of the value in a column?
|
99
|
-
Or another example: maybe it is finding the maximum population
|
100
|
-
rather than the CITY with the maximum population?
|
101
|
-
If you notice a problem with the DATAFRAME CALCULATION, then
|
102
|
-
ONLY SUBMIT FEEDBACK ON THE DATAFRAME CALCULATION, and DO NOT
|
103
|
-
SUGGEST ANYTHING ELSE.
|
104
|
-
- If the DATAFRAME CALCULATION looks correct, then check if
|
105
|
-
the REPHRASED QUERY makes sense given the ORIGINAL QUERY and FILTER.
|
106
|
-
If this is the problem, then ONLY SUBMIT FEEDBACK ON THE REPHRASED QUERY,
|
107
|
-
and DO NOT SUGGEST ANYTHING ELSE.
|
108
|
-
- If the REPHRASED QUERY looks correct, then check if the FILTER makes sense.
|
109
|
-
REMEMBER: A filter should ONLY be used if EXPLICITLY REQUIRED BY THE QUERY.
|
110
|
-
|
111
|
-
|
112
|
-
IMPORTANT!! The DATAFRAME CALCULATION is done AFTER applying the
|
113
|
-
FILTER and REPHRASED QUERY! Keep this in mind when evaluating
|
114
|
-
the correctness of the DATAFRAME CALCULATION.
|
115
|
-
|
116
|
-
ALWAYS use `query_plan_feedback` tool/fn to present your feedback
|
117
|
-
in the `feedback` field, and if any fix is suggested,
|
118
|
-
present it in the `suggested_fix` field.
|
119
|
-
DO NOT SAY ANYTHING ELSE OUTSIDE THE TOOL/FN.
|
120
|
-
IF NO REVISION NEEDED, simply leave the `suggested_fix` field EMPTY,
|
121
|
-
and SAY NOTHING ELSE
|
122
|
-
and DO NOT EXPLAIN YOURSELF.
|
123
|
-
"""
|
124
|
-
|
125
|
-
|
126
|
-
def plain_text_query_plan(msg: QueryPlanAnswerTool) -> str:
|
127
|
-
plan = f"""
|
128
|
-
OriginalQuery: {msg.plan.original_query}
|
129
|
-
Filter: {msg.plan.filter}
|
130
|
-
Rephrased Query: {msg.plan.query}
|
131
|
-
DataframeCalc: {msg.plan.dataframe_calc}
|
132
|
-
Answer: {msg.answer}
|
133
|
-
"""
|
134
|
-
return plan
|
135
|
-
|
136
|
-
|
137
|
-
class QueryPlanCritic(ChatAgent):
|
138
|
-
"""
|
139
|
-
Critic for LanceQueryPlanAgent, provides feedback on
|
140
|
-
query plan + answer.
|
141
|
-
"""
|
142
|
-
|
143
|
-
def __init__(self, cfg: LanceQueryPlanAgentConfig):
|
144
|
-
super().__init__(cfg)
|
145
|
-
self.config = cfg
|
146
|
-
self.enable_message(QueryPlanAnswerTool, use=False, handle=True)
|
147
|
-
self.enable_message(QueryPlanFeedbackTool, use=True, handle=True)
|
148
|
-
|
149
|
-
def query_plan_answer(self, msg: QueryPlanAnswerTool) -> str:
|
150
|
-
"""Present query plan + answer in plain text (not JSON)
|
151
|
-
so LLM can give feedback"""
|
152
|
-
return plain_text_query_plan(msg)
|
153
|
-
|
154
|
-
def query_plan_feedback(self, msg: QueryPlanFeedbackTool) -> ChatDocument:
|
155
|
-
"""Format Valid so return to Query Planner"""
|
156
|
-
doc = self.create_agent_response(DONE)
|
157
|
-
doc.tool_messages = [msg]
|
158
|
-
return doc
|
159
|
-
|
160
|
-
def handle_message_fallback(
|
161
|
-
self, msg: str | ChatDocument
|
162
|
-
) -> str | ChatDocument | None:
|
163
|
-
"""Remind the LLM to use QueryPlanFeedbackTool since it forgot"""
|
164
|
-
if isinstance(msg, ChatDocument) and msg.metadata.sender == Entity.LLM:
|
165
|
-
return """
|
166
|
-
You forgot to use the `query_plan_feedback` tool/function.
|
167
|
-
Re-try your response using the `query_plan_feedback` tool/function,
|
168
|
-
remember to provide feedback in the `feedback` field,
|
169
|
-
and if any fix is suggested, provide it in the `suggested_fix` field.
|
170
|
-
"""
|
171
|
-
return None
|
@@ -1,144 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
The LanceRAGTaskCreator.new() method creates a 3-Agent system that uses this agent.
|
3
|
-
It takes a LanceDocChatAgent instance as argument, and adds two more agents:
|
4
|
-
- LanceQueryPlanAgent, which is given the LanceDB schema in LanceDocChatAgent,
|
5
|
-
and based on this schema, for a given user query, creates a Query Plan
|
6
|
-
using the QueryPlanTool, which contains a filter, a rephrased query,
|
7
|
-
and a dataframe_calc.
|
8
|
-
- QueryPlanCritic, which is given the LanceDB schema in LanceDocChatAgent,
|
9
|
-
and gives feedback on the Query Plan and Result using the QueryPlanFeedbackTool.
|
10
|
-
|
11
|
-
The LanceRAGTaskCreator.new() method sets up the given LanceDocChatAgent and
|
12
|
-
QueryPlanCritic as sub-tasks of the LanceQueryPlanAgent's task.
|
13
|
-
|
14
|
-
Langroid's built-in task orchestration ensures that:
|
15
|
-
- the LanceQueryPlanAgent reformulates the plan based
|
16
|
-
on the QueryPlanCritics's feedback,
|
17
|
-
- LLM deviations are corrected via tools and overrides of ChatAgent methods.
|
18
|
-
"""
|
19
|
-
|
20
|
-
import logging
|
21
|
-
|
22
|
-
from langroid.agent.special.lance_tools import (
|
23
|
-
QueryPlanAnswerTool,
|
24
|
-
)
|
25
|
-
from langroid.agent.task import Task
|
26
|
-
from langroid.mytypes import Entity
|
27
|
-
from langroid.utils.constants import NO_ANSWER
|
28
|
-
|
29
|
-
from ..lance_doc_chat_agent import LanceDocChatAgent
|
30
|
-
from .critic_agent import (
|
31
|
-
QueryPlanCritic,
|
32
|
-
QueryPlanCriticConfig,
|
33
|
-
)
|
34
|
-
from .query_planner_agent import (
|
35
|
-
LanceQueryPlanAgent,
|
36
|
-
LanceQueryPlanAgentConfig,
|
37
|
-
)
|
38
|
-
|
39
|
-
logger = logging.getLogger(__name__)
|
40
|
-
|
41
|
-
|
42
|
-
def run_lance_rag_task(
|
43
|
-
query: str,
|
44
|
-
agent: LanceDocChatAgent,
|
45
|
-
interactive: bool = True,
|
46
|
-
) -> str:
|
47
|
-
"""
|
48
|
-
Add a LanceFilterAgent to the LanceDocChatAgent,
|
49
|
-
set up the corresponding Tasks, connect them,
|
50
|
-
and return the top-level query_plan_task.
|
51
|
-
"""
|
52
|
-
doc_agent_name = "LanceRAG"
|
53
|
-
critic_name = "QueryPlanCritic"
|
54
|
-
query_plan_agent_config = LanceQueryPlanAgentConfig(
|
55
|
-
critic_name=critic_name,
|
56
|
-
doc_agent_name=doc_agent_name,
|
57
|
-
doc_schema=agent._get_clean_vecdb_schema(),
|
58
|
-
)
|
59
|
-
query_plan_agent_config.set_system_message()
|
60
|
-
|
61
|
-
query_planner = LanceQueryPlanAgent(query_plan_agent_config)
|
62
|
-
query_plan_task = Task(
|
63
|
-
query_planner,
|
64
|
-
interactive=interactive,
|
65
|
-
restart=False,
|
66
|
-
done_if_response=[Entity.AGENT],
|
67
|
-
)
|
68
|
-
# TODO - figure out how to define the fns so we avoid re-creating
|
69
|
-
# agents in each invocation. Right now we are defining the fn
|
70
|
-
# inside this context, which may not be great.
|
71
|
-
|
72
|
-
rag_task = Task(
|
73
|
-
agent,
|
74
|
-
name="LanceRAG",
|
75
|
-
restart=True, # default; no need to accumulate dialog
|
76
|
-
interactive=False,
|
77
|
-
done_if_response=[Entity.LLM], # done when non-null response from LLM
|
78
|
-
done_if_no_response=[Entity.LLM], # done when null response from LLM
|
79
|
-
)
|
80
|
-
|
81
|
-
critic_config = QueryPlanCriticConfig(
|
82
|
-
doc_schema=agent._get_clean_vecdb_schema(),
|
83
|
-
)
|
84
|
-
critic_config.set_system_message()
|
85
|
-
|
86
|
-
critic_agent = QueryPlanCritic(critic_config)
|
87
|
-
critic_task = Task(
|
88
|
-
critic_agent,
|
89
|
-
interactive=False,
|
90
|
-
restart=True, # default; no need to accumulate dialog
|
91
|
-
)
|
92
|
-
|
93
|
-
no_answer = False
|
94
|
-
feedback = None
|
95
|
-
i = 0
|
96
|
-
while i := i + 1 < 5:
|
97
|
-
# query, feedback (QueryPlanFeedbackTool) => ChatDocument[QueryPlanTool]
|
98
|
-
if feedback is not None and feedback.suggested_fix != "":
|
99
|
-
prompt = f"""
|
100
|
-
A Critic has seen your Query Plan and the Answer, and has given the
|
101
|
-
following feedback. Take it into account and re-generate your Query Plan
|
102
|
-
for the QUERY:
|
103
|
-
|
104
|
-
QUERY: {query}
|
105
|
-
FEEDBACK: {feedback.feedback}
|
106
|
-
SUGGESTED FIX: {feedback.suggested_fix}
|
107
|
-
"""
|
108
|
-
elif no_answer:
|
109
|
-
prompt = f"There was a {NO_ANSWER} response; try a different query plan"
|
110
|
-
else:
|
111
|
-
prompt = query
|
112
|
-
|
113
|
-
while True:
|
114
|
-
plan_doc = query_plan_task.run(prompt)
|
115
|
-
if len(plan_doc.tool_messages) > 0:
|
116
|
-
break
|
117
|
-
# forgot to use QueryPlanTool
|
118
|
-
prompt = """You forgot to use the `query_plan` tool/function. Try again."""
|
119
|
-
|
120
|
-
# TODO if plan_doc does NOT have a QueryPlan, remind the agent
|
121
|
-
|
122
|
-
# ChatDocument with QueryPlanTool => ChatDocument with answer
|
123
|
-
rag_answer_doc = rag_task.run(plan_doc)
|
124
|
-
|
125
|
-
if rag_answer_doc is None:
|
126
|
-
rag_answer_doc = rag_task.agent.create_llm_response(NO_ANSWER)
|
127
|
-
# QueryPlan, answer => QueryPlanAnswerTool
|
128
|
-
plan_answer_tool = QueryPlanAnswerTool(
|
129
|
-
plan=plan_doc.tool_messages[0].plan,
|
130
|
-
answer=rag_answer_doc.content,
|
131
|
-
)
|
132
|
-
# QueryPlanAnswerTool => ChatDocument[QueryPlanAnswerTool]
|
133
|
-
plan_answer_doc = agent.create_agent_response(tool_messages=[plan_answer_tool])
|
134
|
-
|
135
|
-
# ChatDocument[QueryPlanAnswerTool] => ChatDocument[QueryPlanFeedbackTool]
|
136
|
-
feedback_doc = critic_task.run(plan_answer_doc)
|
137
|
-
# ChatDocument[QueryPlanFeedbackTool] => QueryPlanFeedbackTool
|
138
|
-
feedback = feedback_doc.tool_messages[0] # QueryPlanFeedbackTool
|
139
|
-
no_answer = NO_ANSWER in rag_answer_doc.content
|
140
|
-
if feedback.suggested_fix == "" and not no_answer:
|
141
|
-
break
|
142
|
-
|
143
|
-
# query_plan_task.add_sub_task([critic_task, rag_task])
|
144
|
-
return rag_answer_doc.content
|