langroid 0.10.2__tar.gz → 0.11.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.10.2 → langroid-0.11.0}/PKG-INFO +1 -1
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/__init__.py +1 -2
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/base.py +138 -54
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/chat_agent.py +25 -4
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/chat_document.py +5 -1
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/task.py +129 -25
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/tool_message.py +15 -43
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/tools/__init__.py +4 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/tools/orchestration.py +87 -8
- {langroid-0.10.2 → langroid-0.11.0}/langroid/language_models/mock_lm.py +5 -4
- {langroid-0.10.2 → langroid-0.11.0}/langroid/parsing/web_search.py +7 -4
- langroid-0.11.0/langroid/utils/.chainlit/config.toml +121 -0
- langroid-0.11.0/langroid/utils/.chainlit/translations/en-US.json +231 -0
- langroid-0.11.0/langroid/utils/types.py +93 -0
- {langroid-0.10.2 → langroid-0.11.0}/pyproject.toml +2 -2
- {langroid-0.10.2 → langroid-0.11.0}/LICENSE +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/README.md +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/__init__.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/batch.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/callbacks/__init__.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/callbacks/chainlit.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/helpers.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/junk +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/openai_assistant.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/special/__init__.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/special/doc_chat_agent.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/special/lance_doc_chat_agent.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/special/lance_rag/__init__.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/special/lance_rag/critic_agent.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/special/lance_rag/lance_rag_task.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/special/lance_rag/query_planner_agent.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/special/lance_tools.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/special/neo4j/__init__.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/special/neo4j/csv_kg_chat.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/special/neo4j/neo4j_chat_agent.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/special/neo4j/utils/__init__.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/special/neo4j/utils/system_message.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/special/relevance_extractor_agent.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/special/retriever_agent.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/special/sql/__init__.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/special/sql/sql_chat_agent.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/special/sql/utils/__init__.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/special/sql/utils/description_extractors.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/special/sql/utils/populate_metadata.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/special/sql/utils/system_message.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/special/sql/utils/tools.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/special/table_chat_agent.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/structured_message.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/tools/duckduckgo_search_tool.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/tools/extract_tool.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/tools/generator_tool.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/tools/google_search_tool.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/tools/metaphor_search_tool.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/tools/note_tool.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/tools/recipient_tool.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/tools/retrieval_tool.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/tools/rewind_tool.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/tools/run_python_code.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/tools/segment_extract_tool.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent/typed_task.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/agent_config.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/cachedb/__init__.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/cachedb/base.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/cachedb/momento_cachedb.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/cachedb/redis_cachedb.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/embedding_models/__init__.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/embedding_models/base.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/embedding_models/clustering.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/embedding_models/models.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/embedding_models/protoc/__init__.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/embedding_models/protoc/embeddings.proto +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/embedding_models/protoc/embeddings_pb2.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/embedding_models/protoc/embeddings_pb2.pyi +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/embedding_models/protoc/embeddings_pb2_grpc.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/embedding_models/remote_embeds.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/exceptions.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/language_models/.chainlit/config.toml +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/language_models/.chainlit/translations/en-US.json +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/language_models/__init__.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/language_models/azure_openai.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/language_models/base.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/language_models/config.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/language_models/openai_gpt.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/language_models/prompt_formatter/__init__.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/language_models/prompt_formatter/base.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/language_models/prompt_formatter/hf_formatter.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/language_models/prompt_formatter/llama2_formatter.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/language_models/utils.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/mytypes.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/parsing/__init__.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/parsing/agent_chats.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/parsing/code-parsing.md +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/parsing/code_parser.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/parsing/config.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/parsing/document_parser.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/parsing/image_text.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/parsing/para_sentence_split.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/parsing/parse_json.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/parsing/parser.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/parsing/repo_loader.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/parsing/routing.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/parsing/search.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/parsing/spider.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/parsing/table_loader.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/parsing/url_loader.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/parsing/url_loader_cookies.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/parsing/urls.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/parsing/utils.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/prompts/__init__.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/prompts/chat-gpt4-system-prompt.md +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/prompts/dialog.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/prompts/prompts_config.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/prompts/templates.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/pydantic_v1/__init__.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/pydantic_v1/main.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/utils/__init__.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/utils/algorithms/__init__.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/utils/algorithms/graph.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/utils/configuration.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/utils/constants.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/utils/docker.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/utils/globals.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/utils/llms/__init__.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/utils/llms/strings.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/utils/logging.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/utils/object_registry.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/utils/output/__init__.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/utils/output/citations.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/utils/output/printing.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/utils/output/status.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/utils/pandas_utils.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/utils/pydantic_utils.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/utils/system.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/utils/web/__init__.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/utils/web/login.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/vector_store/__init__.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/vector_store/base.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/vector_store/chromadb.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/vector_store/lancedb.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/vector_store/meilisearch.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/vector_store/momento.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/vector_store/qdrant_cloud.py +0 -0
- {langroid-0.10.2 → langroid-0.11.0}/langroid/vector_store/qdrantdb.py +0 -0
@@ -6,7 +6,7 @@ from .chat_document import (
|
|
6
6
|
ChatDocument,
|
7
7
|
)
|
8
8
|
from .chat_agent import ChatAgentConfig, ChatAgent
|
9
|
-
from .tool_message import ToolMessage
|
9
|
+
from .tool_message import ToolMessage
|
10
10
|
from .task import Task
|
11
11
|
|
12
12
|
from . import base
|
@@ -29,7 +29,6 @@ __all__ = [
|
|
29
29
|
"ChatAgent",
|
30
30
|
"ChatAgentConfig",
|
31
31
|
"ToolMessage",
|
32
|
-
"FinalResultTool",
|
33
32
|
"Task",
|
34
33
|
"base",
|
35
34
|
"chat_document",
|
@@ -18,7 +18,10 @@ from typing import (
|
|
18
18
|
Set,
|
19
19
|
Tuple,
|
20
20
|
Type,
|
21
|
+
TypeVar,
|
21
22
|
cast,
|
23
|
+
get_args,
|
24
|
+
get_origin,
|
22
25
|
no_type_check,
|
23
26
|
)
|
24
27
|
|
@@ -46,7 +49,6 @@ from langroid.parsing.parse_json import extract_top_level_json
|
|
46
49
|
from langroid.parsing.parser import Parser, ParsingConfig
|
47
50
|
from langroid.prompts.prompts_config import PromptsConfig
|
48
51
|
from langroid.pydantic_v1 import (
|
49
|
-
BaseModel,
|
50
52
|
BaseSettings,
|
51
53
|
Field,
|
52
54
|
ValidationError,
|
@@ -56,6 +58,7 @@ from langroid.utils.configuration import settings
|
|
56
58
|
from langroid.utils.constants import DONE, NO_ANSWER, PASS, PASS_TO, SEND_TO
|
57
59
|
from langroid.utils.object_registry import ObjectRegistry
|
58
60
|
from langroid.utils.output import status
|
61
|
+
from langroid.utils.types import from_string, to_string
|
59
62
|
from langroid.vector_store.base import VectorStore, VectorStoreConfig
|
60
63
|
|
61
64
|
ORCHESTRATION_STRINGS = [DONE, PASS, PASS_TO, SEND_TO]
|
@@ -63,6 +66,8 @@ console = Console(quiet=settings.quiet)
|
|
63
66
|
|
64
67
|
logger = logging.getLogger(__name__)
|
65
68
|
|
69
|
+
T = TypeVar("T")
|
70
|
+
|
66
71
|
|
67
72
|
class AgentConfig(BaseSettings):
|
68
73
|
"""
|
@@ -78,6 +83,7 @@ class AgentConfig(BaseSettings):
|
|
78
83
|
prompts: Optional[PromptsConfig] = PromptsConfig()
|
79
84
|
show_stats: bool = True # show token usage/cost stats?
|
80
85
|
add_to_registry: bool = True # register agent in ObjectRegistry?
|
86
|
+
respond_tools_only: bool = False # respond only to tool messages (not plain text)?
|
81
87
|
|
82
88
|
@validator("name")
|
83
89
|
def check_name_alphanum(cls, v: str) -> str:
|
@@ -341,6 +347,7 @@ class Agent(ABC):
|
|
341
347
|
def create_agent_response(
|
342
348
|
self,
|
343
349
|
content: str | None = None,
|
350
|
+
content_any: Any = None,
|
344
351
|
tool_messages: List[ToolMessage] = [],
|
345
352
|
oai_tool_calls: Optional[List[OpenAIToolCall]] = None,
|
346
353
|
oai_tool_choice: ToolChoiceTypes | Dict[str, Dict[str, str] | str] = "auto",
|
@@ -352,6 +359,7 @@ class Agent(ABC):
|
|
352
359
|
return self.response_template(
|
353
360
|
Entity.AGENT,
|
354
361
|
content=content,
|
362
|
+
content_any=content_any,
|
355
363
|
tool_messages=tool_messages,
|
356
364
|
oai_tool_calls=oai_tool_calls,
|
357
365
|
oai_tool_choice=oai_tool_choice,
|
@@ -543,6 +551,7 @@ class Agent(ABC):
|
|
543
551
|
self,
|
544
552
|
e: Entity,
|
545
553
|
content: str | None = None,
|
554
|
+
content_any: Any = None,
|
546
555
|
tool_messages: List[ToolMessage] = [],
|
547
556
|
oai_tool_calls: Optional[List[OpenAIToolCall]] = None,
|
548
557
|
oai_tool_choice: ToolChoiceTypes | Dict[str, Dict[str, str] | str] = "auto",
|
@@ -553,6 +562,7 @@ class Agent(ABC):
|
|
553
562
|
"""Template for response from entity `e`."""
|
554
563
|
return ChatDocument(
|
555
564
|
content=content or "",
|
565
|
+
content_any=content_any,
|
556
566
|
tool_messages=tool_messages,
|
557
567
|
oai_tool_calls=oai_tool_calls,
|
558
568
|
oai_tool_id2result=oai_tool_id2result,
|
@@ -566,6 +576,7 @@ class Agent(ABC):
|
|
566
576
|
def create_user_response(
|
567
577
|
self,
|
568
578
|
content: str | None = None,
|
579
|
+
content_any: Any = None,
|
569
580
|
tool_messages: List[ToolMessage] = [],
|
570
581
|
oai_tool_calls: List[OpenAIToolCall] | None = None,
|
571
582
|
oai_tool_choice: ToolChoiceTypes | Dict[str, Dict[str, str] | str] = "auto",
|
@@ -577,6 +588,7 @@ class Agent(ABC):
|
|
577
588
|
return self.response_template(
|
578
589
|
e=Entity.USER,
|
579
590
|
content=content,
|
591
|
+
content_any=content_any,
|
580
592
|
tool_messages=tool_messages,
|
581
593
|
oai_tool_calls=oai_tool_calls,
|
582
594
|
oai_tool_choice=oai_tool_choice,
|
@@ -677,9 +689,26 @@ class Agent(ABC):
|
|
677
689
|
|
678
690
|
return True
|
679
691
|
|
692
|
+
def can_respond(self, message: Optional[str | ChatDocument] = None) -> bool:
|
693
|
+
"""
|
694
|
+
Whether the agent can respond to a message.
|
695
|
+
Used in Task.py to skip a sub-task when we know it would not respond.
|
696
|
+
Args:
|
697
|
+
message (str|ChatDocument): message or ChatDocument object to respond to.
|
698
|
+
"""
|
699
|
+
tools = self.get_tool_messages(message)
|
700
|
+
if len(tools) == 0 and self.config.respond_tools_only:
|
701
|
+
return False
|
702
|
+
if message is not None and self.has_only_unhandled_tools(message):
|
703
|
+
# The message has tools that are NOT enabled to be handled by this agent,
|
704
|
+
# which means the agent cannot respond to it.
|
705
|
+
return False
|
706
|
+
return True
|
707
|
+
|
680
708
|
def create_llm_response(
|
681
709
|
self,
|
682
710
|
content: str | None = None,
|
711
|
+
content_any: Any = None,
|
683
712
|
tool_messages: List[ToolMessage] = [],
|
684
713
|
oai_tool_calls: None | List[OpenAIToolCall] = None,
|
685
714
|
oai_tool_choice: ToolChoiceTypes | Dict[str, Dict[str, str] | str] = "auto",
|
@@ -691,6 +720,7 @@ class Agent(ABC):
|
|
691
720
|
return self.response_template(
|
692
721
|
Entity.LLM,
|
693
722
|
content=content,
|
723
|
+
content_any=content_any,
|
694
724
|
tool_messages=tool_messages,
|
695
725
|
oai_tool_calls=oai_tool_calls,
|
696
726
|
oai_tool_choice=oai_tool_choice,
|
@@ -856,6 +886,8 @@ class Agent(ABC):
|
|
856
886
|
Does the msg have at least one tool, and ALL tools are
|
857
887
|
disabled for handling by this agent?
|
858
888
|
"""
|
889
|
+
if msg is None:
|
890
|
+
return False
|
859
891
|
tools = self.get_tool_messages(msg, all_tools=True)
|
860
892
|
if len(tools) == 0:
|
861
893
|
return False
|
@@ -863,7 +895,7 @@ class Agent(ABC):
|
|
863
895
|
|
864
896
|
def get_tool_messages(
|
865
897
|
self,
|
866
|
-
msg: str | ChatDocument,
|
898
|
+
msg: str | ChatDocument | None,
|
867
899
|
all_tools: bool = False,
|
868
900
|
) -> List[ToolMessage]:
|
869
901
|
"""
|
@@ -874,6 +906,9 @@ class Agent(ABC):
|
|
874
906
|
- otherwise, return only the tools handled by this agent.
|
875
907
|
"""
|
876
908
|
|
909
|
+
if msg is None:
|
910
|
+
return []
|
911
|
+
|
877
912
|
if isinstance(msg, str):
|
878
913
|
json_tools = self.get_json_tool_messages(msg)
|
879
914
|
if all_tools:
|
@@ -1070,7 +1105,7 @@ class Agent(ABC):
|
|
1070
1105
|
fallback_result = self.handle_message_fallback(msg)
|
1071
1106
|
if fallback_result is None:
|
1072
1107
|
return None
|
1073
|
-
return self.
|
1108
|
+
return self.to_ChatDocument(
|
1074
1109
|
fallback_result,
|
1075
1110
|
chat_doc=msg if isinstance(msg, ChatDocument) else None,
|
1076
1111
|
)
|
@@ -1109,7 +1144,13 @@ class Agent(ABC):
|
|
1109
1144
|
results = [err_str for _ in tools]
|
1110
1145
|
else:
|
1111
1146
|
results = [self.handle_tool_message(t, chat_doc=chat_doc) for t in tools]
|
1147
|
+
# if there's a solitary ChatDocument|str result, return it as is
|
1148
|
+
if len(results) == 1 and isinstance(results[0], (str, ChatDocument)):
|
1149
|
+
return results[0]
|
1150
|
+
# extract content from ChatDocument results so we have all str|None
|
1151
|
+
results = [r.content if isinstance(r, ChatDocument) else r for r in results]
|
1112
1152
|
|
1153
|
+
# now all results are str|None
|
1113
1154
|
tool_names = [t.default_value("request") for t in tools]
|
1114
1155
|
if has_ids:
|
1115
1156
|
id2result = OrderedDict(
|
@@ -1132,35 +1173,16 @@ class Agent(ABC):
|
|
1132
1173
|
(name, r) for name, r in zip(tool_names, results) if r is not None
|
1133
1174
|
]
|
1134
1175
|
if len(name_results_list) == 0:
|
1135
|
-
return None
|
1176
|
+
return None
|
1177
|
+
|
1136
1178
|
# there was a non-None result
|
1137
|
-
chat_doc_results = [
|
1138
|
-
r for _, r in name_results_list if isinstance(r, ChatDocument)
|
1139
|
-
]
|
1140
|
-
if len(chat_doc_results) > 1:
|
1141
|
-
logger.warning(
|
1142
|
-
"""There were multiple ChatDocument results from tools,
|
1143
|
-
which is unexpected. The first one will be returned, and the others
|
1144
|
-
will be ignored.
|
1145
|
-
"""
|
1146
|
-
)
|
1147
|
-
if len(chat_doc_results) > 0:
|
1148
|
-
return chat_doc_results[0]
|
1149
1179
|
|
1150
1180
|
if has_ids and len(id2result) > 1:
|
1151
1181
|
# if there are multiple OpenAI Tool results, return them as a dict
|
1152
1182
|
return id2result
|
1153
1183
|
|
1154
|
-
if len(name_results_list) == 1 and isinstance(name_results_list[0][1], str):
|
1155
|
-
# single str result -- return it as is
|
1156
|
-
return name_results_list[0][1]
|
1157
|
-
|
1158
1184
|
# multi-results: prepend the tool name to each result
|
1159
|
-
str_results = [
|
1160
|
-
f"Result from {name}: {r}"
|
1161
|
-
for name, r in name_results_list
|
1162
|
-
if isinstance(r, str)
|
1163
|
-
]
|
1185
|
+
str_results = [f"Result from {name}: {r}" for name, r in name_results_list]
|
1164
1186
|
final = "\n\n".join(str_results)
|
1165
1187
|
return final
|
1166
1188
|
|
@@ -1260,20 +1282,41 @@ class Agent(ABC):
|
|
1260
1282
|
raise ve
|
1261
1283
|
return message
|
1262
1284
|
|
1263
|
-
def
|
1285
|
+
def to_ChatDocument(
|
1264
1286
|
self,
|
1265
1287
|
msg: Any,
|
1266
1288
|
orig_tool_name: str | None = None,
|
1267
1289
|
chat_doc: Optional[ChatDocument] = None,
|
1268
|
-
|
1290
|
+
author_entity: Entity = Entity.AGENT,
|
1291
|
+
) -> Optional[ChatDocument]:
|
1269
1292
|
"""
|
1270
|
-
|
1293
|
+
Convert result of a responder (agent_response or llm_response, or task.run()),
|
1294
|
+
or tool handler, or handle_message_fallback,
|
1295
|
+
to a ChatDocument, to enabling handling by other
|
1296
|
+
responders/tasks in a task loop possibly involving multiple agents.
|
1297
|
+
|
1298
|
+
Args:
|
1299
|
+
msg (Any): The result of a responder or tool handler or task.run()
|
1300
|
+
orig_tool_name (str): The original tool name that generated the response,
|
1301
|
+
if any.
|
1302
|
+
chat_doc (ChatDocument): The original ChatDocument object that `msg`
|
1303
|
+
is a response to.
|
1304
|
+
author_entity (Entity): The intended author of the result ChatDocument
|
1271
1305
|
"""
|
1272
|
-
if isinstance(msg,
|
1306
|
+
if msg is None or isinstance(msg, ChatDocument):
|
1307
|
+
return msg
|
1308
|
+
|
1309
|
+
is_agent_author = author_entity == Entity.AGENT
|
1310
|
+
|
1311
|
+
if isinstance(msg, str):
|
1312
|
+
return self.response_template(author_entity, content=msg, content_any=msg)
|
1313
|
+
elif isinstance(msg, ToolMessage):
|
1273
1314
|
# result is a ToolMessage, so...
|
1274
1315
|
result_tool_name = msg.default_value("request")
|
1275
|
-
if
|
1276
|
-
|
1316
|
+
if (
|
1317
|
+
is_agent_author
|
1318
|
+
and result_tool_name in self.llm_tools_handled
|
1319
|
+
and (orig_tool_name is None or orig_tool_name != result_tool_name)
|
1277
1320
|
):
|
1278
1321
|
# TODO: do we need to remove the tool message from the chat_doc?
|
1279
1322
|
# if (chat_doc is not None and
|
@@ -1281,30 +1324,73 @@ class Agent(ABC):
|
|
1281
1324
|
# chat_doc.tool_messages.remove(msg)
|
1282
1325
|
# if we can handle it, do so
|
1283
1326
|
result = self.handle_tool_message(msg, chat_doc=chat_doc)
|
1327
|
+
if result is not None and isinstance(result, ChatDocument):
|
1328
|
+
return result
|
1284
1329
|
else:
|
1285
1330
|
# else wrap it in an agent response and return it so
|
1286
1331
|
# orchestrator can find a respondent
|
1287
|
-
|
1288
|
-
elif isinstance(msg, (ChatDocument, str)):
|
1289
|
-
result = msg
|
1290
|
-
elif isinstance(msg, BaseModel):
|
1291
|
-
result = msg.json()
|
1332
|
+
return self.response_template(author_entity, tool_messages=[msg])
|
1292
1333
|
else:
|
1293
|
-
|
1294
|
-
|
1295
|
-
|
1296
|
-
|
1297
|
-
|
1298
|
-
|
1299
|
-
|
1300
|
-
|
1301
|
-
|
1302
|
-
|
1303
|
-
|
1304
|
-
|
1305
|
-
|
1306
|
-
|
1307
|
-
return
|
1334
|
+
result = to_string(msg)
|
1335
|
+
|
1336
|
+
return (
|
1337
|
+
None
|
1338
|
+
if result is None
|
1339
|
+
else self.response_template(author_entity, content=result, content_any=msg)
|
1340
|
+
)
|
1341
|
+
|
1342
|
+
def from_ChatDocument(self, msg: ChatDocument, output_type: Type[T]) -> Optional[T]:
|
1343
|
+
"""
|
1344
|
+
Extract a desired output_type from a ChatDocument object.
|
1345
|
+
We use this fallback order:
|
1346
|
+
- if `msg.content_any` exists and matches the output_type, return it
|
1347
|
+
- if `msg.content` exists and output_type is str return it
|
1348
|
+
- if output_type is a ToolMessage, return the first tool in `msg.tool_messages`
|
1349
|
+
- if output_type is a list of ToolMessage,
|
1350
|
+
return all tools in `msg.tool_messages`
|
1351
|
+
- search for a tool in `msg.tool_messages` that has a field of output_type,
|
1352
|
+
and if found, return that field value
|
1353
|
+
- return None if all the above fail
|
1354
|
+
"""
|
1355
|
+
content = msg.content
|
1356
|
+
if output_type is str and content != "":
|
1357
|
+
return cast(T, content)
|
1358
|
+
content_any = msg.content_any
|
1359
|
+
if content_any is not None and isinstance(content_any, output_type):
|
1360
|
+
return cast(T, content_any)
|
1361
|
+
|
1362
|
+
tools = self.get_tool_messages(msg, all_tools=True)
|
1363
|
+
|
1364
|
+
if get_origin(output_type) is list:
|
1365
|
+
list_element_type = get_args(output_type)[0]
|
1366
|
+
if issubclass(list_element_type, ToolMessage):
|
1367
|
+
# list_element_type is a subclass of ToolMessage:
|
1368
|
+
# We output a list of objects derived from list_element_type
|
1369
|
+
return cast(
|
1370
|
+
T,
|
1371
|
+
[t for t in tools if isinstance(t, list_element_type)],
|
1372
|
+
)
|
1373
|
+
elif get_origin(output_type) is None and issubclass(output_type, ToolMessage):
|
1374
|
+
# output_type is a subclass of ToolMessage:
|
1375
|
+
# return the first tool that has this specific output_type
|
1376
|
+
for tool in tools:
|
1377
|
+
if isinstance(tool, output_type):
|
1378
|
+
return cast(T, tool)
|
1379
|
+
return None
|
1380
|
+
elif get_origin(output_type) is None and output_type in (str, int, float, bool):
|
1381
|
+
# attempt to get the output_type from the content,
|
1382
|
+
# if it's a primitive type
|
1383
|
+
primitive_value = from_string(content, output_type) # type: ignore
|
1384
|
+
if primitive_value is not None:
|
1385
|
+
return cast(T, primitive_value)
|
1386
|
+
|
1387
|
+
# then search for output_type as a field in a tool
|
1388
|
+
for tool in tools:
|
1389
|
+
value = tool.get_value_of_type(output_type)
|
1390
|
+
if value is not None:
|
1391
|
+
return cast(T, value)
|
1392
|
+
|
1393
|
+
return None
|
1308
1394
|
|
1309
1395
|
def handle_tool_message(
|
1310
1396
|
self,
|
@@ -1335,9 +1421,7 @@ class Agent(ABC):
|
|
1335
1421
|
maybe_result = handler_method(tool, chat_doc=chat_doc)
|
1336
1422
|
else:
|
1337
1423
|
maybe_result = handler_method(tool)
|
1338
|
-
result = self.
|
1339
|
-
maybe_result, tool_name, chat_doc
|
1340
|
-
)
|
1424
|
+
result = self.to_ChatDocument(maybe_result, tool_name, chat_doc)
|
1341
1425
|
except Exception as e:
|
1342
1426
|
# raise the error here since we are sure it's
|
1343
1427
|
# not a pydantic validation error,
|
@@ -161,6 +161,7 @@ class ChatAgent(Agent):
|
|
161
161
|
DoneTool,
|
162
162
|
ForwardTool,
|
163
163
|
PassTool,
|
164
|
+
ResultTool,
|
164
165
|
SendTool,
|
165
166
|
)
|
166
167
|
|
@@ -171,6 +172,7 @@ class ChatAgent(Agent):
|
|
171
172
|
self.enable_message(DonePassTool, use=False, handle=True)
|
172
173
|
self.enable_message(SendTool, use=False, handle=True)
|
173
174
|
self.enable_message(AgentSendTool, use=False, handle=True)
|
175
|
+
self.enable_message(ResultTool, use=False, handle=True)
|
174
176
|
|
175
177
|
def init_state(self) -> None:
|
176
178
|
"""
|
@@ -312,8 +314,7 @@ class ChatAgent(Agent):
|
|
312
314
|
usable_tool_classes: List[Type[ToolMessage]] = [
|
313
315
|
t
|
314
316
|
for t in list(self.llm_tools_map.values())
|
315
|
-
if
|
316
|
-
and t.default_value("request") in self.llm_tools_usable
|
317
|
+
if t.default_value("request") in self.llm_tools_usable
|
317
318
|
]
|
318
319
|
|
319
320
|
if len(usable_tool_classes) == 0:
|
@@ -522,6 +523,13 @@ class ChatAgent(Agent):
|
|
522
523
|
tools = self._get_tool_list(message_class)
|
523
524
|
if message_class is not None:
|
524
525
|
request = message_class.default_value("request")
|
526
|
+
if request == "":
|
527
|
+
raise ValueError(
|
528
|
+
f"""
|
529
|
+
ToolMessage class {message_class} must have a non-empty
|
530
|
+
'request' field if it is to be enabled as a tool.
|
531
|
+
"""
|
532
|
+
)
|
525
533
|
llm_function = message_class.llm_function_schema(defaults=include_defaults)
|
526
534
|
self.llm_functions_map[request] = llm_function
|
527
535
|
if force:
|
@@ -540,8 +548,21 @@ class ChatAgent(Agent):
|
|
540
548
|
self.llm_functions_handled.discard(t)
|
541
549
|
|
542
550
|
if use:
|
543
|
-
self.
|
544
|
-
|
551
|
+
tool_class = self.llm_tools_map[t]
|
552
|
+
if tool_class._allow_llm_use:
|
553
|
+
self.llm_tools_usable.add(t)
|
554
|
+
self.llm_functions_usable.add(t)
|
555
|
+
else:
|
556
|
+
logger.warning(
|
557
|
+
f"""
|
558
|
+
ToolMessage class {tool_class} does not allow LLM use,
|
559
|
+
because `_allow_llm_use=False` either in the Tool or a
|
560
|
+
parent class of this tool;
|
561
|
+
so not enabling LLM use for this tool!
|
562
|
+
If you intended an LLM to use this tool,
|
563
|
+
set `_allow_llm_use=True` when you define the tool.
|
564
|
+
"""
|
565
|
+
)
|
545
566
|
else:
|
546
567
|
self.llm_tools_usable.discard(t)
|
547
568
|
self.llm_functions_usable.discard(t)
|
@@ -22,6 +22,7 @@ from langroid.parsing.parse_json import extract_top_level_json, top_level_json_f
|
|
22
22
|
from langroid.pydantic_v1 import BaseModel, Extra
|
23
23
|
from langroid.utils.object_registry import ObjectRegistry
|
24
24
|
from langroid.utils.output.printing import shorten_text
|
25
|
+
from langroid.utils.types import to_string
|
25
26
|
|
26
27
|
|
27
28
|
class ChatDocAttachment(BaseModel):
|
@@ -115,6 +116,7 @@ class ChatDocument(Document):
|
|
115
116
|
attachment (None | ChatDocAttachment): Any additional data attached.
|
116
117
|
"""
|
117
118
|
|
119
|
+
content_any: Any = None # to hold arbitrary data returned by responders
|
118
120
|
oai_tool_calls: Optional[List[OpenAIToolCall]] = None
|
119
121
|
oai_tool_id2result: Optional[OrderedDict[str, str]] = None
|
120
122
|
oai_tool_choice: ToolChoiceTypes | Dict[str, Dict[str, str] | str] = "auto"
|
@@ -281,6 +283,7 @@ class ChatDocument(Document):
|
|
281
283
|
ChatDocument._clean_fn_call(oai_tc.function)
|
282
284
|
return ChatDocument(
|
283
285
|
content=message,
|
286
|
+
content_any=message,
|
284
287
|
oai_tool_calls=response.oai_tool_calls,
|
285
288
|
function_call=response.function_call,
|
286
289
|
metadata=ChatDocMetaData(
|
@@ -303,6 +306,7 @@ class ChatDocument(Document):
|
|
303
306
|
message = msg # retain the whole msg in this case
|
304
307
|
return ChatDocument(
|
305
308
|
content=message,
|
309
|
+
content_any=message,
|
306
310
|
metadata=ChatDocMetaData(
|
307
311
|
source=Entity.USER,
|
308
312
|
sender=Entity.USER,
|
@@ -335,7 +339,7 @@ class ChatDocument(Document):
|
|
335
339
|
tool_id = "" # for OpenAI Assistant
|
336
340
|
chat_document_id: str = ""
|
337
341
|
if isinstance(message, ChatDocument):
|
338
|
-
content = message.content
|
342
|
+
content = message.content or to_string(message.content_any) or ""
|
339
343
|
fun_call = message.function_call
|
340
344
|
oai_tool_calls = message.oai_tool_calls
|
341
345
|
if message.metadata.sender == Entity.USER and fun_call is not None:
|