langroid 0.33.6__py3-none-any.whl → 0.33.8__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/__init__.py +106 -0
- langroid/agent/__init__.py +41 -0
- langroid/agent/base.py +1983 -0
- langroid/agent/batch.py +398 -0
- langroid/agent/callbacks/__init__.py +0 -0
- langroid/agent/callbacks/chainlit.py +598 -0
- langroid/agent/chat_agent.py +1899 -0
- langroid/agent/chat_document.py +454 -0
- langroid/agent/openai_assistant.py +882 -0
- langroid/agent/special/__init__.py +59 -0
- langroid/agent/special/arangodb/__init__.py +0 -0
- langroid/agent/special/arangodb/arangodb_agent.py +656 -0
- langroid/agent/special/arangodb/system_messages.py +186 -0
- langroid/agent/special/arangodb/tools.py +107 -0
- langroid/agent/special/arangodb/utils.py +36 -0
- langroid/agent/special/doc_chat_agent.py +1466 -0
- langroid/agent/special/lance_doc_chat_agent.py +262 -0
- langroid/agent/special/lance_rag/__init__.py +9 -0
- langroid/agent/special/lance_rag/critic_agent.py +198 -0
- langroid/agent/special/lance_rag/lance_rag_task.py +82 -0
- langroid/agent/special/lance_rag/query_planner_agent.py +260 -0
- langroid/agent/special/lance_tools.py +61 -0
- langroid/agent/special/neo4j/__init__.py +0 -0
- langroid/agent/special/neo4j/csv_kg_chat.py +174 -0
- langroid/agent/special/neo4j/neo4j_chat_agent.py +433 -0
- langroid/agent/special/neo4j/system_messages.py +120 -0
- langroid/agent/special/neo4j/tools.py +32 -0
- langroid/agent/special/relevance_extractor_agent.py +127 -0
- langroid/agent/special/retriever_agent.py +56 -0
- langroid/agent/special/sql/__init__.py +17 -0
- langroid/agent/special/sql/sql_chat_agent.py +654 -0
- langroid/agent/special/sql/utils/__init__.py +21 -0
- langroid/agent/special/sql/utils/description_extractors.py +190 -0
- langroid/agent/special/sql/utils/populate_metadata.py +85 -0
- langroid/agent/special/sql/utils/system_message.py +35 -0
- langroid/agent/special/sql/utils/tools.py +64 -0
- langroid/agent/special/table_chat_agent.py +263 -0
- langroid/agent/task.py +2099 -0
- langroid/agent/tool_message.py +393 -0
- langroid/agent/tools/__init__.py +38 -0
- langroid/agent/tools/duckduckgo_search_tool.py +50 -0
- langroid/agent/tools/file_tools.py +234 -0
- langroid/agent/tools/google_search_tool.py +39 -0
- langroid/agent/tools/metaphor_search_tool.py +68 -0
- langroid/agent/tools/orchestration.py +303 -0
- langroid/agent/tools/recipient_tool.py +235 -0
- langroid/agent/tools/retrieval_tool.py +32 -0
- langroid/agent/tools/rewind_tool.py +137 -0
- langroid/agent/tools/segment_extract_tool.py +41 -0
- langroid/agent/xml_tool_message.py +382 -0
- langroid/cachedb/__init__.py +17 -0
- langroid/cachedb/base.py +58 -0
- langroid/cachedb/momento_cachedb.py +108 -0
- langroid/cachedb/redis_cachedb.py +153 -0
- langroid/embedding_models/__init__.py +39 -0
- langroid/embedding_models/base.py +74 -0
- langroid/embedding_models/models.py +461 -0
- langroid/embedding_models/protoc/__init__.py +0 -0
- langroid/embedding_models/protoc/embeddings.proto +19 -0
- langroid/embedding_models/protoc/embeddings_pb2.py +33 -0
- langroid/embedding_models/protoc/embeddings_pb2.pyi +50 -0
- langroid/embedding_models/protoc/embeddings_pb2_grpc.py +79 -0
- langroid/embedding_models/remote_embeds.py +153 -0
- langroid/exceptions.py +71 -0
- langroid/language_models/__init__.py +53 -0
- langroid/language_models/azure_openai.py +153 -0
- langroid/language_models/base.py +678 -0
- langroid/language_models/config.py +18 -0
- langroid/language_models/mock_lm.py +124 -0
- langroid/language_models/openai_gpt.py +1964 -0
- langroid/language_models/prompt_formatter/__init__.py +16 -0
- langroid/language_models/prompt_formatter/base.py +40 -0
- langroid/language_models/prompt_formatter/hf_formatter.py +132 -0
- langroid/language_models/prompt_formatter/llama2_formatter.py +75 -0
- langroid/language_models/utils.py +151 -0
- langroid/mytypes.py +84 -0
- langroid/parsing/__init__.py +52 -0
- langroid/parsing/agent_chats.py +38 -0
- langroid/parsing/code_parser.py +121 -0
- langroid/parsing/document_parser.py +718 -0
- langroid/parsing/para_sentence_split.py +62 -0
- langroid/parsing/parse_json.py +155 -0
- langroid/parsing/parser.py +313 -0
- langroid/parsing/repo_loader.py +790 -0
- langroid/parsing/routing.py +36 -0
- langroid/parsing/search.py +275 -0
- langroid/parsing/spider.py +102 -0
- langroid/parsing/table_loader.py +94 -0
- langroid/parsing/url_loader.py +115 -0
- langroid/parsing/urls.py +273 -0
- langroid/parsing/utils.py +373 -0
- langroid/parsing/web_search.py +156 -0
- langroid/prompts/__init__.py +9 -0
- langroid/prompts/dialog.py +17 -0
- langroid/prompts/prompts_config.py +5 -0
- langroid/prompts/templates.py +141 -0
- langroid/pydantic_v1/__init__.py +10 -0
- langroid/pydantic_v1/main.py +4 -0
- langroid/utils/__init__.py +19 -0
- langroid/utils/algorithms/__init__.py +3 -0
- langroid/utils/algorithms/graph.py +103 -0
- langroid/utils/configuration.py +98 -0
- langroid/utils/constants.py +30 -0
- langroid/utils/git_utils.py +252 -0
- langroid/utils/globals.py +49 -0
- langroid/utils/logging.py +135 -0
- langroid/utils/object_registry.py +66 -0
- langroid/utils/output/__init__.py +20 -0
- langroid/utils/output/citations.py +41 -0
- langroid/utils/output/printing.py +99 -0
- langroid/utils/output/status.py +40 -0
- langroid/utils/pandas_utils.py +30 -0
- langroid/utils/pydantic_utils.py +602 -0
- langroid/utils/system.py +286 -0
- langroid/utils/types.py +93 -0
- langroid/vector_store/__init__.py +50 -0
- langroid/vector_store/base.py +359 -0
- langroid/vector_store/chromadb.py +214 -0
- langroid/vector_store/lancedb.py +406 -0
- langroid/vector_store/meilisearch.py +299 -0
- langroid/vector_store/momento.py +278 -0
- langroid/vector_store/qdrantdb.py +468 -0
- {langroid-0.33.6.dist-info → langroid-0.33.8.dist-info}/METADATA +95 -94
- langroid-0.33.8.dist-info/RECORD +127 -0
- {langroid-0.33.6.dist-info → langroid-0.33.8.dist-info}/WHEEL +1 -1
- langroid-0.33.6.dist-info/RECORD +0 -7
- langroid-0.33.6.dist-info/entry_points.txt +0 -4
- pyproject.toml +0 -356
- {langroid-0.33.6.dist-info → langroid-0.33.8.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,235 @@
|
|
1
|
+
"""
|
2
|
+
The `recipient_tool` is used to send a message to a specific recipient.
|
3
|
+
Various methods from the RecipientTool and AddRecipientTool class
|
4
|
+
are inserted into the Agent as methods (see `langroid/agent/base.py`,
|
5
|
+
the method `_get_tool_list()`).
|
6
|
+
|
7
|
+
See usage examples in `tests/main/test_multi_agent_complex.py` and
|
8
|
+
`tests/main/test_recipient_tool.py`.
|
9
|
+
|
10
|
+
A simpler alternative to this tool is `SendTool`, see here:
|
11
|
+
https://github.com/langroid/langroid/blob/main/langroid/agent/tools/orchestration.py
|
12
|
+
|
13
|
+
You can also define your own XML-based variant of this tool:
|
14
|
+
https://github.com/langroid/langroid/blob/main/examples/basic/xml-tool.py
|
15
|
+
which uses XML rather than JSON, and can be more reliable than JSON,
|
16
|
+
especially with weaker LLMs.
|
17
|
+
|
18
|
+
"""
|
19
|
+
|
20
|
+
from typing import List, Type
|
21
|
+
|
22
|
+
from rich import print
|
23
|
+
|
24
|
+
from langroid.agent.chat_agent import ChatAgent
|
25
|
+
from langroid.agent.chat_document import ChatDocMetaData, ChatDocument
|
26
|
+
from langroid.agent.tool_message import ToolMessage
|
27
|
+
from langroid.mytypes import Entity
|
28
|
+
from langroid.utils.pydantic_utils import has_field
|
29
|
+
|
30
|
+
|
31
|
+
class AddRecipientTool(ToolMessage):
|
32
|
+
"""
|
33
|
+
Used by LLM to add a recipient to the previous message, when it has
|
34
|
+
forgotten to specify a recipient. This avoids having to re-generate the
|
35
|
+
previous message (and thus saves token-cost and time).
|
36
|
+
"""
|
37
|
+
|
38
|
+
request: str = "add_recipient"
|
39
|
+
purpose: str = (
|
40
|
+
"To clarify that the <intended_recipient> when I forgot to specify it, "
|
41
|
+
"to clarify who the message is intended for."
|
42
|
+
)
|
43
|
+
intended_recipient: str
|
44
|
+
_saved_content: str = ""
|
45
|
+
|
46
|
+
def response(self, agent: ChatAgent) -> ChatDocument:
|
47
|
+
"""
|
48
|
+
Returns:
|
49
|
+
(ChatDocument): with content set to self.content and
|
50
|
+
metadata.recipient set to self.recipient.
|
51
|
+
"""
|
52
|
+
print(
|
53
|
+
"[red]RecipientTool: "
|
54
|
+
f"Added recipient {self.intended_recipient} to message."
|
55
|
+
)
|
56
|
+
if self.__class__._saved_content == "":
|
57
|
+
recipient_request_name = RecipientTool.default_value("request")
|
58
|
+
content = f"""
|
59
|
+
Recipient specified but content is empty!
|
60
|
+
This could be because the `{self.request}` tool/function was used
|
61
|
+
before using `{recipient_request_name}` tool/function.
|
62
|
+
Resend the message using `{recipient_request_name}` tool/function.
|
63
|
+
"""
|
64
|
+
else:
|
65
|
+
content = self.__class__._saved_content # use class-level attrib value
|
66
|
+
# erase content since we just used it.
|
67
|
+
self.__class__._saved_content = ""
|
68
|
+
return ChatDocument(
|
69
|
+
content=content,
|
70
|
+
metadata=ChatDocMetaData(
|
71
|
+
recipient=self.intended_recipient,
|
72
|
+
# we are constructing this so it looks as it msg is from LLM
|
73
|
+
sender=Entity.LLM,
|
74
|
+
),
|
75
|
+
)
|
76
|
+
|
77
|
+
|
78
|
+
class RecipientTool(ToolMessage):
|
79
|
+
"""
|
80
|
+
Used by LLM to send a message to a specific recipient.
|
81
|
+
|
82
|
+
Useful in cases where an LLM is talking to 2 or more
|
83
|
+
agents (or an Agent and human user), and needs to specify which agent (task)
|
84
|
+
its message is intended for. The recipient name should be the name of a task
|
85
|
+
(which is normally the name of the agent that the task wraps, although the task
|
86
|
+
can have its own name).
|
87
|
+
|
88
|
+
To use this tool/function-call, LLM must generate a JSON structure
|
89
|
+
with these fields:
|
90
|
+
{
|
91
|
+
"request": "recipient_message", # also the function name when using fn-calling
|
92
|
+
"intended_recipient": <name_of_recipient_task_or_entity>,
|
93
|
+
"content": <content>
|
94
|
+
}
|
95
|
+
The effect of this is that `content` will be sent to the `intended_recipient` task.
|
96
|
+
"""
|
97
|
+
|
98
|
+
request: str = "recipient_message"
|
99
|
+
purpose: str = "To send message <content> to a specific <intended_recipient>."
|
100
|
+
intended_recipient: str
|
101
|
+
content: str
|
102
|
+
|
103
|
+
@classmethod
|
104
|
+
def create(cls, recipients: List[str], default: str = "") -> Type["RecipientTool"]:
|
105
|
+
"""Create a restricted version of RecipientTool that
|
106
|
+
only allows certain recipients, and possibly sets a default recipient."""
|
107
|
+
|
108
|
+
class RecipientToolRestricted(cls): # type: ignore
|
109
|
+
allowed_recipients = recipients
|
110
|
+
default_recipient = default
|
111
|
+
|
112
|
+
return RecipientToolRestricted
|
113
|
+
|
114
|
+
@classmethod
|
115
|
+
def instructions(cls) -> str:
|
116
|
+
"""
|
117
|
+
Generate instructions for using this tool/function.
|
118
|
+
These are intended to be appended to the system message of the LLM.
|
119
|
+
"""
|
120
|
+
recipients = []
|
121
|
+
if has_field(cls, "allowed_recipients"):
|
122
|
+
recipients = cls.default_value("allowed_recipients")
|
123
|
+
if len(recipients) > 0:
|
124
|
+
recipients_str = ", ".join(recipients)
|
125
|
+
return f"""
|
126
|
+
Since you will be talking to multiple recipients,
|
127
|
+
you must clarify who your intended recipient is, using
|
128
|
+
the `{cls.default_value("request")}` tool/function-call, by setting the
|
129
|
+
'intended_recipient' field to one of the following:
|
130
|
+
{recipients_str},
|
131
|
+
and setting the 'content' field to your message.
|
132
|
+
"""
|
133
|
+
else:
|
134
|
+
return f"""
|
135
|
+
Since you will be talking to multiple recipients,
|
136
|
+
you must clarify who your intended recipient is, using
|
137
|
+
the `{cls.default_value("request")}` tool/function-call, by setting the
|
138
|
+
'intended_recipient' field to the name of the recipient,
|
139
|
+
and setting the 'content' field to your message.
|
140
|
+
"""
|
141
|
+
|
142
|
+
def response(self, agent: ChatAgent) -> str | ChatDocument:
|
143
|
+
"""
|
144
|
+
When LLM has correctly used this tool,
|
145
|
+
construct a ChatDocument with an explicit recipient,
|
146
|
+
and make it look like it is from the LLM.
|
147
|
+
|
148
|
+
Returns:
|
149
|
+
(ChatDocument): with content set to self.content and
|
150
|
+
metadata.recipient set to self.intended_recipient.
|
151
|
+
"""
|
152
|
+
default_recipient = self.__class__.default_value("default_recipient")
|
153
|
+
if self.intended_recipient == "" and default_recipient not in ["", None]:
|
154
|
+
self.intended_recipient = default_recipient
|
155
|
+
elif self.intended_recipient == "":
|
156
|
+
# save the content as a class-variable, so that
|
157
|
+
# we can construct the ChatDocument once the LLM specifies a recipient.
|
158
|
+
# This avoids having to re-generate the entire message, saving time + cost.
|
159
|
+
AddRecipientTool._saved_content = self.content
|
160
|
+
agent.enable_message(AddRecipientTool)
|
161
|
+
return ChatDocument(
|
162
|
+
content="""
|
163
|
+
Empty recipient field!
|
164
|
+
Please use the 'add_recipient' tool/function-call to specify who your
|
165
|
+
message is intended for.
|
166
|
+
DO NOT REPEAT your original message; ONLY specify the recipient via this
|
167
|
+
tool/function-call.
|
168
|
+
""",
|
169
|
+
metadata=ChatDocMetaData(
|
170
|
+
sender=Entity.AGENT,
|
171
|
+
recipient=Entity.LLM,
|
172
|
+
),
|
173
|
+
)
|
174
|
+
|
175
|
+
print("[red]RecipientTool: Validated properly addressed message")
|
176
|
+
|
177
|
+
return ChatDocument(
|
178
|
+
content=self.content,
|
179
|
+
metadata=ChatDocMetaData(
|
180
|
+
recipient=self.intended_recipient,
|
181
|
+
# we are constructing this so it looks as if msg is from LLM
|
182
|
+
sender=Entity.LLM,
|
183
|
+
),
|
184
|
+
)
|
185
|
+
|
186
|
+
@staticmethod
|
187
|
+
def handle_message_fallback(
|
188
|
+
agent: ChatAgent, msg: str | ChatDocument
|
189
|
+
) -> str | ChatDocument | None:
|
190
|
+
"""
|
191
|
+
Response of agent if this tool is not used, e.g.
|
192
|
+
the LLM simply sends a message without using this tool.
|
193
|
+
This method has two purposes:
|
194
|
+
(a) Alert the LLM that it has forgotten to specify a recipient, and prod it
|
195
|
+
to use the `add_recipient` tool to specify just the recipient
|
196
|
+
(and not re-generate the entire message).
|
197
|
+
(b) Save the content of the message in the agent's `content` field,
|
198
|
+
so the agent can construct a ChatDocument with this content once LLM
|
199
|
+
later specifies a recipient using the `add_recipient` tool.
|
200
|
+
|
201
|
+
This method is used to set the agent's handle_message_fallback() method.
|
202
|
+
|
203
|
+
Returns:
|
204
|
+
(str): reminder to LLM to use the `add_recipient` tool.
|
205
|
+
"""
|
206
|
+
# Note: once the LLM specifies a missing recipient, the task loop
|
207
|
+
# mechanism will not allow any of the "native" responders to respond,
|
208
|
+
# since the recipient will differ from the task name.
|
209
|
+
# So if this method is called, we can be sure that the recipient has not
|
210
|
+
# been specified.
|
211
|
+
if (
|
212
|
+
isinstance(msg, str)
|
213
|
+
or msg.metadata.sender != Entity.LLM
|
214
|
+
or msg.metadata.recipient != "" # there IS an explicit recipient
|
215
|
+
):
|
216
|
+
return None
|
217
|
+
content = msg if isinstance(msg, str) else msg.content
|
218
|
+
# save the content as a class-variable, so that
|
219
|
+
# we can construct the ChatDocument once the LLM specifies a recipient.
|
220
|
+
# This avoids having to re-generate the entire message, saving time + cost.
|
221
|
+
AddRecipientTool._saved_content = content
|
222
|
+
agent.enable_message(AddRecipientTool)
|
223
|
+
print("[red]RecipientTool: Recipient not specified, asking LLM to clarify.")
|
224
|
+
return ChatDocument(
|
225
|
+
content="""
|
226
|
+
Please use the 'add_recipient' tool/function-call to specify who your
|
227
|
+
`intended_recipient` is.
|
228
|
+
DO NOT REPEAT your original message; ONLY specify the
|
229
|
+
`intended_recipient` via this tool/function-call.
|
230
|
+
""",
|
231
|
+
metadata=ChatDocMetaData(
|
232
|
+
sender=Entity.AGENT,
|
233
|
+
recipient=Entity.LLM,
|
234
|
+
),
|
235
|
+
)
|
@@ -0,0 +1,32 @@
|
|
1
|
+
from typing import List, Tuple
|
2
|
+
|
3
|
+
from langroid.agent.tool_message import ToolMessage
|
4
|
+
|
5
|
+
|
6
|
+
class RetrievalTool(ToolMessage):
|
7
|
+
"""
|
8
|
+
Retrieval tool, only to be used by a DocChatAgent.
|
9
|
+
The handler method is defined in DocChatAgent.retrieval_tool
|
10
|
+
"""
|
11
|
+
|
12
|
+
request: str = "retrieval_tool"
|
13
|
+
purpose: str = """
|
14
|
+
To retrieve up to <num_results> passages from a document-set, that are
|
15
|
+
relevant to a <query>, which could be a question or simply a topic or
|
16
|
+
search phrase.
|
17
|
+
"""
|
18
|
+
query: str
|
19
|
+
num_results: int
|
20
|
+
|
21
|
+
@classmethod
|
22
|
+
def examples(cls) -> List["ToolMessage" | Tuple[str, "ToolMessage"]]:
|
23
|
+
return [
|
24
|
+
cls(
|
25
|
+
query="What are the eligibility criteria for the scholarship?",
|
26
|
+
num_results=3,
|
27
|
+
),
|
28
|
+
cls(
|
29
|
+
query="Self-Attention mechanism in RNNs",
|
30
|
+
num_results=5,
|
31
|
+
),
|
32
|
+
]
|
@@ -0,0 +1,137 @@
|
|
1
|
+
"""
|
2
|
+
The `rewind_tool` is used to rewind to the `n`th previous Assistant message
|
3
|
+
and replace it with a new `content`. This is useful in several scenarios and
|
4
|
+
- saves token-cost + inference time,
|
5
|
+
- reduces distracting clutter in chat history, which helps improve response quality.
|
6
|
+
|
7
|
+
This is intended to mimic how a human user might use a chat interface, where they
|
8
|
+
go down a conversation path, and want to go back in history to "edit and re-submit"
|
9
|
+
a previous message, to get a better response.
|
10
|
+
|
11
|
+
See usage examples in `tests/main/test_rewind_tool.py`.
|
12
|
+
"""
|
13
|
+
|
14
|
+
from typing import List, Tuple
|
15
|
+
|
16
|
+
import langroid.language_models as lm
|
17
|
+
from langroid.agent.chat_agent import ChatAgent
|
18
|
+
from langroid.agent.chat_document import ChatDocument
|
19
|
+
from langroid.agent.tool_message import ToolMessage
|
20
|
+
|
21
|
+
|
22
|
+
def prune_messages(agent: ChatAgent, idx: int) -> ChatDocument | None:
|
23
|
+
"""
|
24
|
+
Clear the message history of agent, starting at index `idx`,
|
25
|
+
taking care to first clear all dependent messages (possibly from other agents'
|
26
|
+
message histories) that are linked to the message at `idx`, via the `child_id` field
|
27
|
+
of the `metadata` field of the ChatDocument linked from the message at `idx`.
|
28
|
+
|
29
|
+
Args:
|
30
|
+
agent (ChatAgent): The agent whose message history is to be pruned.
|
31
|
+
idx (int): The index from which to start clearing the message history.
|
32
|
+
|
33
|
+
Returns:
|
34
|
+
The parent ChatDocument of the ChatDocument linked from the message at `idx`,
|
35
|
+
if it exists, else None.
|
36
|
+
|
37
|
+
"""
|
38
|
+
assert idx >= 0, "Invalid index for message history!"
|
39
|
+
chat_doc_id = agent.message_history[idx].chat_document_id
|
40
|
+
chat_doc = ChatDocument.from_id(chat_doc_id)
|
41
|
+
assert chat_doc is not None, "ChatDocument not found in registry!"
|
42
|
+
|
43
|
+
parent = ChatDocument.from_id(chat_doc.metadata.parent_id) # may be None
|
44
|
+
# We're invaliding the msg at idx,
|
45
|
+
# so starting with chat_doc, go down the child links
|
46
|
+
# and clear history of each agent, to the msg_idx
|
47
|
+
curr_doc = chat_doc
|
48
|
+
while child_doc := curr_doc.metadata.child:
|
49
|
+
if child_doc.metadata.msg_idx >= 0:
|
50
|
+
child_agent = ChatAgent.from_id(child_doc.metadata.agent_id)
|
51
|
+
if child_agent is not None:
|
52
|
+
child_agent.clear_history(child_doc.metadata.msg_idx)
|
53
|
+
curr_doc = child_doc
|
54
|
+
|
55
|
+
# Clear out ObjectRegistry entries for this ChatDocuments
|
56
|
+
# and all descendants (in case they weren't already cleared above)
|
57
|
+
ChatDocument.delete_id(chat_doc.id())
|
58
|
+
|
59
|
+
# Finally, clear this agent's history back to idx,
|
60
|
+
# and replace the msg at idx with the new content
|
61
|
+
agent.clear_history(idx)
|
62
|
+
return parent
|
63
|
+
|
64
|
+
|
65
|
+
class RewindTool(ToolMessage):
|
66
|
+
"""
|
67
|
+
Used by LLM to rewind (i.e. backtrack) to the `n`th Assistant message
|
68
|
+
and replace with a new msg.
|
69
|
+
"""
|
70
|
+
|
71
|
+
request: str = "rewind_tool"
|
72
|
+
purpose: str = """
|
73
|
+
To rewind the conversation and replace the
|
74
|
+
<n>'th Assistant message with <content>
|
75
|
+
"""
|
76
|
+
n: int
|
77
|
+
content: str
|
78
|
+
|
79
|
+
@classmethod
|
80
|
+
def examples(cls) -> List["ToolMessage" | Tuple[str, "ToolMessage"]]:
|
81
|
+
return [
|
82
|
+
cls(n=1, content="What are the 3 major causes of heart disease?"),
|
83
|
+
(
|
84
|
+
"""
|
85
|
+
Based on the conversation so far, I realize I would get a better
|
86
|
+
response from Bob if rephrase my 2nd message to him to:
|
87
|
+
'Who wrote the book Grime and Banishment?'
|
88
|
+
""",
|
89
|
+
cls(n=2, content="who wrote the book 'Grime and Banishment'?"),
|
90
|
+
),
|
91
|
+
]
|
92
|
+
|
93
|
+
def response(self, agent: ChatAgent) -> str | ChatDocument:
|
94
|
+
"""
|
95
|
+
Define the tool-handler method for this tool here itself,
|
96
|
+
since it is a generic tool whose functionality should be the
|
97
|
+
same for any agent.
|
98
|
+
|
99
|
+
When LLM has correctly used this tool, rewind this agent's
|
100
|
+
`message_history` to the `n`th assistant msg, and replace it with `content`.
|
101
|
+
We need to mock it as if the LLM is sending this message.
|
102
|
+
|
103
|
+
Within a multi-agent scenario, this also means that any other messages dependent
|
104
|
+
on this message will need to be invalidated --
|
105
|
+
so go down the chain of child messages and clear each agent's history
|
106
|
+
back to the `msg_idx` corresponding to the child message.
|
107
|
+
|
108
|
+
Returns:
|
109
|
+
(ChatDocument): with content set to self.content.
|
110
|
+
"""
|
111
|
+
idx = agent.nth_message_idx_with_role(lm.Role.ASSISTANT, self.n)
|
112
|
+
if idx < 0:
|
113
|
+
# set up a corrective message from AGENT
|
114
|
+
msg = f"""
|
115
|
+
Could not rewind to {self.n}th Assistant message!
|
116
|
+
Please check the value of `n` and try again.
|
117
|
+
Or it may be too early to use the `rewind_tool`.
|
118
|
+
"""
|
119
|
+
return agent.create_agent_response(msg)
|
120
|
+
|
121
|
+
parent = prune_messages(agent, idx)
|
122
|
+
|
123
|
+
# create ChatDocument with new content, to be returned as result of this tool
|
124
|
+
result_doc = agent.create_llm_response(self.content)
|
125
|
+
result_doc.metadata.parent_id = "" if parent is None else parent.id()
|
126
|
+
result_doc.metadata.agent_id = agent.id
|
127
|
+
result_doc.metadata.msg_idx = idx
|
128
|
+
|
129
|
+
# replace the message at idx with this new message
|
130
|
+
agent.message_history.extend(ChatDocument.to_LLMMessage(result_doc))
|
131
|
+
|
132
|
+
# set the replaced doc's parent's child to this result_doc
|
133
|
+
if parent is not None:
|
134
|
+
# first remove the this parent's child from registry
|
135
|
+
ChatDocument.delete_id(parent.metadata.child_id)
|
136
|
+
parent.metadata.child_id = result_doc.id()
|
137
|
+
return result_doc
|
@@ -0,0 +1,41 @@
|
|
1
|
+
"""
|
2
|
+
A tool to extract segment numbers from the last user message,
|
3
|
+
containing numbered segments.
|
4
|
+
|
5
|
+
The idea is that when an LLM wants to (or is asked to) simply extract
|
6
|
+
portions of a message verbatim, it should use this tool/function to
|
7
|
+
SPECIFY what should be extracted, rather than actually extracting it.
|
8
|
+
The output will be in the form of a list of segment numbers or ranges.
|
9
|
+
This will usually be much cheaper and faster than actually writing out the extracted
|
10
|
+
text. The handler of this tool/function will then extract the text and send it back.
|
11
|
+
"""
|
12
|
+
|
13
|
+
from typing import List, Tuple
|
14
|
+
|
15
|
+
from langroid.agent.tool_message import ToolMessage
|
16
|
+
|
17
|
+
|
18
|
+
class SegmentExtractTool(ToolMessage):
|
19
|
+
request: str = "extract_segments"
|
20
|
+
purpose: str = """
|
21
|
+
To extract segments from a body of text containing numbered
|
22
|
+
segments, in the form of a <segment_list> which is a list of segment
|
23
|
+
numbers or ranges, like "10,12,14-17".
|
24
|
+
"""
|
25
|
+
segment_list: str
|
26
|
+
|
27
|
+
@classmethod
|
28
|
+
def examples(cls) -> List["ToolMessage" | Tuple[str, "ToolMessage"]]:
|
29
|
+
return [
|
30
|
+
(
|
31
|
+
"I want to extract segments 1, 3, and 5 thru 7",
|
32
|
+
cls(segment_list="1,3,5-7"),
|
33
|
+
)
|
34
|
+
]
|
35
|
+
|
36
|
+
@classmethod
|
37
|
+
def instructions(cls) -> str:
|
38
|
+
return """
|
39
|
+
Use this tool/function to indicate certain segments from
|
40
|
+
a body of text containing numbered segments.
|
41
|
+
"""
|