langroid 0.24.1__tar.gz → 0.26.0__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.24.1 → langroid-0.26.0}/PKG-INFO +2 -2
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/base.py +339 -125
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/callbacks/chainlit.py +57 -129
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/chat_agent.py +19 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/tools/orchestration.py +8 -3
- {langroid-0.24.1 → langroid-0.26.0}/pyproject.toml +3 -3
- {langroid-0.24.1 → langroid-0.26.0}/LICENSE +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/README.md +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/__init__.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/__init__.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/batch.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/callbacks/__init__.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/chat_document.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/helpers.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/junk +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/openai_assistant.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/special/__init__.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/special/arangodb/__init__.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/special/arangodb/arangodb_agent.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/special/arangodb/system_messages.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/special/arangodb/tools.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/special/arangodb/utils.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/special/doc_chat_agent.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/special/lance_doc_chat_agent.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/special/lance_rag/__init__.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/special/lance_rag/critic_agent.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/special/lance_rag/lance_rag_task.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/special/lance_rag/query_planner_agent.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/special/lance_tools.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/special/neo4j/__init__.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/special/neo4j/csv_kg_chat.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/special/neo4j/neo4j_chat_agent.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/special/neo4j/system_messages.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/special/neo4j/tools.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/special/relevance_extractor_agent.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/special/retriever_agent.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/special/sql/__init__.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/special/sql/sql_chat_agent.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/special/sql/utils/__init__.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/special/sql/utils/description_extractors.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/special/sql/utils/populate_metadata.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/special/sql/utils/system_message.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/special/sql/utils/tools.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/special/table_chat_agent.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/structured_message.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/task.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/tool_message.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/tools/__init__.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/tools/duckduckgo_search_tool.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/tools/file_tools.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/tools/google_search_tool.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/tools/metaphor_search_tool.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/tools/recipient_tool.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/tools/retrieval_tool.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/tools/rewind_tool.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/tools/segment_extract_tool.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/typed_task.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent/xml_tool_message.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/agent_config.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/cachedb/__init__.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/cachedb/base.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/cachedb/momento_cachedb.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/cachedb/redis_cachedb.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/embedding_models/__init__.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/embedding_models/base.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/embedding_models/clustering.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/embedding_models/models.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/embedding_models/protoc/__init__.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/embedding_models/protoc/embeddings.proto +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/embedding_models/protoc/embeddings_pb2.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/embedding_models/protoc/embeddings_pb2.pyi +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/embedding_models/protoc/embeddings_pb2_grpc.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/embedding_models/remote_embeds.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/exceptions.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/language_models/.chainlit/config.toml +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/language_models/.chainlit/translations/en-US.json +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/language_models/__init__.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/language_models/azure_openai.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/language_models/base.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/language_models/config.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/language_models/mock_lm.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/language_models/openai_gpt.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/language_models/prompt_formatter/__init__.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/language_models/prompt_formatter/base.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/language_models/prompt_formatter/hf_formatter.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/language_models/prompt_formatter/llama2_formatter.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/language_models/utils.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/mytypes.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/parsing/__init__.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/parsing/agent_chats.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/parsing/code-parsing.md +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/parsing/code_parser.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/parsing/config.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/parsing/document_parser.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/parsing/image_text.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/parsing/para_sentence_split.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/parsing/parse_json.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/parsing/parser.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/parsing/repo_loader.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/parsing/routing.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/parsing/search.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/parsing/spider.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/parsing/table_loader.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/parsing/url_loader.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/parsing/url_loader_cookies.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/parsing/urls.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/parsing/utils.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/parsing/web_search.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/prompts/__init__.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/prompts/chat-gpt4-system-prompt.md +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/prompts/dialog.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/prompts/prompts_config.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/prompts/templates.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/py.typed +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/pydantic_v1/__init__.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/pydantic_v1/main.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/utils/.chainlit/config.toml +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/utils/.chainlit/translations/en-US.json +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/utils/__init__.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/utils/algorithms/__init__.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/utils/algorithms/graph.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/utils/configuration.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/utils/constants.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/utils/docker.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/utils/git_utils.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/utils/globals.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/utils/llms/__init__.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/utils/llms/strings.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/utils/logging.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/utils/object_registry.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/utils/output/__init__.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/utils/output/citations.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/utils/output/printing.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/utils/output/status.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/utils/pandas_utils.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/utils/pydantic_utils.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/utils/system.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/utils/types.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/utils/web/__init__.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/utils/web/login.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/vector_store/__init__.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/vector_store/base.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/vector_store/chromadb.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/vector_store/lancedb.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/vector_store/meilisearch.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/vector_store/momento.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/vector_store/qdrant_cloud.py +0 -0
- {langroid-0.24.1 → langroid-0.26.0}/langroid/vector_store/qdrantdb.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: langroid
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.26.0
|
4
4
|
Summary: Harness LLMs with Multi-Agent Programming
|
5
5
|
License: MIT
|
6
6
|
Author: Prasad Chalasani
|
@@ -42,7 +42,7 @@ Requires-Dist: arango-datasets (>=1.2.2,<2.0.0) ; extra == "all" or extra == "ar
|
|
42
42
|
Requires-Dist: async-generator (>=1.10,<2.0)
|
43
43
|
Requires-Dist: bs4 (>=0.0.1,<0.0.2)
|
44
44
|
Requires-Dist: cerebras-cloud-sdk (>=1.1.0,<2.0.0)
|
45
|
-
Requires-Dist: chainlit (
|
45
|
+
Requires-Dist: chainlit (>=1.3.2,<2.0.0) ; extra == "all" or extra == "chainlit"
|
46
46
|
Requires-Dist: chromadb (>=0.4.21,<=0.4.23) ; extra == "vecdbs" or extra == "all" or extra == "chromadb"
|
47
47
|
Requires-Dist: colorlog (>=6.7.0,<7.0.0)
|
48
48
|
Requires-Dist: docstring-parser (>=0.15,<0.16)
|
@@ -175,6 +175,7 @@ class Agent(ABC):
|
|
175
175
|
show_llm_response=noop_fn,
|
176
176
|
show_agent_response=noop_fn,
|
177
177
|
get_user_response=None,
|
178
|
+
get_user_response_async=None,
|
178
179
|
get_last_step=noop_fn,
|
179
180
|
set_parent_agent=noop_fn,
|
180
181
|
show_error_message=noop_fn,
|
@@ -322,6 +323,52 @@ class Agent(ABC):
|
|
322
323
|
lambda msg: message_class.handle_message_fallback(self, msg),
|
323
324
|
)
|
324
325
|
|
326
|
+
async_tool_name = f"{tool}_async"
|
327
|
+
if (
|
328
|
+
hasattr(message_class, "handle_async")
|
329
|
+
and inspect.isfunction(message_class.handle_async)
|
330
|
+
and not hasattr(self, async_tool_name)
|
331
|
+
):
|
332
|
+
has_chat_doc_arg = (
|
333
|
+
len(inspect.signature(message_class.handle_async).parameters) > 1
|
334
|
+
)
|
335
|
+
|
336
|
+
if has_chat_doc_arg:
|
337
|
+
|
338
|
+
@no_type_check
|
339
|
+
async def handler(obj, chat_doc):
|
340
|
+
return await obj.handle_async(chat_doc)
|
341
|
+
|
342
|
+
else:
|
343
|
+
|
344
|
+
@no_type_check
|
345
|
+
async def handler(obj):
|
346
|
+
return await obj.handle_async()
|
347
|
+
|
348
|
+
setattr(self, async_tool_name, handler)
|
349
|
+
elif (
|
350
|
+
hasattr(message_class, "response_async")
|
351
|
+
and inspect.isfunction(message_class.response_async)
|
352
|
+
and not hasattr(self, async_tool_name)
|
353
|
+
):
|
354
|
+
has_chat_doc_arg = (
|
355
|
+
len(inspect.signature(message_class.response_async).parameters) > 2
|
356
|
+
)
|
357
|
+
|
358
|
+
if has_chat_doc_arg:
|
359
|
+
|
360
|
+
@no_type_check
|
361
|
+
async def handler(obj, chat_doc):
|
362
|
+
return await obj.response_async(self, chat_doc)
|
363
|
+
|
364
|
+
else:
|
365
|
+
|
366
|
+
@no_type_check
|
367
|
+
async def handler(obj):
|
368
|
+
return await obj.response_async(self)
|
369
|
+
|
370
|
+
setattr(self, async_tool_name, handler)
|
371
|
+
|
325
372
|
return [tool]
|
326
373
|
|
327
374
|
def enable_message_handling(
|
@@ -393,32 +440,14 @@ class Agent(ABC):
|
|
393
440
|
recipient=recipient,
|
394
441
|
)
|
395
442
|
|
396
|
-
|
443
|
+
def _agent_response_final(
|
397
444
|
self,
|
398
|
-
msg: Optional[str | ChatDocument]
|
399
|
-
|
400
|
-
return self.agent_response(msg)
|
401
|
-
|
402
|
-
def agent_response(
|
403
|
-
self,
|
404
|
-
msg: Optional[str | ChatDocument] = None,
|
445
|
+
msg: Optional[str | ChatDocument],
|
446
|
+
results: Optional[str | OrderedDict[str, str] | ChatDocument],
|
405
447
|
) -> Optional[ChatDocument]:
|
406
448
|
"""
|
407
|
-
|
408
|
-
used to handle LLM's "tool message" or `function_call`
|
409
|
-
(e.g. OpenAI `function_call`).
|
410
|
-
Args:
|
411
|
-
msg (str|ChatDocument): the input to respond to: if msg is a string,
|
412
|
-
and it contains a valid JSON-structured "tool message", or
|
413
|
-
if msg is a ChatDocument, and it contains a `function_call`.
|
414
|
-
Returns:
|
415
|
-
Optional[ChatDocument]: the response, packaged as a ChatDocument
|
416
|
-
|
449
|
+
Convert results to final response.
|
417
450
|
"""
|
418
|
-
if msg is None:
|
419
|
-
return None
|
420
|
-
|
421
|
-
results = self.handle_message(msg)
|
422
451
|
if results is None:
|
423
452
|
return None
|
424
453
|
if not settings.quiet:
|
@@ -438,7 +467,7 @@ class Agent(ABC):
|
|
438
467
|
if isinstance(results, ChatDocument):
|
439
468
|
# Preserve trail of tool_ids for OpenAI Assistant fn-calls
|
440
469
|
results.metadata.tool_ids = (
|
441
|
-
[] if isinstance(msg, str) else msg.metadata.tool_ids
|
470
|
+
[] if msg is None or isinstance(msg, str) else msg.metadata.tool_ids
|
442
471
|
)
|
443
472
|
return results
|
444
473
|
sender_name = self.config.name
|
@@ -461,10 +490,49 @@ class Agent(ABC):
|
|
461
490
|
sender_name=sender_name,
|
462
491
|
oai_tool_id=oai_tool_id,
|
463
492
|
# preserve trail of tool_ids for OpenAI Assistant fn-calls
|
464
|
-
tool_ids=
|
493
|
+
tool_ids=(
|
494
|
+
[] if msg is None or isinstance(msg, str) else msg.metadata.tool_ids
|
495
|
+
),
|
465
496
|
),
|
466
497
|
)
|
467
498
|
|
499
|
+
async def agent_response_async(
|
500
|
+
self,
|
501
|
+
msg: Optional[str | ChatDocument] = None,
|
502
|
+
) -> Optional[ChatDocument]:
|
503
|
+
"""
|
504
|
+
Asynch version of `agent_response`. See there for details.
|
505
|
+
"""
|
506
|
+
if msg is None:
|
507
|
+
return None
|
508
|
+
|
509
|
+
results = await self.handle_message_async(msg)
|
510
|
+
|
511
|
+
return self._agent_response_final(msg, results)
|
512
|
+
|
513
|
+
def agent_response(
|
514
|
+
self,
|
515
|
+
msg: Optional[str | ChatDocument] = None,
|
516
|
+
) -> Optional[ChatDocument]:
|
517
|
+
"""
|
518
|
+
Response from the "agent itself", typically (but not only)
|
519
|
+
used to handle LLM's "tool message" or `function_call`
|
520
|
+
(e.g. OpenAI `function_call`).
|
521
|
+
Args:
|
522
|
+
msg (str|ChatDocument): the input to respond to: if msg is a string,
|
523
|
+
and it contains a valid JSON-structured "tool message", or
|
524
|
+
if msg is a ChatDocument, and it contains a `function_call`.
|
525
|
+
Returns:
|
526
|
+
Optional[ChatDocument]: the response, packaged as a ChatDocument
|
527
|
+
|
528
|
+
"""
|
529
|
+
if msg is None:
|
530
|
+
return None
|
531
|
+
|
532
|
+
results = self.handle_message(msg)
|
533
|
+
|
534
|
+
return self._agent_response_final(msg, results)
|
535
|
+
|
468
536
|
def process_tool_results(
|
469
537
|
self,
|
470
538
|
results: str,
|
@@ -625,60 +693,46 @@ class Agent(ABC):
|
|
625
693
|
recipient=recipient,
|
626
694
|
)
|
627
695
|
|
628
|
-
|
629
|
-
self,
|
630
|
-
msg: Optional[str | ChatDocument] = None,
|
631
|
-
) -> Optional[ChatDocument]:
|
632
|
-
return self.user_response(msg)
|
633
|
-
|
634
|
-
def user_response(
|
635
|
-
self,
|
636
|
-
msg: Optional[str | ChatDocument] = None,
|
637
|
-
) -> Optional[ChatDocument]:
|
696
|
+
def user_can_respond(self, msg: Optional[str | ChatDocument] = None) -> bool:
|
638
697
|
"""
|
639
|
-
|
640
|
-
with an actual answer, or quit using "q" or "x"
|
698
|
+
Whether the user can respond to a message.
|
641
699
|
|
642
700
|
Args:
|
643
701
|
msg (str|ChatDocument): the string to respond to.
|
644
702
|
|
645
703
|
Returns:
|
646
|
-
(str) User response, packaged as a ChatDocument
|
647
704
|
|
648
705
|
"""
|
649
|
-
|
650
706
|
# When msg explicitly addressed to user, this means an actual human response
|
651
707
|
# is being sought.
|
652
708
|
need_human_response = (
|
653
709
|
isinstance(msg, ChatDocument) and msg.metadata.recipient == Entity.USER
|
654
710
|
)
|
655
|
-
default_user_msg = (
|
656
|
-
(self.default_human_response or "null") if need_human_response else ""
|
657
|
-
)
|
658
711
|
|
659
712
|
if not self.interactive and not need_human_response:
|
660
|
-
return
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
713
|
+
return False
|
714
|
+
|
715
|
+
return True
|
716
|
+
|
717
|
+
def _user_response_final(
|
718
|
+
self, msg: Optional[str | ChatDocument], user_msg: str
|
719
|
+
) -> Optional[ChatDocument]:
|
720
|
+
"""
|
721
|
+
Convert user_msg to final response.
|
722
|
+
"""
|
723
|
+
if not user_msg:
|
724
|
+
need_human_response = (
|
725
|
+
isinstance(msg, ChatDocument) and msg.metadata.recipient == Entity.USER
|
726
|
+
)
|
727
|
+
user_msg = (
|
728
|
+
(self.default_human_response or "null") if need_human_response else ""
|
729
|
+
)
|
730
|
+
user_msg = user_msg.strip()
|
676
731
|
|
677
732
|
tool_ids = []
|
678
733
|
if msg is not None and isinstance(msg, ChatDocument):
|
679
734
|
tool_ids = msg.metadata.tool_ids
|
680
735
|
|
681
|
-
user_msg = user_msg.strip() or default_user_msg.strip()
|
682
736
|
# only return non-None result if user_msg not empty
|
683
737
|
if not user_msg:
|
684
738
|
return None
|
@@ -700,6 +754,72 @@ class Agent(ABC):
|
|
700
754
|
),
|
701
755
|
)
|
702
756
|
|
757
|
+
async def user_response_async(
|
758
|
+
self,
|
759
|
+
msg: Optional[str | ChatDocument] = None,
|
760
|
+
) -> Optional[ChatDocument]:
|
761
|
+
"""
|
762
|
+
Asynch version of `user_response`. See there for details.
|
763
|
+
"""
|
764
|
+
if not self.user_can_respond(msg):
|
765
|
+
return None
|
766
|
+
|
767
|
+
if self.default_human_response is not None:
|
768
|
+
user_msg = self.default_human_response
|
769
|
+
else:
|
770
|
+
if (
|
771
|
+
self.callbacks.get_user_response_async is not None
|
772
|
+
and self.callbacks.get_user_response_async is not async_noop_fn
|
773
|
+
):
|
774
|
+
user_msg = await self.callbacks.get_user_response_async(prompt="")
|
775
|
+
elif self.callbacks.get_user_response is not None:
|
776
|
+
user_msg = self.callbacks.get_user_response(prompt="")
|
777
|
+
else:
|
778
|
+
user_msg = Prompt.ask(
|
779
|
+
f"[blue]{self.indent}"
|
780
|
+
+ self.config.human_prompt
|
781
|
+
+ f"\n{self.indent}"
|
782
|
+
)
|
783
|
+
|
784
|
+
return self._user_response_final(msg, user_msg)
|
785
|
+
|
786
|
+
def user_response(
|
787
|
+
self,
|
788
|
+
msg: Optional[str | ChatDocument] = None,
|
789
|
+
) -> Optional[ChatDocument]:
|
790
|
+
"""
|
791
|
+
Get user response to current message. Could allow (human) user to intervene
|
792
|
+
with an actual answer, or quit using "q" or "x"
|
793
|
+
|
794
|
+
Args:
|
795
|
+
msg (str|ChatDocument): the string to respond to.
|
796
|
+
|
797
|
+
Returns:
|
798
|
+
(str) User response, packaged as a ChatDocument
|
799
|
+
|
800
|
+
"""
|
801
|
+
|
802
|
+
if not self.user_can_respond(msg):
|
803
|
+
return None
|
804
|
+
|
805
|
+
if self.default_human_response is not None:
|
806
|
+
user_msg = self.default_human_response
|
807
|
+
else:
|
808
|
+
if self.callbacks.get_user_response is not None:
|
809
|
+
# ask user with empty prompt: no need for prompt
|
810
|
+
# since user has seen the conversation so far.
|
811
|
+
# But non-empty prompt can be useful when Agent
|
812
|
+
# uses a tool that requires user input, or in other scenarios.
|
813
|
+
user_msg = self.callbacks.get_user_response(prompt="")
|
814
|
+
else:
|
815
|
+
user_msg = Prompt.ask(
|
816
|
+
f"[blue]{self.indent}"
|
817
|
+
+ self.config.human_prompt
|
818
|
+
+ f"\n{self.indent}"
|
819
|
+
)
|
820
|
+
|
821
|
+
return self._user_response_final(msg, user_msg)
|
822
|
+
|
703
823
|
@no_type_check
|
704
824
|
def llm_can_respond(self, message: Optional[str | ChatDocument] = None) -> bool:
|
705
825
|
"""
|
@@ -791,7 +911,7 @@ class Agent(ABC):
|
|
791
911
|
f"""
|
792
912
|
Requested output length has been shortened to {output_len}
|
793
913
|
so that the total length of Prompt + Output is less than
|
794
|
-
the completion context length of the LLM.
|
914
|
+
the completion context length of the LLM.
|
795
915
|
"""
|
796
916
|
)
|
797
917
|
|
@@ -865,7 +985,7 @@ class Agent(ABC):
|
|
865
985
|
f"""
|
866
986
|
Requested output length has been shortened to {output_len}
|
867
987
|
so that the total length of Prompt + Output is less than
|
868
|
-
the completion context length of the LLM.
|
988
|
+
the completion context length of the LLM.
|
869
989
|
"""
|
870
990
|
)
|
871
991
|
if self.llm.get_stream() and not settings.quiet:
|
@@ -1075,7 +1195,7 @@ class Agent(ABC):
|
|
1075
1195
|
if tool_name not in self.llm_tools_handled:
|
1076
1196
|
logger.warning(
|
1077
1197
|
f"""
|
1078
|
-
The function_call '{tool_name}' is not handled
|
1198
|
+
The function_call '{tool_name}' is not handled
|
1079
1199
|
by the agent named '{self.config.name}'!
|
1080
1200
|
If you intended this agent to handle this function_call,
|
1081
1201
|
either the fn-call name is incorrectly generated by the LLM,
|
@@ -1110,7 +1230,7 @@ class Agent(ABC):
|
|
1110
1230
|
if tool_name not in self.llm_tools_handled:
|
1111
1231
|
logger.warning(
|
1112
1232
|
f"""
|
1113
|
-
The tool_call '{tool_name}' is not handled
|
1233
|
+
The tool_call '{tool_name}' is not handled
|
1114
1234
|
by the agent named '{self.config.name}'!
|
1115
1235
|
If you intended this agent to handle this function_call,
|
1116
1236
|
either the fn-call name is incorrectly generated by the LLM,
|
@@ -1145,65 +1265,18 @@ class Agent(ABC):
|
|
1145
1265
|
[f"{e['loc']}: {e['msg']}" for e in ve.errors() if "loc" in e]
|
1146
1266
|
)
|
1147
1267
|
return f"""
|
1148
|
-
There were one or more errors in your attempt to use the
|
1149
|
-
TOOL or function_call named '{tool_name}':
|
1268
|
+
There were one or more errors in your attempt to use the
|
1269
|
+
TOOL or function_call named '{tool_name}':
|
1150
1270
|
{bad_field_errors}
|
1151
1271
|
Please write your message again, correcting the errors.
|
1152
1272
|
"""
|
1153
1273
|
|
1154
|
-
def
|
1155
|
-
self,
|
1156
|
-
) ->
|
1274
|
+
def _get_multiple_orch_tool_errs(
|
1275
|
+
self, tools: List[ToolMessage]
|
1276
|
+
) -> List[str | ChatDocument | None]:
|
1157
1277
|
"""
|
1158
|
-
|
1159
|
-
valid "tool" JSON substrings, or a
|
1160
|
-
ChatDocument containing a `function_call` attribute.
|
1161
|
-
Handle with the corresponding handler method, and return
|
1162
|
-
the results as a combined string.
|
1163
|
-
|
1164
|
-
Args:
|
1165
|
-
msg (str | ChatDocument): The string or ChatDocument to handle
|
1166
|
-
|
1167
|
-
Returns:
|
1168
|
-
The result of the handler method can be:
|
1169
|
-
- None if no tools successfully handled, or no tools present
|
1170
|
-
- str if langroid-native JSON tools were handled, and results concatenated,
|
1171
|
-
OR there's a SINGLE OpenAI tool-call.
|
1172
|
-
(We do this so the common scenario of a single tool/fn-call
|
1173
|
-
has a simple behavior).
|
1174
|
-
- Dict[str, str] if multiple OpenAI tool-calls were handled
|
1175
|
-
(dict is an id->result map)
|
1176
|
-
- ChatDocument if a handler returned a ChatDocument, intended to be the
|
1177
|
-
final response of the `agent_response` method.
|
1278
|
+
Return error document if the message contains multiple orchestration tools
|
1178
1279
|
"""
|
1179
|
-
try:
|
1180
|
-
tools = self.get_tool_messages(msg)
|
1181
|
-
tools = [t for t in tools if self._tool_recipient_match(t)]
|
1182
|
-
except ValidationError as ve:
|
1183
|
-
# correct tool name but bad fields
|
1184
|
-
return self.tool_validation_error(ve)
|
1185
|
-
except XMLException as xe: # from XMLToolMessage parsing
|
1186
|
-
return str(xe)
|
1187
|
-
except ValueError:
|
1188
|
-
# invalid tool name
|
1189
|
-
# We return None since returning "invalid tool name" would
|
1190
|
-
# be considered a valid result in task loop, and would be treated
|
1191
|
-
# as a response to the tool message even though the tool was not intended
|
1192
|
-
# for this agent.
|
1193
|
-
return None
|
1194
|
-
if len(tools) > 1 and not self.config.allow_multiple_tools:
|
1195
|
-
return self.to_ChatDocument("ERROR: Use ONE tool at a time!")
|
1196
|
-
if len(tools) == 0:
|
1197
|
-
fallback_result = self.handle_message_fallback(msg)
|
1198
|
-
if fallback_result is None:
|
1199
|
-
return None
|
1200
|
-
return self.to_ChatDocument(
|
1201
|
-
fallback_result,
|
1202
|
-
chat_doc=msg if isinstance(msg, ChatDocument) else None,
|
1203
|
-
)
|
1204
|
-
has_ids = all([t.id != "" for t in tools])
|
1205
|
-
chat_doc = msg if isinstance(msg, ChatDocument) else None
|
1206
|
-
|
1207
1280
|
# check whether there are multiple orchestration-tools (e.g. DoneTool etc),
|
1208
1281
|
# in which case set result to error-string since we don't yet support
|
1209
1282
|
# multi-tools with one or more orch tools.
|
@@ -1230,20 +1303,24 @@ class Agent(ABC):
|
|
1230
1303
|
)
|
1231
1304
|
|
1232
1305
|
has_orch = any(isinstance(t, ORCHESTRATION_TOOLS) for t in tools)
|
1233
|
-
results: List[str | ChatDocument | None]
|
1234
1306
|
if has_orch and len(tools) > 1:
|
1235
1307
|
err_str = "ERROR: Use ONE tool at a time!"
|
1236
|
-
|
1237
|
-
|
1238
|
-
|
1239
|
-
|
1240
|
-
|
1241
|
-
|
1242
|
-
|
1243
|
-
|
1308
|
+
return [err_str for _ in tools]
|
1309
|
+
|
1310
|
+
return []
|
1311
|
+
|
1312
|
+
def _handle_message_final(
|
1313
|
+
self, tools: List[ToolMessage], results: List[str | ChatDocument | None]
|
1314
|
+
) -> None | str | OrderedDict[str, str] | ChatDocument:
|
1315
|
+
"""
|
1316
|
+
Convert results to final response
|
1317
|
+
"""
|
1318
|
+
# extract content from ChatDocument results so we have all str|None
|
1319
|
+
results = [r.content if isinstance(r, ChatDocument) else r for r in results]
|
1244
1320
|
|
1245
|
-
# now all results are str|None
|
1246
1321
|
tool_names = [t.default_value("request") for t in tools]
|
1322
|
+
|
1323
|
+
has_ids = all([t.id != "" for t in tools])
|
1247
1324
|
if has_ids:
|
1248
1325
|
id2result = OrderedDict(
|
1249
1326
|
(t.id, r)
|
@@ -1278,6 +1355,112 @@ class Agent(ABC):
|
|
1278
1355
|
final = "\n\n".join(str_results)
|
1279
1356
|
return final
|
1280
1357
|
|
1358
|
+
async def handle_message_async(
|
1359
|
+
self, msg: str | ChatDocument
|
1360
|
+
) -> None | str | OrderedDict[str, str] | ChatDocument:
|
1361
|
+
"""
|
1362
|
+
Asynch version of `handle_message`. See there for details.
|
1363
|
+
"""
|
1364
|
+
try:
|
1365
|
+
tools = self.get_tool_messages(msg)
|
1366
|
+
tools = [t for t in tools if self._tool_recipient_match(t)]
|
1367
|
+
except ValidationError as ve:
|
1368
|
+
# correct tool name but bad fields
|
1369
|
+
return self.tool_validation_error(ve)
|
1370
|
+
except XMLException as xe: # from XMLToolMessage parsing
|
1371
|
+
return str(xe)
|
1372
|
+
except ValueError:
|
1373
|
+
# invalid tool name
|
1374
|
+
# We return None since returning "invalid tool name" would
|
1375
|
+
# be considered a valid result in task loop, and would be treated
|
1376
|
+
# as a response to the tool message even though the tool was not intended
|
1377
|
+
# for this agent.
|
1378
|
+
return None
|
1379
|
+
if len(tools) > 1 and not self.config.allow_multiple_tools:
|
1380
|
+
return self.to_ChatDocument("ERROR: Use ONE tool at a time!")
|
1381
|
+
if len(tools) == 0:
|
1382
|
+
fallback_result = self.handle_message_fallback(msg)
|
1383
|
+
if fallback_result is None:
|
1384
|
+
return None
|
1385
|
+
return self.to_ChatDocument(
|
1386
|
+
fallback_result,
|
1387
|
+
chat_doc=msg if isinstance(msg, ChatDocument) else None,
|
1388
|
+
)
|
1389
|
+
chat_doc = msg if isinstance(msg, ChatDocument) else None
|
1390
|
+
|
1391
|
+
results = self._get_multiple_orch_tool_errs(tools)
|
1392
|
+
if not results:
|
1393
|
+
results = [
|
1394
|
+
await self.handle_tool_message_async(t, chat_doc=chat_doc)
|
1395
|
+
for t in tools
|
1396
|
+
]
|
1397
|
+
# if there's a solitary ChatDocument|str result, return it as is
|
1398
|
+
if len(results) == 1 and isinstance(results[0], (str, ChatDocument)):
|
1399
|
+
return results[0]
|
1400
|
+
|
1401
|
+
return self._handle_message_final(tools, results)
|
1402
|
+
|
1403
|
+
def handle_message(
|
1404
|
+
self, msg: str | ChatDocument
|
1405
|
+
) -> None | str | OrderedDict[str, str] | ChatDocument:
|
1406
|
+
"""
|
1407
|
+
Handle a "tool" message either a string containing one or more
|
1408
|
+
valid "tool" JSON substrings, or a
|
1409
|
+
ChatDocument containing a `function_call` attribute.
|
1410
|
+
Handle with the corresponding handler method, and return
|
1411
|
+
the results as a combined string.
|
1412
|
+
|
1413
|
+
Args:
|
1414
|
+
msg (str | ChatDocument): The string or ChatDocument to handle
|
1415
|
+
|
1416
|
+
Returns:
|
1417
|
+
The result of the handler method can be:
|
1418
|
+
- None if no tools successfully handled, or no tools present
|
1419
|
+
- str if langroid-native JSON tools were handled, and results concatenated,
|
1420
|
+
OR there's a SINGLE OpenAI tool-call.
|
1421
|
+
(We do this so the common scenario of a single tool/fn-call
|
1422
|
+
has a simple behavior).
|
1423
|
+
- Dict[str, str] if multiple OpenAI tool-calls were handled
|
1424
|
+
(dict is an id->result map)
|
1425
|
+
- ChatDocument if a handler returned a ChatDocument, intended to be the
|
1426
|
+
final response of the `agent_response` method.
|
1427
|
+
"""
|
1428
|
+
try:
|
1429
|
+
tools = self.get_tool_messages(msg)
|
1430
|
+
tools = [t for t in tools if self._tool_recipient_match(t)]
|
1431
|
+
except ValidationError as ve:
|
1432
|
+
# correct tool name but bad fields
|
1433
|
+
return self.tool_validation_error(ve)
|
1434
|
+
except XMLException as xe: # from XMLToolMessage parsing
|
1435
|
+
return str(xe)
|
1436
|
+
except ValueError:
|
1437
|
+
# invalid tool name
|
1438
|
+
# We return None since returning "invalid tool name" would
|
1439
|
+
# be considered a valid result in task loop, and would be treated
|
1440
|
+
# as a response to the tool message even though the tool was not intended
|
1441
|
+
# for this agent.
|
1442
|
+
return None
|
1443
|
+
if len(tools) > 1 and not self.config.allow_multiple_tools:
|
1444
|
+
return self.to_ChatDocument("ERROR: Use ONE tool at a time!")
|
1445
|
+
if len(tools) == 0:
|
1446
|
+
fallback_result = self.handle_message_fallback(msg)
|
1447
|
+
if fallback_result is None:
|
1448
|
+
return None
|
1449
|
+
return self.to_ChatDocument(
|
1450
|
+
fallback_result,
|
1451
|
+
chat_doc=msg if isinstance(msg, ChatDocument) else None,
|
1452
|
+
)
|
1453
|
+
chat_doc = msg if isinstance(msg, ChatDocument) else None
|
1454
|
+
|
1455
|
+
results = self._get_multiple_orch_tool_errs(tools)
|
1456
|
+
if not results:
|
1457
|
+
results = [self.handle_tool_message(t, chat_doc=chat_doc) for t in tools]
|
1458
|
+
# if there's a solitary ChatDocument|str result, return it as is
|
1459
|
+
if len(results) == 1 and isinstance(results[0], (str, ChatDocument)):
|
1460
|
+
return results[0]
|
1461
|
+
|
1462
|
+
return self._handle_message_final(tools, results)
|
1463
|
+
|
1281
1464
|
@property
|
1282
1465
|
def all_llm_tools_known(self) -> set[str]:
|
1283
1466
|
"""All known tools; this may extend self.llm_tools_known."""
|
@@ -1546,6 +1729,37 @@ class Agent(ABC):
|
|
1546
1729
|
) + truncate_warning
|
1547
1730
|
return result
|
1548
1731
|
|
1732
|
+
async def handle_tool_message_async(
|
1733
|
+
self,
|
1734
|
+
tool: ToolMessage,
|
1735
|
+
chat_doc: Optional[ChatDocument] = None,
|
1736
|
+
) -> None | str | ChatDocument:
|
1737
|
+
"""
|
1738
|
+
Asynch version of `handle_tool_message`. See there for details.
|
1739
|
+
"""
|
1740
|
+
tool_name = tool.default_value("request")
|
1741
|
+
handler_method = getattr(self, tool_name + "_async", None)
|
1742
|
+
if handler_method is None:
|
1743
|
+
return self.handle_tool_message(tool, chat_doc=chat_doc)
|
1744
|
+
has_chat_doc_arg = (
|
1745
|
+
chat_doc is not None
|
1746
|
+
and "chat_doc" in inspect.signature(handler_method).parameters
|
1747
|
+
)
|
1748
|
+
try:
|
1749
|
+
if has_chat_doc_arg:
|
1750
|
+
maybe_result = await handler_method(tool, chat_doc=chat_doc)
|
1751
|
+
else:
|
1752
|
+
maybe_result = await handler_method(tool)
|
1753
|
+
result = self.to_ChatDocument(maybe_result, tool_name, chat_doc)
|
1754
|
+
except Exception as e:
|
1755
|
+
# raise the error here since we are sure it's
|
1756
|
+
# not a pydantic validation error,
|
1757
|
+
# which we check in `handle_message`
|
1758
|
+
raise e
|
1759
|
+
return self._maybe_truncate_result(
|
1760
|
+
result, tool._max_result_tokens
|
1761
|
+
) # type: ignore
|
1762
|
+
|
1549
1763
|
def handle_tool_message(
|
1550
1764
|
self,
|
1551
1765
|
tool: ToolMessage,
|