langroid 0.1.61__tar.gz → 0.1.62__tar.gz
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-0.1.61 → langroid-0.1.62}/PKG-INFO +1 -1
- {langroid-0.1.61 → langroid-0.1.62}/langroid/agent/base.py +69 -29
- {langroid-0.1.61 → langroid-0.1.62}/langroid/agent/chat_agent.py +93 -89
- {langroid-0.1.61 → langroid-0.1.62}/langroid/agent/chat_document.py +2 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/agent/task.py +20 -15
- {langroid-0.1.61 → langroid-0.1.62}/langroid/agent/tool_message.py +1 -1
- {langroid-0.1.61 → langroid-0.1.62}/langroid/mytypes.py +1 -0
- {langroid-0.1.61 → langroid-0.1.62}/pyproject.toml +1 -1
- {langroid-0.1.61 → langroid-0.1.62}/setup.py +1 -1
- {langroid-0.1.61 → langroid-0.1.62}/LICENSE +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/README.md +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/__init__.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/agent/__init__.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/agent/helpers.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/agent/junk +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/agent/special/__init__.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/agent/special/doc_chat_agent.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/agent/special/recipient_validator_agent.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/agent/special/retriever_agent.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/agent/special/sql/__init__.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/agent/special/sql/sql_chat_agent.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/agent/special/sql/utils/__init__.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/agent/special/sql/utils/description_extractors.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/agent/special/sql/utils/populate_metadata.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/agent/special/sql/utils/system_message.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/agent/special/sql/utils/tools.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/agent/special/table_chat_agent.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/agent/tools/__init__.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/agent/tools/google_search_tool.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/agent/tools/recipient_tool.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/agent_config.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/cachedb/__init__.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/cachedb/base.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/cachedb/momento_cachedb.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/cachedb/redis_cachedb.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/embedding_models/__init__.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/embedding_models/base.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/embedding_models/clustering.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/embedding_models/models.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/language_models/__init__.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/language_models/azure_openai.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/language_models/base.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/language_models/config.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/language_models/openai_gpt.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/language_models/prompt_formatter/__init__.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/language_models/prompt_formatter/base.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/language_models/prompt_formatter/llama2_formatter.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/language_models/utils.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/parsing/__init__.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/parsing/agent_chats.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/parsing/code-parsing.md +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/parsing/code_parser.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/parsing/json.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/parsing/para_sentence_split.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/parsing/parser.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/parsing/pdf_parser.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/parsing/repo_loader.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/parsing/table_loader.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/parsing/url_loader.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/parsing/url_loader_cookies.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/parsing/urls.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/parsing/utils.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/parsing/web_search.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/prompts/__init__.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/prompts/dialog.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/prompts/prompts_config.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/prompts/templates.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/prompts/transforms.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/scripts/__init__.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/utils/__init__.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/utils/configuration.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/utils/constants.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/utils/docker.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/utils/globals.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/utils/llms/__init__.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/utils/llms/strings.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/utils/logging.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/utils/output/__init__.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/utils/output/printing.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/utils/pydantic_utils.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/utils/system.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/utils/web/__init__.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/utils/web/login.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/utils/web/selenium_login.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/vector_store/__init__.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/vector_store/base.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/vector_store/chromadb.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/vector_store/qdrant_cloud.py +0 -0
- {langroid-0.1.61 → langroid-0.1.62}/langroid/vector_store/qdrantdb.py +0 -0
@@ -1,6 +1,7 @@
|
|
1
1
|
import inspect
|
2
2
|
import json
|
3
3
|
import logging
|
4
|
+
import textwrap
|
4
5
|
from abc import ABC
|
5
6
|
from contextlib import ExitStack
|
6
7
|
from typing import (
|
@@ -211,7 +212,7 @@ class Agent(ABC):
|
|
211
212
|
def json_format_rules(self) -> str:
|
212
213
|
"""
|
213
214
|
Specification of JSON formatting rules, based on the currently enabled
|
214
|
-
|
215
|
+
usable `ToolMessage`s
|
215
216
|
|
216
217
|
Returns:
|
217
218
|
str: formatting rules
|
@@ -220,7 +221,7 @@ class Agent(ABC):
|
|
220
221
|
if len(enabled_classes) == 0:
|
221
222
|
return "You can ask questions in natural language."
|
222
223
|
|
223
|
-
|
224
|
+
json_instructions = "\n\n".join(
|
224
225
|
[
|
225
226
|
str(msg_cls.default_value("request"))
|
226
227
|
+ ":\n"
|
@@ -229,7 +230,53 @@ class Agent(ABC):
|
|
229
230
|
if msg_cls.default_value("request") in self.llm_tools_usable
|
230
231
|
]
|
231
232
|
)
|
232
|
-
return
|
233
|
+
return textwrap.dedent(
|
234
|
+
f"""
|
235
|
+
=== ALL AVAILABLE TOOLS and THEIR JSON FORMAT INSTRUCTIONS ===
|
236
|
+
You have access to the following TOOLS to accomplish your task:
|
237
|
+
|
238
|
+
{json_instructions}
|
239
|
+
|
240
|
+
{INSTRUCTION}
|
241
|
+
----------------------------
|
242
|
+
""".lstrip()
|
243
|
+
)
|
244
|
+
|
245
|
+
def tool_instructions(self) -> str:
|
246
|
+
"""
|
247
|
+
Instructions (defined via `instructions` classmethod in a
|
248
|
+
ToolMessage class) for currently enabled and usable Tools.
|
249
|
+
|
250
|
+
Returns:
|
251
|
+
str: concatenation of instructions for all usable tools
|
252
|
+
"""
|
253
|
+
enabled_classes: List[Type[ToolMessage]] = list(self.llm_tools_map.values())
|
254
|
+
if len(enabled_classes) == 0:
|
255
|
+
return ""
|
256
|
+
instructions = []
|
257
|
+
for msg_cls in enabled_classes:
|
258
|
+
if (
|
259
|
+
hasattr(msg_cls, "instructions")
|
260
|
+
and inspect.ismethod(msg_cls.instructions)
|
261
|
+
and msg_cls.default_value("request") in self.llm_tools_usable
|
262
|
+
):
|
263
|
+
instructions.append(
|
264
|
+
textwrap.dedent(
|
265
|
+
f"""
|
266
|
+
{msg_cls.default_value("request")}:
|
267
|
+
{msg_cls.instructions()}
|
268
|
+
""".lstrip()
|
269
|
+
)
|
270
|
+
)
|
271
|
+
if len(instructions) == 0:
|
272
|
+
return ""
|
273
|
+
instructions_str = "\n\n".join(instructions)
|
274
|
+
return textwrap.dedent(
|
275
|
+
f"""
|
276
|
+
=== GUIDELINES ON SOME TOOLS/FUNCTIONS USAGE ===
|
277
|
+
{instructions_str}
|
278
|
+
""".lstrip()
|
279
|
+
)
|
233
280
|
|
234
281
|
def sample_multi_round_dialog(self) -> str:
|
235
282
|
"""
|
@@ -246,26 +293,6 @@ class Agent(ABC):
|
|
246
293
|
]
|
247
294
|
return "\n\n".join(sample_convo)
|
248
295
|
|
249
|
-
def json_tool_format_instructions(self) -> str:
|
250
|
-
"""
|
251
|
-
Generate a string containing instructions to the LLM on when to format
|
252
|
-
requests/questions as JSON, based on the currently enabled message classes.
|
253
|
-
|
254
|
-
Returns:
|
255
|
-
str: The instructions string.
|
256
|
-
"""
|
257
|
-
format_rules = self.json_format_rules()
|
258
|
-
|
259
|
-
return f"""
|
260
|
-
You have access to the following TOOLS to accomplish your task:
|
261
|
-
TOOLS AVAILABLE:
|
262
|
-
{format_rules}
|
263
|
-
|
264
|
-
{INSTRUCTION}
|
265
|
-
|
266
|
-
Now start, and be concise!
|
267
|
-
"""
|
268
|
-
|
269
296
|
def agent_response(
|
270
297
|
self,
|
271
298
|
msg: Optional[str | ChatDocument] = None,
|
@@ -338,11 +365,18 @@ class Agent(ABC):
|
|
338
365
|
if not user_msg:
|
339
366
|
return None
|
340
367
|
else:
|
368
|
+
if user_msg.startswith("SYSTEM"):
|
369
|
+
user_msg = user_msg[6:].strip()
|
370
|
+
source = Entity.SYSTEM
|
371
|
+
sender = Entity.SYSTEM
|
372
|
+
else:
|
373
|
+
source = Entity.USER
|
374
|
+
sender = Entity.USER
|
341
375
|
return ChatDocument(
|
342
376
|
content=user_msg,
|
343
377
|
metadata=DocMetaData(
|
344
|
-
source=
|
345
|
-
sender=
|
378
|
+
source=source,
|
379
|
+
sender=sender,
|
346
380
|
),
|
347
381
|
)
|
348
382
|
|
@@ -433,7 +467,12 @@ class Agent(ABC):
|
|
433
467
|
console.print(f"[green]{self.indent}", end="")
|
434
468
|
print("[green]" + response.message)
|
435
469
|
displayed = True
|
436
|
-
self.update_token_usage(
|
470
|
+
self.update_token_usage(
|
471
|
+
response,
|
472
|
+
prompt,
|
473
|
+
self.llm.get_stream(),
|
474
|
+
print_response_stats=settings.debug,
|
475
|
+
)
|
437
476
|
return ChatDocument.from_LLMResponse(response, displayed)
|
438
477
|
|
439
478
|
def get_tool_messages(self, msg: str | ChatDocument) -> List[ToolMessage]:
|
@@ -636,10 +675,11 @@ class Agent(ABC):
|
|
636
675
|
cumul_cost = format(tot_cost, ".4f")
|
637
676
|
assert isinstance(self.llm, LanguageModel)
|
638
677
|
context_length = self.llm.chat_context_length()
|
639
|
-
|
678
|
+
max_out = self.config.llm.max_output_tokens
|
640
679
|
print(
|
641
|
-
f"[bold]Stats:[/bold] [magenta] N_MSG={chat_length}, "
|
642
|
-
f"TOKENS: in={in_tokens}, out={out_tokens},
|
680
|
+
f"{self.indent}[bold]Stats:[/bold] [magenta] N_MSG={chat_length}, "
|
681
|
+
f"TOKENS: in={in_tokens}, out={out_tokens}, "
|
682
|
+
f"max={max_out}, ctx={context_length}, "
|
643
683
|
f"COST: now=${llm_response_cost}, cumul=${cumul_cost}[/magenta]"
|
644
684
|
""
|
645
685
|
)
|
@@ -1,5 +1,5 @@
|
|
1
|
-
import inspect
|
2
1
|
import logging
|
2
|
+
import textwrap
|
3
3
|
from contextlib import ExitStack
|
4
4
|
from typing import Dict, List, Optional, Set, Type, cast, no_type_check
|
5
5
|
|
@@ -28,8 +28,10 @@ class ChatAgentConfig(AgentConfig):
|
|
28
28
|
Configuration for ChatAgent
|
29
29
|
Attributes:
|
30
30
|
system_message: system message to include in message sequence
|
31
|
-
(typically defines role and task of agent)
|
32
|
-
|
31
|
+
(typically defines role and task of agent).
|
32
|
+
Used only if `task` is not specified in the constructor.
|
33
|
+
user_message: user message to include in message sequence.
|
34
|
+
Used only if `task` is not specified in the constructor.
|
33
35
|
use_tools: whether to use our own ToolMessages mechanism
|
34
36
|
use_functions_api: whether to use functions native to the LLM API
|
35
37
|
(e.g. OpenAI's `function_call` mechanism)
|
@@ -70,23 +72,53 @@ class ChatAgent(Agent):
|
|
70
72
|
self.config: ChatAgentConfig = config
|
71
73
|
self.message_history: List[LLMMessage] = []
|
72
74
|
self.tool_instructions_added: bool = False
|
73
|
-
# system
|
75
|
+
# An agent's "task" is defined by a system msg and an optional user msg;
|
76
|
+
# These are "priming" messages that kick off the agent's conversation.
|
77
|
+
self.system_message: str = self.config.system_message
|
78
|
+
self.user_message: str | None = self.config.user_message
|
79
|
+
|
80
|
+
if task is not None:
|
81
|
+
# if task contains a system msg, we override the config system msg
|
82
|
+
if len(task) > 0 and task[0].role == Role.SYSTEM:
|
83
|
+
self.system_message = task[0].content
|
84
|
+
# if task contains a user msg, we override the config user msg
|
85
|
+
if len(task) > 1 and task[1].role == Role.USER:
|
86
|
+
self.user_message = task[1].content
|
87
|
+
|
88
|
+
# system-level instructions for using tools/functions:
|
89
|
+
# We maintain these as tools/functions are enabled/disabled,
|
90
|
+
# and whenever an LLM response is sought, these are used to
|
91
|
+
# recreate the system message (via `_create_system_and_tools_message`)
|
92
|
+
# each time, so it reflects the current set of enabled tools/functions.
|
93
|
+
# (a) these are general instructions on using certain tools/functions,
|
94
|
+
# if they are specified in a ToolMessage class as a classmethod `instructions`
|
74
95
|
self.system_tool_instructions: str = ""
|
96
|
+
# (b) these are only for the builtin in Langroid TOOLS mechanism:
|
97
|
+
self.system_json_tool_instructions: str = ""
|
98
|
+
|
75
99
|
self.llm_functions_map: Dict[str, LLMFunctionSpec] = {}
|
76
100
|
self.llm_functions_handled: Set[str] = set()
|
77
101
|
self.llm_functions_usable: Set[str] = set()
|
78
102
|
self.llm_function_force: Optional[Dict[str, str]] = None
|
79
103
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
104
|
+
def set_system_message(self, msg: str) -> None:
|
105
|
+
self.system_message = msg
|
106
|
+
|
107
|
+
def set_user_message(self, msg: str) -> None:
|
108
|
+
self.user_message = msg
|
109
|
+
|
110
|
+
@property
|
111
|
+
def task_messages(self) -> List[LLMMessage]:
|
112
|
+
"""
|
113
|
+
The task messages are the initial messages that define the task
|
114
|
+
of the agent. There will be at least a system message plus possibly a user msg.
|
115
|
+
Returns:
|
116
|
+
List[LLMMessage]: the task messages
|
117
|
+
"""
|
118
|
+
msgs = [self._create_system_and_tools_message()]
|
119
|
+
if self.user_message:
|
120
|
+
msgs.append(LLMMessage(role=Role.USER, content=self.user_message))
|
121
|
+
return msgs
|
90
122
|
|
91
123
|
def clear_history(self, start: int = -2) -> None:
|
92
124
|
"""
|
@@ -122,31 +154,11 @@ class ChatAgent(Agent):
|
|
122
154
|
Args:
|
123
155
|
message (str): system message
|
124
156
|
"""
|
125
|
-
|
126
|
-
if self.message_history[0].role == Role.SYSTEM:
|
127
|
-
self.message_history[0].content = (
|
128
|
-
self.message_history[0].content + "\n\n" + message
|
129
|
-
)
|
130
|
-
else:
|
131
|
-
if self.task_messages[0].role == Role.SYSTEM:
|
132
|
-
self.task_messages[0].content = (
|
133
|
-
self.task_messages[0].content + "\n\n" + message
|
134
|
-
)
|
135
|
-
|
136
|
-
def add_user_message(self, message: str) -> None:
|
137
|
-
"""
|
138
|
-
Add a user message to the message history.
|
139
|
-
Args:
|
140
|
-
message (str): user message
|
141
|
-
"""
|
142
|
-
if len(self.message_history) > 0:
|
143
|
-
self.message_history.append(LLMMessage(role=Role.USER, content=message))
|
144
|
-
else:
|
145
|
-
self.task_messages.append(LLMMessage(role=Role.USER, content=message))
|
157
|
+
self.system_message += "\n\n" + message
|
146
158
|
|
147
159
|
def update_last_message(self, message: str, role: str = Role.USER) -> None:
|
148
160
|
"""
|
149
|
-
Update the last message
|
161
|
+
Update the last message that has role `role` in the message history.
|
150
162
|
Useful when we want to replace a long user prompt, that may contain context
|
151
163
|
documents plus a question, with just the question.
|
152
164
|
Args:
|
@@ -161,6 +173,33 @@ class ChatAgent(Agent):
|
|
161
173
|
self.message_history[i].content = message
|
162
174
|
break
|
163
175
|
|
176
|
+
def _create_system_and_tools_message(self) -> LLMMessage:
|
177
|
+
"""
|
178
|
+
(Re-)Create the system message for the LLM of the agent,
|
179
|
+
taking into account any tool instructions that have been added
|
180
|
+
after the agent was initialized.
|
181
|
+
|
182
|
+
The system message will consist of:
|
183
|
+
(a) the system message from the `task` arg in constructor, if any,
|
184
|
+
otherwise the default system message from the config
|
185
|
+
(b) the system tool instructions, if any
|
186
|
+
(c) the system json tool instructions, if any
|
187
|
+
|
188
|
+
Returns:
|
189
|
+
LLMMessage object
|
190
|
+
"""
|
191
|
+
content = textwrap.dedent(
|
192
|
+
f"""
|
193
|
+
{self.system_message}
|
194
|
+
|
195
|
+
{self.system_tool_instructions}
|
196
|
+
|
197
|
+
{self.system_json_tool_instructions}
|
198
|
+
|
199
|
+
""".lstrip()
|
200
|
+
)
|
201
|
+
return LLMMessage(role=Role.SYSTEM, content=content)
|
202
|
+
|
164
203
|
def enable_message(
|
165
204
|
self,
|
166
205
|
message_class: Optional[Type[ToolMessage]],
|
@@ -202,15 +241,7 @@ class ChatAgent(Agent):
|
|
202
241
|
self.llm_function_force = dict(name=request)
|
203
242
|
else:
|
204
243
|
self.llm_function_force = None
|
205
|
-
|
206
|
-
message_class.instructions
|
207
|
-
):
|
208
|
-
# save tool instructions so that when LLM is ready
|
209
|
-
# to respond (i.e. either when a wrapping Task is started,
|
210
|
-
# or when the LLM is directly queried),
|
211
|
-
# we can append to system msg.
|
212
|
-
self.system_tool_instructions += "\n\n" + message_class.instructions()
|
213
|
-
n_usable_tools = len(self.llm_tools_usable)
|
244
|
+
|
214
245
|
for t in tools:
|
215
246
|
if handle:
|
216
247
|
self.llm_tools_handled.add(t)
|
@@ -226,12 +257,10 @@ class ChatAgent(Agent):
|
|
226
257
|
self.llm_tools_usable.discard(t)
|
227
258
|
self.llm_functions_usable.discard(t)
|
228
259
|
|
229
|
-
#
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
# Update JSON format instructions if the set of usable tools has changed
|
234
|
-
self.update_json_tool_instructions()
|
260
|
+
# Set tool instructions and JSON format instructions
|
261
|
+
if self.config.use_tools:
|
262
|
+
self.system_json_tool_instructions = self.json_format_rules()
|
263
|
+
self.system_tool_instructions = self.tool_instructions()
|
235
264
|
|
236
265
|
def disable_message_handling(
|
237
266
|
self,
|
@@ -275,37 +304,6 @@ class ChatAgent(Agent):
|
|
275
304
|
self.llm_tools_usable.discard(r)
|
276
305
|
self.llm_functions_usable.discard(r)
|
277
306
|
|
278
|
-
def update_json_tool_instructions(self) -> None:
|
279
|
-
"""
|
280
|
-
Add special instructions on situations when the LLM should send JSON-formatted
|
281
|
-
messages, and save the index position of these instructions in the
|
282
|
-
message history. Note this specifically only relates to the
|
283
|
-
Langroid JSON tools mechanism, not the LLM-native function-calling.
|
284
|
-
"""
|
285
|
-
# Add the instructions as a user message...
|
286
|
-
# TODO need to adapt this based on model type, as some models may
|
287
|
-
# pay more attention to system message.
|
288
|
-
json_instructions = super().json_tool_format_instructions()
|
289
|
-
if not self.tool_instructions_added:
|
290
|
-
self.task_messages.append(
|
291
|
-
LLMMessage(role=Role.USER, content=json_instructions)
|
292
|
-
)
|
293
|
-
self.tool_instructions_added = True
|
294
|
-
else:
|
295
|
-
# json_instructions will contain instructions on ALL tools,
|
296
|
-
# so it is ok to overwrite the last message in the task_messages,
|
297
|
-
# since we know it is a tool-instruction msg.
|
298
|
-
self.task_messages[-1].content = json_instructions
|
299
|
-
|
300
|
-
# Note that task_messages is the initial set of messages created to set up
|
301
|
-
# the task, and they may not yet have been sent to the LLM at this point.
|
302
|
-
|
303
|
-
# But if the task_messages have already been sent to the LLM, then we need to
|
304
|
-
# update the self.message_history as well, since this history will be sent to
|
305
|
-
# the LLM on each round, after appending the latest assistant, user msgs.
|
306
|
-
if len(self.message_history) > 0:
|
307
|
-
self.message_history[-1].content = json_instructions
|
308
|
-
|
309
307
|
@no_type_check
|
310
308
|
def llm_response(
|
311
309
|
self, message: Optional[str | ChatDocument] = None
|
@@ -327,13 +325,13 @@ class ChatAgent(Agent):
|
|
327
325
|
), "message can be None only if message_history is empty, i.e. at start."
|
328
326
|
|
329
327
|
if len(self.message_history) == 0:
|
330
|
-
#
|
331
|
-
self.message_history = self.
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
"\n\n" + self.system_tool_instructions
|
328
|
+
# initial messages have not yet been loaded, so load them
|
329
|
+
self.message_history = [self._create_system_and_tools_message()]
|
330
|
+
if self.user_message:
|
331
|
+
self.message_history.append(
|
332
|
+
LLMMessage(role=Role.USER, content=self.user_message)
|
336
333
|
)
|
334
|
+
|
337
335
|
# for debugging, show the initial message history
|
338
336
|
if settings.debug:
|
339
337
|
print(
|
@@ -342,6 +340,10 @@ class ChatAgent(Agent):
|
|
342
340
|
{self.message_history_str()}
|
343
341
|
"""
|
344
342
|
)
|
343
|
+
else:
|
344
|
+
assert self.message_history[0].role == Role.SYSTEM
|
345
|
+
# update the system message with the latest tool instructions
|
346
|
+
self.message_history[0] = self._create_system_and_tools_message()
|
345
347
|
|
346
348
|
if message is not None:
|
347
349
|
llm_msg = ChatDocument.to_LLMMessage(message)
|
@@ -456,7 +458,9 @@ class ChatAgent(Agent):
|
|
456
458
|
response_str = response.message
|
457
459
|
print(cached + "[green]" + response_str)
|
458
460
|
stream = self.llm.get_stream() # type: ignore
|
459
|
-
self.update_token_usage(
|
461
|
+
self.update_token_usage(
|
462
|
+
response, messages, stream, print_response_stats=settings.debug
|
463
|
+
)
|
460
464
|
return ChatDocument.from_LLMResponse(response, displayed)
|
461
465
|
|
462
466
|
def _llm_response_temp_context(self, message: str, prompt: str) -> ChatDocument:
|
@@ -172,6 +172,8 @@ class ChatDocument(Document):
|
|
172
172
|
content = message.content
|
173
173
|
fun_call = message.function_call
|
174
174
|
sender_name = message.metadata.sender_name
|
175
|
+
if message.metadata.sender == Entity.SYSTEM:
|
176
|
+
sender_role = Role.SYSTEM
|
175
177
|
if (
|
176
178
|
message.metadata.parent is not None
|
177
179
|
and message.metadata.parent.function_call is not None
|
@@ -12,7 +12,6 @@ from langroid.agent.chat_document import (
|
|
12
12
|
ChatDocMetaData,
|
13
13
|
ChatDocument,
|
14
14
|
)
|
15
|
-
from langroid.language_models.base import LLMMessage, Role
|
16
15
|
from langroid.mytypes import Entity
|
17
16
|
from langroid.utils.configuration import settings
|
18
17
|
from langroid.utils.constants import DONE, NO_ANSWER, USER_QUIT
|
@@ -82,8 +81,8 @@ class Task:
|
|
82
81
|
and subsequent response by non-controller. If false, runs for the
|
83
82
|
specified number of turns in `run`, or until `done()` is true.
|
84
83
|
One run of step() is considered a "turn".
|
85
|
-
system_message (str): if not empty, overrides agent's
|
86
|
-
user_message (str): if not empty, overrides agent's
|
84
|
+
system_message (str): if not empty, overrides agent's system_message
|
85
|
+
user_message (str): if not empty, overrides agent's user_message
|
87
86
|
restart (bool): if true, resets the agent's message history
|
88
87
|
default_human_response (str): default response from user; useful for
|
89
88
|
testing, to avoid interactive input from user.
|
@@ -99,18 +98,14 @@ class Task:
|
|
99
98
|
"""
|
100
99
|
if isinstance(agent, ChatAgent) and len(agent.message_history) == 0 or restart:
|
101
100
|
agent = cast(ChatAgent, agent)
|
102
|
-
agent.
|
103
|
-
# possibly change the
|
101
|
+
agent.clear_history(0)
|
102
|
+
# possibly change the system and user messages
|
104
103
|
if system_message:
|
105
104
|
# we always have at least 1 task_message
|
106
|
-
agent.
|
105
|
+
agent.set_system_message(system_message)
|
107
106
|
if user_message:
|
108
|
-
agent.
|
109
|
-
|
110
|
-
role=Role.USER,
|
111
|
-
content=user_message,
|
112
|
-
)
|
113
|
-
)
|
107
|
+
agent.set_user_message(user_message)
|
108
|
+
|
114
109
|
self.logger: None | RichFileLogger = None
|
115
110
|
self.tsv_logger: None | logging.Logger = None
|
116
111
|
self.color_log: bool = True
|
@@ -231,6 +226,8 @@ class Task:
|
|
231
226
|
# the CURRENT task's USER entity
|
232
227
|
self.pending_message.metadata.sender = Entity.USER
|
233
228
|
|
229
|
+
self._show_pending_message_if_debug()
|
230
|
+
|
234
231
|
if self.caller is not None and self.caller.logger is not None:
|
235
232
|
self.logger = self.caller.logger
|
236
233
|
else:
|
@@ -257,8 +254,8 @@ class Task:
|
|
257
254
|
|
258
255
|
Args:
|
259
256
|
msg (str|ChatDocument): initial message to process; if None,
|
260
|
-
the LLM will respond to
|
261
|
-
which set up the overall task.
|
257
|
+
the LLM will respond to its initial `self.task_messages`
|
258
|
+
which set up and kick off the overall task.
|
262
259
|
The agent tries to achieve this goal by looping
|
263
260
|
over `self.step()` until the task is considered
|
264
261
|
done; this can involve a series of messages produced by Agent,
|
@@ -325,6 +322,8 @@ class Task:
|
|
325
322
|
n_messages = 0
|
326
323
|
if isinstance(self.agent, ChatAgent):
|
327
324
|
if self.erase_substeps:
|
325
|
+
# TODO I don't like directly accessing agent message_history. Revisit.
|
326
|
+
# (Pchalasani)
|
328
327
|
del self.agent.message_history[message_history_idx + 2 : n_messages - 1]
|
329
328
|
n_messages = len(self.agent.message_history)
|
330
329
|
if self.erase_substeps:
|
@@ -441,11 +440,16 @@ class Task:
|
|
441
440
|
self.pending_sender = responder
|
442
441
|
self.log_message(self.pending_sender, self.pending_message, mark=True)
|
443
442
|
|
443
|
+
self._show_pending_message_if_debug()
|
444
|
+
return self.pending_message
|
445
|
+
|
446
|
+
def _show_pending_message_if_debug(self) -> None:
|
447
|
+
if self.pending_message is None:
|
448
|
+
return
|
444
449
|
if settings.debug:
|
445
450
|
sender_str = str(self.pending_sender)
|
446
451
|
msg_str = str(self.pending_message)
|
447
452
|
print(f"[red][{sender_str}]{msg_str}")
|
448
|
-
return self.pending_message
|
449
453
|
|
450
454
|
def response(self, e: Responder, turns: int = -1) -> Optional[ChatDocument]:
|
451
455
|
"""
|
@@ -589,6 +593,7 @@ class Task:
|
|
589
593
|
Entity.LLM: "green",
|
590
594
|
Entity.USER: "blue",
|
591
595
|
Entity.AGENT: "red",
|
596
|
+
Entity.SYSTEM: "magenta",
|
592
597
|
}[msg.metadata.sender]
|
593
598
|
f = msg.log_fields()
|
594
599
|
tool_type = f.tool_type.rjust(6)
|
@@ -56,7 +56,7 @@ INSTRUCTION = """
|
|
56
56
|
In this case you realize there is no available TOOL for this, so you just ask in
|
57
57
|
natural language: "What is the population of France?"
|
58
58
|
|
59
|
-
Whenever possible, AND ONLY IF APPLICABLE, use these
|
59
|
+
Whenever possible, AND ONLY IF APPLICABLE, use these TOOLS, with the JSON syntax
|
60
60
|
specified above. When a TOOL is applicable, simply use this syntax, do not write
|
61
61
|
anything else. Only if no TOOL is exactly applicable, ask in natural language.
|
62
62
|
"""
|
@@ -80,7 +80,7 @@ extras_require = \
|
|
80
80
|
|
81
81
|
setup_kwargs = {
|
82
82
|
'name': 'langroid',
|
83
|
-
'version': '0.1.
|
83
|
+
'version': '0.1.62',
|
84
84
|
'description': 'Harness LLMs with Multi-Agent Programming',
|
85
85
|
'long_description': '<div align="center">\n <img src="docs/assets/langroid-card-lambda-ossem-rust-1200-630.png" alt="Logo" \n width="400" align="center">\n</div>\n\n<div align="center">\n\n[](https://pypi.org/project/langroid/)\n[](https://github.com/langroid/langroid/actions/workflows/pytest.yml)\n[](https://codecov.io/gh/langroid/langroid)\n[](https://github.com/langroid/langroid/actions/workflows/validate.yml)\n[](https://github.com/langroid/langroid/actions/workflows/mkdocs-deploy.yml)\n\n[](https://langroid.github.io/langroid)\n[](https://discord.gg/ZU36McDgDs)\n[](https://colab.research.google.com/github/langroid/langroid/blob/main/examples/langroid_quick_examples.ipynb)\n\n[](https://hub.docker.com/r/langroid/langroid)\n\n[](https://github.com/langroid/langroid/actions/workflows/docker-publish.yml)\n\n[](https://langroid.substack.com/p/langroid-harness-llms-with-multi-agent-programming)\n\n[](https://news.ycombinator.com/submitlink?u=https%3A%2F%2Fgithub.com%2Flangroid%2Flangroid&t=Harness%20LLMs%20with%20Multi-Agent%20Programming)\n[](https://www.reddit.com/submit?url=https%3A%2F%2Fgithub.com%2Flangroid%2Flangroid&title=Harness%20LLMs%20with%20Multi-Agent%20Programming)\n[](https://twitter.com/intent/tweet?text=Langroid%20is%20a%20powerful,%20elegant%20new%20framework%20to%20easily%20build%20%23LLM%20applications.%20You%20set%20up%20LLM-powered%20Agents%20with%20vector-stores,%20assign%20tasks,%20and%20have%20them%20collaboratively%20solve%20problems%20via%20message-transformations.%20https://github.com/langroid/langroid)\n[](https://www.linkedin.com/shareArticle?mini=true&url=https://github.com/langroid/langroid&title=Langroid:%20A%20Powerful,%20Elegant%20Framework&summary=Langroid%20is%20a%20powerful,%20elegant%20new%20framework%20to%20easily%20build%20%23LLM%20applications.%20You%20set%20up%20LLM-powered%20Agents%20with%20vector-stores,%20assign%20tasks,%20and%20have%20them%20collaboratively%20solve%20problems%20via%20message-transformations.)\n\n\n</div>\n\n<h3 align="center">\n <a target="_blank" \n href="https://langroid.github.io/langroid/" rel="dofollow">\n <strong>Documentation</strong></a>\n ·\n <a target="_blank" href="https://github.com/langroid/langroid-examples" rel="dofollow">\n <strong>Examples Repo</strong></a>\n ·\n <a target="_blank" href="https://discord.gg/ZU36McDgDs" rel="dofollow">\n <strong>Discord</strong></a>\n ·\n <a target="_blank" href="./CONTRIBUTING.md" rel="dofollow">\n <strong>Contributing</strong></a>\n\n <br />\n</h3>\n\n`Langroid` is an intuitive, lightweight, extensible and principled\nPython framework to easily build LLM-powered applications. \nYou set up Agents, equip them with optional components (LLM, \nvector-store and methods), assign them tasks, and have them \ncollaboratively solve a problem by exchanging messages. \nThis Multi-Agent paradigm is inspired by the\n[Actor Framework](https://en.wikipedia.org/wiki/Actor_model)\n(but you do not need to know anything about this!). \n\nLangroid is a fresh take on LLM app-development, where considerable thought has gone \ninto simplifying the developer experience. It does not use `Langchain` or `Llama-Index`.\n\nWe welcome contributions -- See the [contributions](./CONTRIBUTING.md) document\nfor ideas on what to contribute.\n\n**Questions, Feedback, Ideas? Join us on [Discord](https://discord.gg/ZU36McDgDs)!**\n\n<details>\n<summary> <b>:fire: Updates/Releases</b></summary>\n\n- **Sep 2023:**\n - **Use with local LLama Models:** see tutorial [here](https://langroid.github.io/langroid/blog/2023/09/14/using-langroid-with-local-llms/)\n - **Langroid Blog/Newsletter Launched!**: First post is [here](https://substack.com/notes/post/p-136704592) -- Please subscribe to stay updated. \n - **0.1.56:** Support Azure OpenAI. \n - **0.1.55:** Improved [`SQLChatAgent`](https://github.com/langroid/langroid/blob/main/langroid/agent/special/sql/sql_chat_agent.py) that efficiently retrieves relevant schema info when translating natural language to SQL. \n- **Aug 2023:**\n - **[Hierarchical computation](https://langroid.github.io/langroid/examples/agent-tree/)** example using Langroid agents and task orchestration.\n - **0.1.51:** Support for global state, see [test_global_state.py](tests/main/test_global_state.py).\n - **:whale: Langroid Docker image**, available, see instructions below.\n - [**RecipientTool**](langroid/agent/tools/recipient_tool.py) enables (+ enforces) LLM to \nspecify an intended recipient when talking to 2 or more agents. \nSee [this test](tests/main/test_recipient_tool.py) for example usage.\n - **Example:** [Answer questions](examples/docqa/chat-search.py) using Google Search + vecdb-retrieval from URL contents. \n - **0.1.39:** [`GoogleSearchTool`](langroid/agent/tools/google_search_tool.py) to enable Agents (their LLM) to do Google searches via function-calling/tools.\n See [this chat example](examples/basic/chat-search.py) for how easy it is to add this tool to an agent.\n - **Colab notebook** to try the quick-start examples: [](https://colab.research.google.com/github/langroid/langroid/blob/main/examples/langroid_quick_examples.ipynb) \n - **0.1.37:** Added [`SQLChatAgent`](langroid/agent/special/sql_chat_agent.py) -- thanks to our latest contributor [Rithwik Babu](https://github.com/rithwikbabu)!\n - Multi-agent Example: [Autocorrect chat](examples/basic/autocorrect.py)\n- **July 2023:** \n - **0.1.30:** Added [`TableChatAgent`](langroid/agent/special/table_chat_agent.py) to \n [chat](examples/data-qa/table_chat.py) with tabular datasets (dataframes, files, URLs): LLM generates Pandas code,\n and code is executed using Langroid\'s tool/function-call mechanism. \n - **Demo:** 3-agent system for Audience [Targeting](https://langroid.github.io/langroid/demos/targeting/audience-targeting/).\n - **0.1.27**: Added [support](langroid/cachedb/momento_cachedb.py) \n for [Momento Serverless Cache](https://www.gomomento.com/) as an alternative to Redis.\n - **0.1.24**: [`DocChatAgent`](langroid/agent/special/doc_chat_agent.py) \n now [accepts](langroid/parsing/pdf_parser.py) PDF files or URLs.\n\n</details>\n\n# :rocket: Demo\nSuppose you want to extract structured information about the key terms \nof a commercial lease document. You can easily do this with Langroid using a two-agent system,\nas we show in the [langroid-examples](https://github.com/langroid/langroid-examples/blob/main/examples/docqa/chat_multi_extract.py) repo.\nThe demo showcases just a few of the many features of Langroid, such as:\n- Multi-agent collaboration: `LeaseExtractor` is in charge of the task, and its LLM (GPT4) generates questions \nto be answered by the `DocAgent`.\n- Retrieval augmented question-answering, with **source-citation**: `DocAgent` LLM (GPT4) uses retrieval from a vector-store to \nanswer the `LeaseExtractor`\'s questions, cites the specific excerpt supporting the answer. \n- Function-calling (also known as tool/plugin): When it has all the information it \nneeds, the `LeaseExtractor` LLM presents the information in a structured \nformat using a Function-call. \n\nHere is what it looks like in action:\n\n\n\n\n# :zap: Highlights\n\n- **Agents as first-class citizens:** The [Agent](https://langroid.github.io/langroid/reference/agent/base/#langroid.agent.base.Agent) class encapsulates LLM conversation state,\n and optionally a vector-store and tools. Agents are a core abstraction in Langroid;\n Agents act as _message transformers_, and by default provide 3 _responder_ methods, one corresponding to each entity: LLM, Agent, User.\n- **Tasks:** A [Task](https://langroid.github.io/langroid/reference/agent/task/) class wraps an Agent, and gives the agent instructions (or roles, or goals), \n manages iteration over an Agent\'s responder methods, \n and orchestrates multi-agent interactions via hierarchical, recursive\n task-delegation. The `Task.run()` method has the same \n type-signature as an Agent\'s responder\'s methods, and this is key to how \n a task of an agent can delegate to other sub-tasks: from the point of view of a Task,\n sub-tasks are simply additional responders, to be used in a round-robin fashion \n after the agent\'s own responders.\n- **Modularity, Reusabilily, Loose coupling:** The `Agent` and `Task` abstractions allow users to design\n Agents with specific skills, wrap them in Tasks, and combine tasks in a flexible way.\n- **LLM Support**: Langroid supports OpenAI LLMs including GPT-3.5-Turbo,\n GPT-4.\n- **Caching of LLM responses:** Langroid supports [Redis](https://redis.com/try-free/) and \n [Momento](https://www.gomomento.com/) to cache LLM responses.\n- **Vector-stores**: [Qdrant](https://qdrant.tech/) and [Chroma](https://www.trychroma.com/) are currently supported.\n Vector stores allow for Retrieval-Augmented-Generation (RAG).\n- **Grounding and source-citation:** Access to external documents via vector-stores \n allows for grounding and source-citation.\n- **Observability, Logging, Lineage:** Langroid generates detailed logs of multi-agent interactions and\n maintains provenance/lineage of messages, so that you can trace back\n the origin of a message.\n- **Tools/Plugins/Function-calling**: Langroid supports OpenAI\'s recently\n released [function calling](https://platform.openai.com/docs/guides/gpt/function-calling)\n feature. In addition, Langroid has its own native equivalent, which we\n call **tools** (also known as "plugins" in other contexts). Function\n calling and tools have the same developer-facing interface, implemented\n using [Pydantic](https://docs.pydantic.dev/latest/),\n which makes it very easy to define tools/functions and enable agents\n to use them. Benefits of using Pydantic are that you never have to write\n complex JSON specs for function calling, and when the LLM\n hallucinates malformed JSON, the Pydantic error message is sent back to\n the LLM so it can fix it!\n\n--- \n\n# :gear: Installation and Setup\n\n:whale: For a simpler setup, see the Docker section below, which lets you get started just\nby setting up environment variables in a `.env` file.\n\n### Install `langroid`\nLangroid requires Python 3.11+. We recommend using a virtual environment.\nUse `pip` to install `langroid` (from PyPi) to your virtual environment:\n```bash\npip install langroid\n```\nThe core Langroid package lets you use OpenAI Embeddings models via their API. \nIf you instead want to use the `all-MiniLM-L6-v2` embeddings model\nfrom from HuggingFace, install Langroid like this:\n```bash\npip install langroid[hf-embeddings]\n```\nNote that this will install `torch` and `sentence-transformers` libraries.\n\n<details>\n<summary><b>Optional Installs for using SQL Chat with a PostgreSQL DB </b></summary>\n\nIf you are using `SQLChatAgent` \n(e.g. the script [`examples/data-qa/sql-chat/sql_chat.py`](examples/data-qa/sql-chat/sql_chat.py)),\nwith a postgres db, you will need to:\n\n- Install PostgreSQL dev libraries for your platform, e.g.\n - `sudo apt-get install libpq-dev` on Ubuntu,\n - `brew install postgresql` on Mac, etc.\n- Install langroid with the postgres extra, e.g. `pip install langroid[postgres]`\n or `poetry add langroid[postgres]` or `poetry install -E postgres`.\n If this gives you an error, try `pip install psycopg2-binary` in your virtualenv.\n</details>\n\n### Set up environment variables (API keys, etc)\n\nTo get started, all you need is an OpenAI API Key.\nIf you don\'t have one, see [this OpenAI Page](https://help.openai.com/en/collections/3675940-getting-started-with-openai-api).\nCurrently only OpenAI models are supported. Others will be added later\n(Pull Requests welcome!).\n\nIn the root of the repo, copy the `.env-template` file to a new file `.env`: \n```bash\ncp .env-template .env\n```\nThen insert your OpenAI API Key. \nYour `.env` file should look like this:\n```bash\nOPENAI_API_KEY=your-key-here-without-quotes\n````\n\nAlternatively, you can set this as an environment variable in your shell\n(you will need to do this every time you open a new shell):\n```bash\nexport OPENAI_API_KEY=your-key-here-without-quotes\n```\n\n\n<details>\n<summary><b>Optional Setup Instructions (click to expand) </b></summary>\n\nAll of the following environment variable settings are optional, and some are only needed \nto use specific features (as noted below).\n\n- **Qdrant** Vector Store API Key, URL. This is only required if you want to use Qdrant cloud.\n You can sign up for a free 1GB account at [Qdrant cloud](https://cloud.qdrant.io).\n If you skip setting up these, Langroid will use Qdrant in local-storage mode.\n Alternatively [Chroma](https://docs.trychroma.com/) is also currently supported. \n We use the local-storage version of Chroma, so there is no need for an API key.\n Langroid uses Qdrant by default.\n- **Redis** Password, host, port: This is optional, and only needed to cache LLM API responses\n using Redis Cloud. Redis [offers](https://redis.com/try-free/) a free 30MB Redis account\n which is more than sufficient to try out Langroid and even beyond.\n If you don\'t set up these, Langroid will use a pure-python \n Redis in-memory cache via the [Fakeredis](https://fakeredis.readthedocs.io/en/latest/) library.\n- **Momento** Serverless Caching of LLM API responses (as an alternative to Redis). \n To use Momento instead of Redis:\n - enter your Momento Token in the `.env` file, as the value of `MOMENTO_AUTH_TOKEN` (see example file below),\n - in the `.env` file set `CACHE_TYPE=momento` (instead of `CACHE_TYPE=redis` which is the default).\n- **GitHub** Personal Access Token (required for apps that need to analyze git\n repos; token-based API calls are less rate-limited). See this\n [GitHub page](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens).\n- **Google Custom Search API Credentials:** Only needed to enable an Agent to use the `GoogleSearchTool`.\n To use Google Search as an LLM Tool/Plugin/function-call, \n you\'ll need to set up \n [a Google API key](https://developers.google.com/custom-search/v1/introduction#identify_your_application_to_google_with_api_key),\n then [setup a Google Custom Search Engine (CSE) and get the CSE ID](https://developers.google.com/custom-search/docs/tutorial/creatingcse).\n (Documentation for these can be challenging, we suggest asking GPT4 for a step-by-step guide.)\n After obtaining these credentials, store them as values of \n `GOOGLE_API_KEY` and `GOOGLE_CSE_ID` in your `.env` file. \n Full documentation on using this (and other such "stateless" tools) is coming soon, but \n in the meantime take a peek at this [chat example](examples/basic/chat-search.py), which \n shows how you can easily equip an Agent with a `GoogleSearchtool`.\n \n\n\nIf you add all of these optional variables, your `.env` file should look like this:\n```bash\nOPENAI_API_KEY=your-key-here-without-quotes\nGITHUB_ACCESS_TOKEN=your-personal-access-token-no-quotes\nCACHE_TYPE=redis # or momento\nREDIS_PASSWORD=your-redis-password-no-quotes\nREDIS_HOST=your-redis-hostname-no-quotes\nREDIS_PORT=your-redis-port-no-quotes\nMOMENTO_AUTH_TOKEN=your-momento-token-no-quotes # instead of REDIS* variables\nQDRANT_API_KEY=your-key\nQDRANT_API_URL=https://your.url.here:6333 # note port number must be included\nGOOGLE_API_KEY=your-key\nGOOGLE_CSE_ID=your-cse-id\n```\n</details>\n\n<details>\n<summary><b>Optional setup instructions for Microsoft Azure OpenAI(click to expand)</b></summary> \n\nWhen using Azure OpenAI, additional environment variables are required in the \n`.env` file.\nThis page [Microsoft Azure OpenAI](https://learn.microsoft.com/en-us/azure/ai-services/openai/chatgpt-quickstart?tabs=command-line&pivots=programming-language-python#environment-variables)\nprovides more information, and you can set each environment variable as follows:\n\n- `AZURE_API_KEY`, from the value of `API_KEY`\n- `AZURE_OPENAI_API_BASE` from the value of `ENDPOINT`, typically looks like `https://your.domain.azure.com`.\n- For `AZURE_OPENAI_API_VERSION`, you can use the default value in `.env-template`, and latest version can be found [here](https://learn.microsoft.com/en-us/azure/ai-services/openai/whats-new#azure-openai-chat-completion-general-availability-ga)\n- `AZURE_OPENAI_DEPLOYMENT_NAME` is the name of the deployed model, which is defined by the user during the model setup \n- `AZURE_GPT_MODEL_NAME` GPT-3.5-Turbo or GPT-4 model names that you chose when you setup your Azure OpenAI account.\n\n</details>\n\n---\n\n# :whale: Docker Instructions\n\nWe provide a containerized version of the [`langroid-examples`](https://github.com/langroid/langroid-examples) \nrepository via this [Docker Image](https://hub.docker.com/r/langroid/langroid).\nAll you need to do is set up environment variables in the `.env` file.\nPlease follow these steps to setup the container:\n\n```bash\n# get the .env file template from `langroid` repo\nwget https://github.com/langroid/langroid/blob/main/.env-template .env\n\n# Edit the .env file with your favorite editor (here nano), \n# and add API keys as explained above\nnano .env\n\n# launch the container\ndocker run -it -v ./.env:/.env langroid/langroid\n\n# Use this command to run any of the scripts in the `examples` directory\npython examples/<Path/To/Example.py> \n``` \n\n\n\n# :tada: Usage Examples\n\nThese are quick teasers to give a glimpse of what you can do with Langroid\nand how your code would look. \n\n:warning: The code snippets below are intended to give a flavor of the code\nand they are **not** complete runnable examples! For that we encourage you to \nconsult the [`langroid-examples`](https://github.com/langroid/langroid-examples) \nrepository.\n\n:information_source: The various LLM prompts and instructions in Langroid\nhave been tested to work well with GPT4.\nSwitching to GPT3.5-Turbo is easy via a config flag\n(e.g., `cfg = OpenAIGPTConfig(chat_model=OpenAIChatModel.GPT3_5_TURBO)`),\nand may suffice for some applications, but in general you may see inferior results.\n\n\n:book: Also see the\n[`Getting Started Guide`](https://langroid.github.io/langroid/quick-start/)\nfor a detailed tutorial.\n\n\n\nClick to expand any of the code examples below.\nAll of these can be run in a Colab notebook:\n[](https://colab.research.google.com/github/langroid/langroid/blob/main/examples/langroid_quick_examples.ipynb)\n\n<details>\n<summary> <b> Direct interaction with OpenAI LLM </b> </summary>\n\n```python\nfrom langroid.language_models.openai_gpt import ( \n OpenAIGPTConfig, OpenAIChatModel, OpenAIGPT,\n)\nfrom langroid.language_models.base import LLMMessage, Role\n\ncfg = OpenAIGPTConfig(chat_model=OpenAIChatModel.GPT4)\n\nmdl = OpenAIGPT(cfg)\n\nmessages = [\n LLMMessage(content="You are a helpful assistant", role=Role.SYSTEM), \n LLMMessage(content="What is the capital of Ontario?", role=Role.USER),\n]\nresponse = mdl.chat(messages, max_tokens=200)\nprint(response.message)\n```\n</details>\n\n<details>\n<summary> <b> Define an agent, set up a task, and run it </b> </summary>\n\n```python\nfrom langroid.agent.chat_agent import ChatAgent, ChatAgentConfig\nfrom langroid.agent.task import Task\nfrom langroid.language_models.openai_gpt import OpenAIChatModel, OpenAIGPTConfig\n\nconfig = ChatAgentConfig(\n llm = OpenAIGPTConfig(\n chat_model=OpenAIChatModel.GPT4,\n ),\n vecdb=None, # no vector store\n)\nagent = ChatAgent(config)\n# get response from agent\'s LLM, and put this in an interactive loop...\n# answer = agent.llm_response("What is the capital of Ontario?")\n # ... OR instead, set up a task (which has a built-in loop) and run it\ntask = Task(agent, name="Bot") \ntask.run() # ... a loop seeking response from LLM or User at each turn\n```\n</details>\n\n<details>\n<summary><b> Three communicating agents </b></summary>\n\nA toy numbers game, where when given a number `n`:\n- `repeater_agent`\'s LLM simply returns `n`,\n- `even_agent`\'s LLM returns `n/2` if `n` is even, else says "DO-NOT-KNOW"\n- `odd_agent`\'s LLM returns `3*n+1` if `n` is odd, else says "DO-NOT-KNOW"\n\nFirst define the 3 agents, and set up their tasks with instructions:\n\n```python\nfrom langroid.utils.constants import NO_ANSWER\nfrom langroid.agent.chat_agent import ChatAgent, ChatAgentConfig\nfrom langroid.agent.task import Task\nfrom langroid.language_models.openai_gpt import OpenAIChatModel, OpenAIGPTConfig\nconfig = ChatAgentConfig(\n llm = OpenAIGPTConfig(\n chat_model=OpenAIChatModel.GPT4,\n ),\n vecdb = None,\n)\nrepeater_agent = ChatAgent(config)\nrepeater_task = Task(\n repeater_agent,\n name = "Repeater",\n system_message="""\n Your job is to repeat whatever number you receive.\n """,\n llm_delegate=True, # LLM takes charge of task\n single_round=False, \n)\neven_agent = ChatAgent(config)\neven_task = Task(\n even_agent,\n name = "EvenHandler",\n system_message=f"""\n You will be given a number. \n If it is even, divide by 2 and say the result, nothing else.\n If it is odd, say {NO_ANSWER}\n """,\n single_round=True, # task done after 1 step() with valid response\n)\n\nodd_agent = ChatAgent(config)\nodd_task = Task(\n odd_agent,\n name = "OddHandler",\n system_message=f"""\n You will be given a number n. \n If it is odd, return (n*3+1), say nothing else. \n If it is even, say {NO_ANSWER}\n """,\n single_round=True, # task done after 1 step() with valid response\n)\n```\nThen add the `even_task` and `odd_task` as sub-tasks of `repeater_task`, \nand run the `repeater_task`, kicking it off with a number as input:\n```python\nrepeater_task.add_sub_task([even_task, odd_task])\nrepeater_task.run("3")\n```\n\n</details>\n\n<details>\n<summary><b> Simple Tool/Function-calling example </b></summary>\n\nLangroid leverages Pydantic to support OpenAI\'s\n[Function-calling API](https://platform.openai.com/docs/guides/gpt/function-calling)\nas well as its own native tools. The benefits are that you don\'t have to write\nany JSON to specify the schema, and also if the LLM hallucinates a malformed\ntool syntax, Langroid sends the Pydantic validation error (suitiably sanitized) \nto the LLM so it can fix it!\n\nSimple example: Say the agent has a secret list of numbers, \nand we want the LLM to find the smallest number in the list. \nWe want to give the LLM a `probe` tool/function which takes a\nsingle number `n` as argument. The tool handler method in the agent\nreturns how many numbers in its list are at most `n`.\n\nFirst define the tool using Langroid\'s `ToolMessage` class:\n\n\n```python\nfrom langroid.agent.tool_message import ToolMessage\nclass ProbeTool(ToolMessage):\n request: str = "probe" # specifies which agent method handles this tool\n purpose: str = """\n To find how many numbers in my list are less than or equal to \n the <number> you specify.\n """ # description used to instruct the LLM on when/how to use the tool\n number: int # required argument to the tool\n```\n\nThen define a `SpyGameAgent` as a subclass of `ChatAgent`, \nwith a method `probe` that handles this tool:\n\n```python\nfrom langroid.agent.chat_agent import ChatAgent, ChatAgentConfig\nclass SpyGameAgent(ChatAgent):\n def __init__(self, config: ChatAgentConfig):\n super().__init__(config)\n self.numbers = [3, 4, 8, 11, 15, 25, 40, 80, 90]\n\n def probe(self, msg: ProbeTool) -> str:\n # return how many numbers in self.numbers are less or equal to msg.number\n return str(len([n for n in self.numbers if n <= msg.number]))\n```\n\nWe then instantiate the agent and enable it to use and respond to the tool:\n\n```python\nfrom langroid.language_models.openai_gpt import OpenAIChatModel, OpenAIGPTConfig\nspy_game_agent = SpyGameAgent(\n ChatAgentConfig(\n name="Spy",\n llm = OpenAIGPTConfig(\n chat_model=OpenAIChatModel.GPT4,\n ),\n vecdb=None,\n use_tools=False, # don\'t use Langroid native tool\n use_functions_api=True, # use OpenAI function-call API\n )\n)\nspy_game_agent.enable_message(ProbeTool)\n```\n\nFor a full working example see the\n[chat-agent-tool.py](https://github.com/langroid/langroid-examples/blob/main/examples/quick-start/chat-agent-tool.py)\nscript in the `langroid-examples` repo.\n</details>\n\n<details>\n<summary> <b>Tool/Function-calling to extract structured information from text </b> </summary>\n\nSuppose you want an agent to extract \nthe key terms of a lease, from a lease document, as a nested JSON structure.\nFirst define the desired structure via Pydantic models:\n\n```python\nfrom pydantic import BaseModel\nclass LeasePeriod(BaseModel):\n start_date: str\n end_date: str\n\n\nclass LeaseFinancials(BaseModel):\n monthly_rent: str\n deposit: str\n\nclass Lease(BaseModel):\n period: LeasePeriod\n financials: LeaseFinancials\n address: str\n```\n\nThen define the `LeaseMessage` tool as a subclass of Langroid\'s `ToolMessage`.\nNote the tool has a required argument `terms` of type `Lease`:\n\n```python\nclass LeaseMessage(ToolMessage):\n request: str = "lease_info"\n purpose: str = """\n Collect information about a Commercial Lease.\n """\n terms: Lease\n```\n\nThen define a `LeaseExtractorAgent` with a method `lease_info` that handles this tool,\ninstantiate the agent, and enable it to use and respond to this tool:\n\n```python\nclass LeaseExtractorAgent(ChatAgent):\n def lease_info(self, message: LeaseMessage) -> str:\n print(\n f"""\n DONE! Successfully extracted Lease Info:\n {message.terms}\n """\n )\n return json.dumps(message.terms.dict())\n \nlease_extractor_agent = LeaseExtractorAgent(\n ChatAgentConfig(\n llm=OpenAIGPTConfig(),\n use_functions_api=False,\n use_tools=True,\n )\n)\nlease_extractor_agent.enable_message(LeaseMessage)\n```\n\nSee the [`chat_multi_extract.py`](https://github.com/langroid/langroid-examples/blob/main/examples/docqa/chat_multi_extract.py)\nscript in the `langroid-examples` repo for a full working example.\n</details>\n\n<details>\n<summary><b> Chat with documents (file paths, URLs, etc) </b></summary>\n\nLangroid provides a specialized agent class `DocChatAgent` for this purpose.\nIt incorporates document sharding, embedding, storage in a vector-DB, \nand retrieval-augmented query-answer generation.\nUsing this class to chat with a collection of documents is easy.\nFirst create a `DocChatAgentConfig` instance, with a \n`doc_paths` field that specifies the documents to chat with.\n\n```python\nfrom langroid.agent.doc_chat_agent import DocChatAgentConfig\nconfig = DocChatAgentConfig(\n doc_paths = [\n "https://en.wikipedia.org/wiki/Language_model",\n "https://en.wikipedia.org/wiki/N-gram_language_model",\n "/path/to/my/notes-on-language-models.txt",\n ]\n llm = OpenAIGPTConfig(\n chat_model=OpenAIChatModel.GPT4,\n ),\n vecdb=VectorStoreConfig(\n type="qdrant",\n ),\n)\n```\n\nThen instantiate the `DocChatAgent` (this ingests the docs into the vector-store):\n\n```python\nagent = DocChatAgent(config)\n```\nThen we can either ask the agent one-off questions,\n```python\nagent.chat("What is a language model?")\n```\nor wrap it in a `Task` and run an interactive loop with the user:\n```python\nfrom langroid.task import Task\ntask = Task(agent)\ntask.run()\n```\n\nSee full working scripts in the \n[`docqa`](https://github.com/langroid/langroid-examples/tree/main/examples/docqa)\nfolder of the `langroid-examples` repo.\n</details>\n\n<details>\n<summary><b> :fire: Chat with tabular data (file paths, URLs, dataframes) </b></summary>\n\nUsing Langroid you can set up a `TableChatAgent` with a dataset (file path, URL or dataframe),\nand query it. The Agent\'s LLM generates Pandas code to answer the query, \nvia function-calling (or tool/plugin), and the Agent\'s function-handling method\nexecutes the code and returns the answer.\n\nHere is how you can do this:\n\n```python\nfrom langroid.agent.special.table_chat_agent import TableChatAgent, TableChatAgentConfig\nfrom langroid.agent.task import Task\nfrom langroid.language_models.openai_gpt import OpenAIChatModel, OpenAIGPTConfig\n```\n\nSet up a `TableChatAgent` for a data file, URL or dataframe\n(Ensure the data table has a header row; the delimiter/separator is auto-detected):\n```python\ndataset = "https://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-red.csv"\n# or dataset = "/path/to/my/data.csv"\n# or dataset = pd.read_csv("/path/to/my/data.csv")\nagent = TableChatAgent(\n config=TableChatAgentConfig(\n data=dataset, \n llm=OpenAIGPTConfig(\n chat_model=OpenAIChatModel.GPT4,\n ),\n )\n)\n```\nSet up a task, and ask one-off questions like this: \n\n```python\ntask = Task(\n agent, \n name = "DataAssistant",\n default_human_response="", # to avoid waiting for user input\n)\nresult = task.run(\n "What is the average alcohol content of wines with a quality rating above 7?",\n turns=2 # return after user question, LLM fun-call/tool response, Agent code-exec result\n) \nprint(result.content)\n```\nOr alternatively, set up a task and run it in an interactive loop with the user:\n\n```python\ntask = Task(agent, name="DataAssistant")\ntask.run()\n``` \n\nFor a full working example see the \n[`table_chat.py`](https://github.com/langroid/langroid-examples/tree/main/examples/data-qa/table_chat.py)\nscript in the `langroid-examples` repo.\n\n\n</details>\n\n---\n\n# :heart: Thank you to our [supporters](https://github.com/langroid/langroid/stargazers)\n\nIf you like this project, please give it a star ⭐ and 📢 spread the word in your network or social media:\n\n[](https://twitter.com/intent/tweet?text=Langroid%20is%20a%20powerful,%20elegant%20new%20framework%20to%20easily%20build%20%23LLM%20applications.%20You%20set%20up%20LLM-powered%20Agents%20with%20vector-stores,%20assign%20tasks,%20and%20have%20them%20collaboratively%20solve%20problems%20via%20message-transformations.%20https://github.com/langroid/langroid)\n[](https://www.linkedin.com/shareArticle?mini=true&url=https://github.com/langroid/langroid&title=Langroid:%20A%20Powerful,%20Elegant%20Framework&summary=Langroid%20is%20a%20powerful,%20elegant%20new%20framework%20to%20easily%20build%20%23LLM%20applications.%20You%20set%20up%20LLM-powered%20Agents%20with%20vector-stores,%20assign%20tasks,%20and%20have%20them%20collaboratively%20solve%20problems%20via%20message-transformations.)\n[](https://news.ycombinator.com/submitlink?u=https%3A%2F%2Fgithub.com%2Flangroid%2Flangroid&t=Harness%20LLMs%20with%20Multi-Agent%20Programming)\n[](https://www.reddit.com/submit?url=https%3A%2F%2Fgithub.com%2Flangroid%2Flangroid&title=Harness%20LLMs%20with%20Multi-Agent%20Programming)\n\n\n\n\nYour support will help build Langroid\'s momentum and community.\n\n\n\n\n# Langroid Co-Founders\n\n- [Prasad Chalasani](https://www.linkedin.com/in/pchalasani/) (IIT BTech/CS, CMU PhD/ML; Independent ML Consultant)\n- [Somesh Jha](https://www.linkedin.com/in/somesh-jha-80208015/) (IIT BTech/CS, CMU PhD/CS; Professor of CS, U Wisc at Madison)\n\n\n\n',
|
86
86
|
'author': 'Prasad Chalasani',
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{langroid-0.1.61 → langroid-0.1.62}/langroid/agent/special/sql/utils/description_extractors.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{langroid-0.1.61 → langroid-0.1.62}/langroid/language_models/prompt_formatter/llama2_formatter.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|