langroid 0.1.85__py3-none-any.whl → 0.1.219__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 +95 -0
- langroid/agent/__init__.py +40 -0
- langroid/agent/base.py +222 -91
- langroid/agent/batch.py +264 -0
- langroid/agent/callbacks/chainlit.py +608 -0
- langroid/agent/chat_agent.py +247 -101
- langroid/agent/chat_document.py +41 -4
- langroid/agent/openai_assistant.py +842 -0
- langroid/agent/special/__init__.py +50 -0
- langroid/agent/special/doc_chat_agent.py +837 -141
- langroid/agent/special/lance_doc_chat_agent.py +258 -0
- langroid/agent/special/lance_rag/__init__.py +9 -0
- langroid/agent/special/lance_rag/critic_agent.py +136 -0
- langroid/agent/special/lance_rag/lance_rag_task.py +80 -0
- langroid/agent/special/lance_rag/query_planner_agent.py +180 -0
- langroid/agent/special/lance_tools.py +44 -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 +370 -0
- langroid/agent/special/neo4j/utils/__init__.py +0 -0
- langroid/agent/special/neo4j/utils/system_message.py +46 -0
- langroid/agent/special/relevance_extractor_agent.py +127 -0
- langroid/agent/special/retriever_agent.py +32 -198
- langroid/agent/special/sql/__init__.py +11 -0
- langroid/agent/special/sql/sql_chat_agent.py +47 -23
- langroid/agent/special/sql/utils/__init__.py +22 -0
- langroid/agent/special/sql/utils/description_extractors.py +95 -46
- langroid/agent/special/sql/utils/populate_metadata.py +28 -21
- langroid/agent/special/table_chat_agent.py +43 -9
- langroid/agent/task.py +475 -122
- langroid/agent/tool_message.py +75 -13
- langroid/agent/tools/__init__.py +13 -0
- langroid/agent/tools/duckduckgo_search_tool.py +66 -0
- langroid/agent/tools/google_search_tool.py +11 -0
- langroid/agent/tools/metaphor_search_tool.py +67 -0
- langroid/agent/tools/recipient_tool.py +16 -29
- langroid/agent/tools/run_python_code.py +60 -0
- langroid/agent/tools/sciphi_search_rag_tool.py +79 -0
- langroid/agent/tools/segment_extract_tool.py +36 -0
- langroid/cachedb/__init__.py +9 -0
- langroid/cachedb/base.py +22 -2
- langroid/cachedb/momento_cachedb.py +26 -2
- langroid/cachedb/redis_cachedb.py +78 -11
- langroid/embedding_models/__init__.py +34 -0
- langroid/embedding_models/base.py +21 -2
- langroid/embedding_models/models.py +120 -18
- 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/language_models/__init__.py +45 -0
- langroid/language_models/azure_openai.py +80 -27
- langroid/language_models/base.py +117 -12
- langroid/language_models/config.py +5 -0
- langroid/language_models/openai_assistants.py +3 -0
- langroid/language_models/openai_gpt.py +558 -174
- langroid/language_models/prompt_formatter/__init__.py +15 -0
- langroid/language_models/prompt_formatter/base.py +4 -6
- langroid/language_models/prompt_formatter/hf_formatter.py +135 -0
- langroid/language_models/utils.py +18 -21
- langroid/mytypes.py +25 -8
- langroid/parsing/__init__.py +46 -0
- langroid/parsing/document_parser.py +260 -63
- langroid/parsing/image_text.py +32 -0
- langroid/parsing/parse_json.py +143 -0
- langroid/parsing/parser.py +122 -59
- langroid/parsing/repo_loader.py +114 -52
- langroid/parsing/search.py +68 -63
- langroid/parsing/spider.py +3 -2
- langroid/parsing/table_loader.py +44 -0
- langroid/parsing/url_loader.py +59 -11
- langroid/parsing/urls.py +85 -37
- langroid/parsing/utils.py +298 -4
- langroid/parsing/web_search.py +73 -0
- langroid/prompts/__init__.py +11 -0
- langroid/prompts/chat-gpt4-system-prompt.md +68 -0
- langroid/prompts/prompts_config.py +1 -1
- langroid/utils/__init__.py +17 -0
- langroid/utils/algorithms/__init__.py +3 -0
- langroid/utils/algorithms/graph.py +103 -0
- langroid/utils/configuration.py +36 -5
- langroid/utils/constants.py +4 -0
- langroid/utils/globals.py +2 -2
- langroid/utils/logging.py +2 -5
- langroid/utils/output/__init__.py +21 -0
- langroid/utils/output/printing.py +47 -1
- langroid/utils/output/status.py +33 -0
- langroid/utils/pandas_utils.py +30 -0
- langroid/utils/pydantic_utils.py +616 -2
- langroid/utils/system.py +98 -0
- langroid/vector_store/__init__.py +40 -0
- langroid/vector_store/base.py +203 -6
- langroid/vector_store/chromadb.py +59 -32
- langroid/vector_store/lancedb.py +463 -0
- langroid/vector_store/meilisearch.py +10 -7
- langroid/vector_store/momento.py +262 -0
- langroid/vector_store/qdrantdb.py +104 -22
- {langroid-0.1.85.dist-info → langroid-0.1.219.dist-info}/METADATA +329 -149
- langroid-0.1.219.dist-info/RECORD +127 -0
- {langroid-0.1.85.dist-info → langroid-0.1.219.dist-info}/WHEEL +1 -1
- langroid/agent/special/recipient_validator_agent.py +0 -157
- langroid/parsing/json.py +0 -64
- langroid/utils/web/selenium_login.py +0 -36
- langroid-0.1.85.dist-info/RECORD +0 -94
- /langroid/{scripts → agent/callbacks}/__init__.py +0 -0
- {langroid-0.1.85.dist-info → langroid-0.1.219.dist-info}/LICENSE +0 -0
langroid/agent/chat_agent.py
CHANGED
@@ -1,14 +1,15 @@
|
|
1
|
+
import copy
|
1
2
|
import inspect
|
2
|
-
import json
|
3
3
|
import logging
|
4
4
|
import textwrap
|
5
5
|
from contextlib import ExitStack
|
6
|
-
from typing import Dict, List, Optional, Set, Tuple, Type, cast
|
6
|
+
from typing import Dict, List, Optional, Set, Tuple, Type, cast
|
7
7
|
|
8
8
|
from rich import print
|
9
9
|
from rich.console import Console
|
10
|
+
from rich.markup import escape
|
10
11
|
|
11
|
-
from langroid.agent.base import Agent, AgentConfig
|
12
|
+
from langroid.agent.base import Agent, AgentConfig, noop_fn
|
12
13
|
from langroid.agent.chat_document import ChatDocument
|
13
14
|
from langroid.agent.tool_message import ToolMessage
|
14
15
|
from langroid.language_models.base import (
|
@@ -17,7 +18,9 @@ from langroid.language_models.base import (
|
|
17
18
|
Role,
|
18
19
|
StreamingIfAllowed,
|
19
20
|
)
|
21
|
+
from langroid.language_models.openai_gpt import OpenAIGPT
|
20
22
|
from langroid.utils.configuration import settings
|
23
|
+
from langroid.utils.output import status
|
21
24
|
|
22
25
|
console = Console()
|
23
26
|
|
@@ -40,8 +43,36 @@ class ChatAgentConfig(AgentConfig):
|
|
40
43
|
|
41
44
|
system_message: str = "You are a helpful assistant."
|
42
45
|
user_message: Optional[str] = None
|
43
|
-
use_tools: bool =
|
44
|
-
use_functions_api: bool =
|
46
|
+
use_tools: bool = False
|
47
|
+
use_functions_api: bool = True
|
48
|
+
|
49
|
+
def _set_fn_or_tools(self, fn_available: bool) -> None:
|
50
|
+
"""
|
51
|
+
Enable Langroid Tool or OpenAI-like fn-calling,
|
52
|
+
depending on config settings and availability of fn-calling.
|
53
|
+
"""
|
54
|
+
if self.use_functions_api and not fn_available:
|
55
|
+
logger.debug(
|
56
|
+
"""
|
57
|
+
You have enabled `use_functions_api` but the LLM does not support it.
|
58
|
+
So we will enable `use_tools` instead, so we can use
|
59
|
+
Langroid's ToolMessage mechanism.
|
60
|
+
"""
|
61
|
+
)
|
62
|
+
self.use_functions_api = False
|
63
|
+
self.use_tools = True
|
64
|
+
|
65
|
+
if not self.use_functions_api or not self.use_tools:
|
66
|
+
return
|
67
|
+
if self.use_functions_api and self.use_tools:
|
68
|
+
logger.debug(
|
69
|
+
"""
|
70
|
+
You have enabled both `use_tools` and `use_functions_api`.
|
71
|
+
Turning off `use_tools`, since the LLM supports function-calling.
|
72
|
+
"""
|
73
|
+
)
|
74
|
+
self.use_tools = False
|
75
|
+
self.use_functions_api = True
|
45
76
|
|
46
77
|
|
47
78
|
class ChatAgent(Agent):
|
@@ -61,7 +92,9 @@ class ChatAgent(Agent):
|
|
61
92
|
"""
|
62
93
|
|
63
94
|
def __init__(
|
64
|
-
self,
|
95
|
+
self,
|
96
|
+
config: ChatAgentConfig = ChatAgentConfig(),
|
97
|
+
task: Optional[List[LLMMessage]] = None,
|
65
98
|
):
|
66
99
|
"""
|
67
100
|
Chat-mode agent initialized with task spec as the initial message sequence
|
@@ -71,6 +104,7 @@ class ChatAgent(Agent):
|
|
71
104
|
"""
|
72
105
|
super().__init__(config)
|
73
106
|
self.config: ChatAgentConfig = config
|
107
|
+
self.config._set_fn_or_tools(self._fn_call_available())
|
74
108
|
self.message_history: List[LLMMessage] = []
|
75
109
|
self.tool_instructions_added: bool = False
|
76
110
|
# An agent's "task" is defined by a system msg and an optional user msg;
|
@@ -102,8 +136,42 @@ class ChatAgent(Agent):
|
|
102
136
|
self.llm_functions_usable: Set[str] = set()
|
103
137
|
self.llm_function_force: Optional[Dict[str, str]] = None
|
104
138
|
|
139
|
+
def clone(self, i: int = 0) -> "ChatAgent":
|
140
|
+
"""Create i'th clone of this agent, ensuring tool use/handling is cloned.
|
141
|
+
Important: We assume all member variables are in the __init__ method here
|
142
|
+
and in the Agent class.
|
143
|
+
TODO: We are attempting to close an agent after its state has been
|
144
|
+
changed in possibly many ways. Below is an imperfect solution. Caution advised.
|
145
|
+
Revisit later.
|
146
|
+
"""
|
147
|
+
agent_cls = type(self)
|
148
|
+
config_copy = copy.deepcopy(self.config)
|
149
|
+
config_copy.name = f"{config_copy.name}-{i}"
|
150
|
+
new_agent = agent_cls(config_copy)
|
151
|
+
new_agent.system_tool_instructions = self.system_tool_instructions
|
152
|
+
new_agent.system_json_tool_instructions = self.system_json_tool_instructions
|
153
|
+
new_agent.llm_tools_map = self.llm_tools_map
|
154
|
+
new_agent.llm_functions_map = self.llm_functions_map
|
155
|
+
new_agent.llm_functions_handled = self.llm_functions_handled
|
156
|
+
new_agent.llm_functions_usable = self.llm_functions_usable
|
157
|
+
new_agent.llm_function_force = self.llm_function_force
|
158
|
+
# Caution - we are copying the vector-db, maybe we don't always want this?
|
159
|
+
new_agent.vecdb = self.vecdb
|
160
|
+
return new_agent
|
161
|
+
|
162
|
+
def _fn_call_available(self) -> bool:
|
163
|
+
"""Does this agent's LLM support function calling?"""
|
164
|
+
return (
|
165
|
+
self.llm is not None
|
166
|
+
and isinstance(self.llm, OpenAIGPT)
|
167
|
+
and self.llm.is_openai_chat_model()
|
168
|
+
)
|
169
|
+
|
105
170
|
def set_system_message(self, msg: str) -> None:
|
106
171
|
self.system_message = msg
|
172
|
+
if len(self.message_history) > 0:
|
173
|
+
# if there is message history, update the system message in it
|
174
|
+
self.message_history[0].content = msg
|
107
175
|
|
108
176
|
def set_user_message(self, msg: str) -> None:
|
109
177
|
self.user_message = msg
|
@@ -160,46 +228,24 @@ class ChatAgent(Agent):
|
|
160
228
|
enabled_classes: List[Type[ToolMessage]] = list(self.llm_tools_map.values())
|
161
229
|
if len(enabled_classes) == 0:
|
162
230
|
return "You can ask questions in natural language."
|
163
|
-
|
164
231
|
json_instructions = "\n\n".join(
|
165
232
|
[
|
166
|
-
|
167
|
-
|
168
|
-
TOOL: {msg_cls.default_value("request")}
|
169
|
-
PURPOSE: {msg_cls.default_value("purpose")}
|
170
|
-
JSON FORMAT: {
|
171
|
-
json.dumps(
|
172
|
-
msg_cls.llm_function_schema(request=True).parameters,
|
173
|
-
indent=4,
|
174
|
-
)
|
175
|
-
}
|
176
|
-
{"EXAMPLE: " + msg_cls.usage_example() if msg_cls.examples() else ""}
|
177
|
-
""".lstrip()
|
178
|
-
)
|
179
|
-
for i, msg_cls in enumerate(enabled_classes)
|
233
|
+
msg_cls.json_instructions(tool=self.config.use_tools)
|
234
|
+
for _, msg_cls in enumerate(enabled_classes)
|
180
235
|
if msg_cls.default_value("request") in self.llm_tools_usable
|
181
236
|
]
|
182
237
|
)
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
The JSON format will be:
|
195
|
-
\\{
|
196
|
-
"request": "<tool_name>",
|
197
|
-
"<arg1>": <value1>,
|
198
|
-
"<arg2>": <value2>,
|
199
|
-
...
|
200
|
-
\\}
|
201
|
-
----------------------------
|
202
|
-
""".lstrip()
|
238
|
+
# if any of the enabled classes has json_group_instructions, then use that,
|
239
|
+
# else fall back to ToolMessage.json_group_instructions
|
240
|
+
for msg_cls in enabled_classes:
|
241
|
+
if hasattr(msg_cls, "json_group_instructions") and callable(
|
242
|
+
getattr(msg_cls, "json_group_instructions")
|
243
|
+
):
|
244
|
+
return msg_cls.json_group_instructions().format(
|
245
|
+
json_instructions=json_instructions
|
246
|
+
)
|
247
|
+
return ToolMessage.json_group_instructions().format(
|
248
|
+
json_instructions=json_instructions
|
203
249
|
)
|
204
250
|
|
205
251
|
def tool_instructions(self) -> str:
|
@@ -260,13 +306,20 @@ class ChatAgent(Agent):
|
|
260
306
|
"""
|
261
307
|
self.system_message += "\n\n" + message
|
262
308
|
|
309
|
+
def last_message_with_role(self, role: Role) -> LLMMessage | None:
|
310
|
+
"""from `message_history`, return the last message with role `role`"""
|
311
|
+
for i in range(len(self.message_history) - 1, -1, -1):
|
312
|
+
if self.message_history[i].role == role:
|
313
|
+
return self.message_history[i]
|
314
|
+
return None
|
315
|
+
|
263
316
|
def update_last_message(self, message: str, role: str = Role.USER) -> None:
|
264
317
|
"""
|
265
318
|
Update the last message that has role `role` in the message history.
|
266
319
|
Useful when we want to replace a long user prompt, that may contain context
|
267
320
|
documents plus a question, with just the question.
|
268
321
|
Args:
|
269
|
-
message (str):
|
322
|
+
message (str): new message to replace with
|
270
323
|
role (str): role of message to replace
|
271
324
|
"""
|
272
325
|
if len(self.message_history) == 0:
|
@@ -302,7 +355,8 @@ class ChatAgent(Agent):
|
|
302
355
|
|
303
356
|
""".lstrip()
|
304
357
|
)
|
305
|
-
|
358
|
+
# remove leading and trailing newlines and other whitespace
|
359
|
+
return LLMMessage(role=Role.SYSTEM, content=content.strip())
|
306
360
|
|
307
361
|
def enable_message(
|
308
362
|
self,
|
@@ -311,6 +365,7 @@ class ChatAgent(Agent):
|
|
311
365
|
handle: bool = True,
|
312
366
|
force: bool = False,
|
313
367
|
require_recipient: bool = False,
|
368
|
+
include_defaults: bool = True,
|
314
369
|
) -> None:
|
315
370
|
"""
|
316
371
|
Add the tool (message class) to the agent, and enable either
|
@@ -331,7 +386,11 @@ class ChatAgent(Agent):
|
|
331
386
|
`force` is ignored if `message_class` is None.
|
332
387
|
require_recipient: whether to require that recipient be specified
|
333
388
|
when using the tool message (only applies if `use` is True).
|
334
|
-
|
389
|
+
require_defaults: whether to include fields that have default values,
|
390
|
+
in the "properties" section of the JSON format instructions.
|
391
|
+
(Normally the OpenAI completion API ignores these fields,
|
392
|
+
but the Assistant fn-calling seems to pay attn to these,
|
393
|
+
and if we don't want this, we should set this to False.)
|
335
394
|
"""
|
336
395
|
super().enable_message_handling(message_class) # enables handling only
|
337
396
|
tools = self._get_tool_list(message_class)
|
@@ -339,7 +398,7 @@ class ChatAgent(Agent):
|
|
339
398
|
if require_recipient:
|
340
399
|
message_class = message_class.require_recipient()
|
341
400
|
request = message_class.default_value("request")
|
342
|
-
llm_function = message_class.llm_function_schema()
|
401
|
+
llm_function = message_class.llm_function_schema(defaults=include_defaults)
|
343
402
|
self.llm_functions_map[request] = llm_function
|
344
403
|
if force:
|
345
404
|
self.llm_function_force = dict(name=request)
|
@@ -403,12 +462,11 @@ class ChatAgent(Agent):
|
|
403
462
|
message_class: The only ToolMessage class to allow
|
404
463
|
"""
|
405
464
|
request = message_class.__fields__["request"].default
|
406
|
-
for r in self.
|
407
|
-
|
408
|
-
|
409
|
-
|
465
|
+
to_remove = [r for r in self.llm_tools_usable if r != request]
|
466
|
+
for r in to_remove:
|
467
|
+
self.llm_tools_usable.discard(r)
|
468
|
+
self.llm_functions_usable.discard(r)
|
410
469
|
|
411
|
-
@no_type_check
|
412
470
|
def llm_response(
|
413
471
|
self, message: Optional[str | ChatDocument] = None
|
414
472
|
) -> Optional[ChatDocument]:
|
@@ -421,32 +479,51 @@ class ChatAgent(Agent):
|
|
421
479
|
Returns:
|
422
480
|
LLM response as a ChatDocument object
|
423
481
|
"""
|
482
|
+
if self.llm is None:
|
483
|
+
return None
|
424
484
|
hist, output_len = self._prep_llm_messages(message)
|
485
|
+
if len(hist) == 0:
|
486
|
+
return None
|
425
487
|
with StreamingIfAllowed(self.llm, self.llm.get_stream()):
|
426
488
|
response = self.llm_response_messages(hist, output_len)
|
427
489
|
# TODO - when response contains function_call we should include
|
428
490
|
# that (and related fields) in the message_history
|
429
491
|
self.message_history.append(ChatDocument.to_LLMMessage(response))
|
492
|
+
# Preserve trail of tool_ids for OpenAI Assistant fn-calls
|
493
|
+
response.metadata.tool_ids = (
|
494
|
+
[]
|
495
|
+
if isinstance(message, str)
|
496
|
+
else message.metadata.tool_ids if message is not None else []
|
497
|
+
)
|
430
498
|
return response
|
431
499
|
|
432
|
-
@no_type_check
|
433
500
|
async def llm_response_async(
|
434
501
|
self, message: Optional[str | ChatDocument] = None
|
435
502
|
) -> Optional[ChatDocument]:
|
436
503
|
"""
|
437
504
|
Async version of `llm_response`. See there for details.
|
438
505
|
"""
|
506
|
+
if self.llm is None:
|
507
|
+
return None
|
508
|
+
|
439
509
|
hist, output_len = self._prep_llm_messages(message)
|
440
510
|
with StreamingIfAllowed(self.llm, self.llm.get_stream()):
|
441
511
|
response = await self.llm_response_messages_async(hist, output_len)
|
442
512
|
# TODO - when response contains function_call we should include
|
443
513
|
# that (and related fields) in the message_history
|
444
514
|
self.message_history.append(ChatDocument.to_LLMMessage(response))
|
515
|
+
# Preserve trail of tool_ids for OpenAI Assistant fn-calls
|
516
|
+
response.metadata.tool_ids = (
|
517
|
+
[]
|
518
|
+
if isinstance(message, str)
|
519
|
+
else message.metadata.tool_ids if message is not None else []
|
520
|
+
)
|
445
521
|
return response
|
446
522
|
|
447
|
-
@no_type_check
|
448
523
|
def _prep_llm_messages(
|
449
|
-
self,
|
524
|
+
self,
|
525
|
+
message: Optional[str | ChatDocument] = None,
|
526
|
+
truncate: bool = True,
|
450
527
|
) -> Tuple[List[LLMMessage], int]:
|
451
528
|
"""
|
452
529
|
Prepare messages to be sent to self.llm_response_messages,
|
@@ -458,12 +535,21 @@ class ChatAgent(Agent):
|
|
458
535
|
output_len = max expected number of tokens in response
|
459
536
|
"""
|
460
537
|
|
461
|
-
if
|
462
|
-
|
538
|
+
if (
|
539
|
+
not self.llm_can_respond(message)
|
540
|
+
or self.config.llm is None
|
541
|
+
or self.llm is None
|
542
|
+
):
|
543
|
+
return [], 0
|
463
544
|
|
464
|
-
|
465
|
-
|
466
|
-
|
545
|
+
if message is None and len(self.message_history) > 0:
|
546
|
+
# this means agent has been used to get LLM response already,
|
547
|
+
# and so the last message is an "assistant" response.
|
548
|
+
# We delete this last assistant response and re-generate it.
|
549
|
+
self.clear_history(-1)
|
550
|
+
logger.warning(
|
551
|
+
"Re-generating the last assistant response since message is None"
|
552
|
+
)
|
467
553
|
|
468
554
|
if len(self.message_history) == 0:
|
469
555
|
# initial messages have not yet been loaded, so load them
|
@@ -477,8 +563,9 @@ class ChatAgent(Agent):
|
|
477
563
|
if settings.debug:
|
478
564
|
print(
|
479
565
|
f"""
|
480
|
-
[
|
481
|
-
{self.message_history_str()}
|
566
|
+
[grey37]LLM Initial Msg History:
|
567
|
+
{escape(self.message_history_str())}
|
568
|
+
[/grey37]
|
482
569
|
"""
|
483
570
|
)
|
484
571
|
else:
|
@@ -493,7 +580,8 @@ class ChatAgent(Agent):
|
|
493
580
|
hist = self.message_history
|
494
581
|
output_len = self.config.llm.max_output_tokens
|
495
582
|
if (
|
496
|
-
|
583
|
+
truncate
|
584
|
+
and self.chat_num_tokens(hist)
|
497
585
|
> self.llm.chat_context_length() - self.config.llm.max_output_tokens
|
498
586
|
):
|
499
587
|
# chat + output > max context length,
|
@@ -517,7 +605,10 @@ class ChatAgent(Agent):
|
|
517
605
|
raise ValueError(
|
518
606
|
"""
|
519
607
|
The message history is longer than the max chat context
|
520
|
-
length allowed, and we have run out of messages to drop.
|
608
|
+
length allowed, and we have run out of messages to drop.
|
609
|
+
HINT: In your `OpenAIGPTConfig` object, try increasing
|
610
|
+
`chat_context_length` or decreasing `max_output_tokens`.
|
611
|
+
"""
|
521
612
|
)
|
522
613
|
# drop the second message, i.e. first msg after the sys msg
|
523
614
|
# (typically user msg).
|
@@ -559,6 +650,18 @@ class ChatAgent(Agent):
|
|
559
650
|
)
|
560
651
|
return hist, output_len
|
561
652
|
|
653
|
+
def _function_args(
|
654
|
+
self,
|
655
|
+
) -> Tuple[Optional[List[LLMFunctionSpec]], str | Dict[str, str]]:
|
656
|
+
functions: Optional[List[LLMFunctionSpec]] = None
|
657
|
+
fun_call: str | Dict[str, str] = "none"
|
658
|
+
if self.config.use_functions_api and len(self.llm_functions_usable) > 0:
|
659
|
+
functions = [self.llm_functions_map[f] for f in self.llm_functions_usable]
|
660
|
+
fun_call = (
|
661
|
+
"auto" if self.llm_function_force is None else self.llm_function_force
|
662
|
+
)
|
663
|
+
return functions, fun_call
|
664
|
+
|
562
665
|
def llm_response_messages(
|
563
666
|
self, messages: List[LLMMessage], output_len: Optional[int] = None
|
564
667
|
) -> ChatDocument:
|
@@ -573,24 +676,21 @@ class ChatAgent(Agent):
|
|
573
676
|
"""
|
574
677
|
assert self.config.llm is not None and self.llm is not None
|
575
678
|
output_len = output_len or self.config.llm.max_output_tokens
|
679
|
+
streamer = noop_fn
|
680
|
+
if self.llm.get_stream():
|
681
|
+
streamer = self.callbacks.start_llm_stream()
|
682
|
+
self.llm.config.streamer = streamer
|
576
683
|
with ExitStack() as stack: # for conditionally using rich spinner
|
577
684
|
if not self.llm.get_stream():
|
578
685
|
# show rich spinner only if not streaming!
|
579
|
-
cm =
|
686
|
+
cm = status(
|
687
|
+
"LLM responding to messages...",
|
688
|
+
log_if_quiet=False,
|
689
|
+
)
|
580
690
|
stack.enter_context(cm)
|
581
|
-
if self.llm.get_stream():
|
691
|
+
if self.llm.get_stream() and not settings.quiet:
|
582
692
|
console.print(f"[green]{self.indent}", end="")
|
583
|
-
functions
|
584
|
-
fun_call: str | Dict[str, str] = "none"
|
585
|
-
if self.config.use_functions_api and len(self.llm_functions_usable) > 0:
|
586
|
-
functions = [
|
587
|
-
self.llm_functions_map[f] for f in self.llm_functions_usable
|
588
|
-
]
|
589
|
-
fun_call = (
|
590
|
-
"auto"
|
591
|
-
if self.llm_function_force is None
|
592
|
-
else self.llm_function_force
|
593
|
-
)
|
693
|
+
functions, fun_call = self._function_args()
|
594
694
|
assert self.llm is not None
|
595
695
|
response = self.llm.chat(
|
596
696
|
messages,
|
@@ -598,22 +698,39 @@ class ChatAgent(Agent):
|
|
598
698
|
functions=functions,
|
599
699
|
function_call=fun_call,
|
600
700
|
)
|
601
|
-
|
701
|
+
if self.llm.get_stream():
|
702
|
+
self.callbacks.finish_llm_stream(
|
703
|
+
content=str(response),
|
704
|
+
is_tool=self.has_tool_message_attempt(
|
705
|
+
ChatDocument.from_LLMResponse(response, displayed=True)
|
706
|
+
),
|
707
|
+
)
|
708
|
+
self.llm.config.streamer = noop_fn
|
709
|
+
if response.cached:
|
710
|
+
self.callbacks.cancel_llm_stream()
|
711
|
+
|
602
712
|
if not self.llm.get_stream() or response.cached:
|
603
|
-
displayed
|
713
|
+
# We would have already displayed the msg "live" ONLY if
|
714
|
+
# streaming was enabled, AND we did not find a cached response.
|
715
|
+
# If we are here, it means the response has not yet been displayed.
|
604
716
|
cached = f"[red]{self.indent}(cached)[/red]" if response.cached else ""
|
605
|
-
if
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
717
|
+
if not settings.quiet:
|
718
|
+
print(cached + "[green]" + escape(str(response)))
|
719
|
+
self.callbacks.show_llm_response(
|
720
|
+
content=str(response),
|
721
|
+
is_tool=self.has_tool_message_attempt(
|
722
|
+
ChatDocument.from_LLMResponse(response, displayed=True)
|
723
|
+
),
|
724
|
+
cached=response.cached,
|
725
|
+
)
|
610
726
|
self.update_token_usage(
|
611
727
|
response,
|
612
728
|
messages,
|
613
729
|
self.llm.get_stream(),
|
614
|
-
|
730
|
+
chat=True,
|
731
|
+
print_response_stats=self.config.show_stats and not settings.quiet,
|
615
732
|
)
|
616
|
-
return ChatDocument.from_LLMResponse(response, displayed)
|
733
|
+
return ChatDocument.from_LLMResponse(response, displayed=True)
|
617
734
|
|
618
735
|
async def llm_response_messages_async(
|
619
736
|
self, messages: List[LLMMessage], output_len: Optional[int] = None
|
@@ -631,26 +748,51 @@ class ChatAgent(Agent):
|
|
631
748
|
"auto" if self.llm_function_force is None else self.llm_function_force
|
632
749
|
)
|
633
750
|
assert self.llm is not None
|
751
|
+
|
752
|
+
streamer = noop_fn
|
753
|
+
if self.llm.get_stream():
|
754
|
+
streamer = self.callbacks.start_llm_stream()
|
755
|
+
self.llm.config.streamer = streamer
|
756
|
+
|
634
757
|
response = await self.llm.achat(
|
635
758
|
messages,
|
636
759
|
output_len,
|
637
760
|
functions=functions,
|
638
761
|
function_call=fun_call,
|
639
762
|
)
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
763
|
+
if self.llm.get_stream():
|
764
|
+
self.callbacks.finish_llm_stream(
|
765
|
+
content=str(response),
|
766
|
+
is_tool=self.has_tool_message_attempt(
|
767
|
+
ChatDocument.from_LLMResponse(response, displayed=True)
|
768
|
+
),
|
769
|
+
)
|
770
|
+
self.llm.config.streamer = noop_fn
|
771
|
+
if response.cached:
|
772
|
+
self.callbacks.cancel_llm_stream()
|
773
|
+
if not self.llm.get_stream() or response.cached:
|
774
|
+
# We would have already displayed the msg "live" ONLY if
|
775
|
+
# streaming was enabled, AND we did not find a cached response.
|
776
|
+
# If we are here, it means the response has not yet been displayed.
|
777
|
+
cached = f"[red]{self.indent}(cached)[/red]" if response.cached else ""
|
778
|
+
if not settings.quiet:
|
779
|
+
print(cached + "[green]" + escape(str(response)))
|
780
|
+
self.callbacks.show_llm_response(
|
781
|
+
content=str(response),
|
782
|
+
is_tool=self.has_tool_message_attempt(
|
783
|
+
ChatDocument.from_LLMResponse(response, displayed=True)
|
784
|
+
),
|
785
|
+
cached=response.cached,
|
786
|
+
)
|
787
|
+
|
647
788
|
self.update_token_usage(
|
648
789
|
response,
|
649
790
|
messages,
|
650
791
|
self.llm.get_stream(),
|
651
|
-
|
792
|
+
chat=True,
|
793
|
+
print_response_stats=self.config.show_stats and not settings.quiet,
|
652
794
|
)
|
653
|
-
return ChatDocument.from_LLMResponse(response, displayed)
|
795
|
+
return ChatDocument.from_LLMResponse(response, displayed=True)
|
654
796
|
|
655
797
|
def _llm_response_temp_context(self, message: str, prompt: str) -> ChatDocument:
|
656
798
|
"""
|
@@ -703,12 +845,14 @@ class ChatAgent(Agent):
|
|
703
845
|
"""
|
704
846
|
# explicitly call THIS class's respond method,
|
705
847
|
# not a derived class's (or else there would be infinite recursion!)
|
848
|
+
n_msgs = len(self.message_history)
|
706
849
|
with StreamingIfAllowed(self.llm, self.llm.get_stream()): # type: ignore
|
707
850
|
response = cast(ChatDocument, ChatAgent.llm_response(self, message))
|
708
|
-
#
|
709
|
-
# user message and the
|
710
|
-
|
711
|
-
self.message_history.pop()
|
851
|
+
# If there is a response, then we will have two additional
|
852
|
+
# messages in the message history, i.e. the user message and the
|
853
|
+
# assistant response. We want to (carefully) remove these two messages.
|
854
|
+
self.message_history.pop() if len(self.message_history) > n_msgs else None
|
855
|
+
self.message_history.pop() if len(self.message_history) > n_msgs else None
|
712
856
|
return response
|
713
857
|
|
714
858
|
async def llm_response_forget_async(self, message: str) -> ChatDocument:
|
@@ -717,14 +861,16 @@ class ChatAgent(Agent):
|
|
717
861
|
"""
|
718
862
|
# explicitly call THIS class's respond method,
|
719
863
|
# not a derived class's (or else there would be infinite recursion!)
|
864
|
+
n_msgs = len(self.message_history)
|
720
865
|
with StreamingIfAllowed(self.llm, self.llm.get_stream()): # type: ignore
|
721
866
|
response = cast(
|
722
867
|
ChatDocument, await ChatAgent.llm_response_async(self, message)
|
723
868
|
)
|
724
|
-
#
|
725
|
-
# user message and the
|
726
|
-
|
727
|
-
self.message_history.pop()
|
869
|
+
# If there is a response, then we will have two additional
|
870
|
+
# messages in the message history, i.e. the user message and the
|
871
|
+
# assistant response. We want to (carefully) remove these two messages.
|
872
|
+
self.message_history.pop() if len(self.message_history) > n_msgs else None
|
873
|
+
self.message_history.pop() if len(self.message_history) > n_msgs else None
|
728
874
|
return response
|
729
875
|
|
730
876
|
def chat_num_tokens(self, messages: Optional[List[LLMMessage]] = None) -> int:
|