langroid 0.31.2__py3-none-any.whl → 0.33.3__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-0.31.2.dist-info → langroid-0.33.3.dist-info}/METADATA +150 -124
- langroid-0.33.3.dist-info/RECORD +7 -0
- {langroid-0.31.2.dist-info → langroid-0.33.3.dist-info}/WHEEL +1 -1
- langroid-0.33.3.dist-info/entry_points.txt +4 -0
- pyproject.toml +317 -212
- langroid/__init__.py +0 -106
- langroid/agent/.chainlit/config.toml +0 -121
- langroid/agent/.chainlit/translations/bn.json +0 -231
- langroid/agent/.chainlit/translations/en-US.json +0 -229
- langroid/agent/.chainlit/translations/gu.json +0 -231
- langroid/agent/.chainlit/translations/he-IL.json +0 -231
- langroid/agent/.chainlit/translations/hi.json +0 -231
- langroid/agent/.chainlit/translations/kn.json +0 -231
- langroid/agent/.chainlit/translations/ml.json +0 -231
- langroid/agent/.chainlit/translations/mr.json +0 -231
- langroid/agent/.chainlit/translations/ta.json +0 -231
- langroid/agent/.chainlit/translations/te.json +0 -231
- langroid/agent/.chainlit/translations/zh-CN.json +0 -229
- langroid/agent/__init__.py +0 -41
- langroid/agent/base.py +0 -1981
- langroid/agent/batch.py +0 -398
- langroid/agent/callbacks/__init__.py +0 -0
- langroid/agent/callbacks/chainlit.py +0 -598
- langroid/agent/chat_agent.py +0 -1899
- langroid/agent/chat_document.py +0 -454
- langroid/agent/helpers.py +0 -0
- langroid/agent/junk +0 -13
- langroid/agent/openai_assistant.py +0 -882
- langroid/agent/special/__init__.py +0 -59
- langroid/agent/special/arangodb/__init__.py +0 -0
- langroid/agent/special/arangodb/arangodb_agent.py +0 -656
- langroid/agent/special/arangodb/system_messages.py +0 -186
- langroid/agent/special/arangodb/tools.py +0 -107
- langroid/agent/special/arangodb/utils.py +0 -36
- langroid/agent/special/doc_chat_agent.py +0 -1466
- langroid/agent/special/lance_doc_chat_agent.py +0 -262
- langroid/agent/special/lance_rag/__init__.py +0 -9
- langroid/agent/special/lance_rag/critic_agent.py +0 -198
- langroid/agent/special/lance_rag/lance_rag_task.py +0 -82
- langroid/agent/special/lance_rag/query_planner_agent.py +0 -260
- langroid/agent/special/lance_tools.py +0 -61
- langroid/agent/special/neo4j/__init__.py +0 -0
- langroid/agent/special/neo4j/csv_kg_chat.py +0 -174
- langroid/agent/special/neo4j/neo4j_chat_agent.py +0 -433
- langroid/agent/special/neo4j/system_messages.py +0 -120
- langroid/agent/special/neo4j/tools.py +0 -32
- langroid/agent/special/relevance_extractor_agent.py +0 -127
- langroid/agent/special/retriever_agent.py +0 -56
- langroid/agent/special/sql/__init__.py +0 -17
- langroid/agent/special/sql/sql_chat_agent.py +0 -654
- langroid/agent/special/sql/utils/__init__.py +0 -21
- langroid/agent/special/sql/utils/description_extractors.py +0 -190
- langroid/agent/special/sql/utils/populate_metadata.py +0 -85
- langroid/agent/special/sql/utils/system_message.py +0 -35
- langroid/agent/special/sql/utils/tools.py +0 -64
- langroid/agent/special/table_chat_agent.py +0 -263
- langroid/agent/structured_message.py +0 -9
- langroid/agent/task.py +0 -2093
- langroid/agent/tool_message.py +0 -393
- langroid/agent/tools/__init__.py +0 -38
- langroid/agent/tools/duckduckgo_search_tool.py +0 -50
- langroid/agent/tools/file_tools.py +0 -234
- langroid/agent/tools/google_search_tool.py +0 -39
- langroid/agent/tools/metaphor_search_tool.py +0 -67
- langroid/agent/tools/orchestration.py +0 -303
- langroid/agent/tools/recipient_tool.py +0 -235
- langroid/agent/tools/retrieval_tool.py +0 -32
- langroid/agent/tools/rewind_tool.py +0 -137
- langroid/agent/tools/segment_extract_tool.py +0 -41
- langroid/agent/typed_task.py +0 -19
- langroid/agent/xml_tool_message.py +0 -382
- langroid/agent_config.py +0 -0
- langroid/cachedb/__init__.py +0 -17
- langroid/cachedb/base.py +0 -58
- langroid/cachedb/momento_cachedb.py +0 -108
- langroid/cachedb/redis_cachedb.py +0 -153
- langroid/embedding_models/__init__.py +0 -39
- langroid/embedding_models/base.py +0 -74
- langroid/embedding_models/clustering.py +0 -189
- langroid/embedding_models/models.py +0 -461
- langroid/embedding_models/protoc/__init__.py +0 -0
- langroid/embedding_models/protoc/embeddings.proto +0 -19
- langroid/embedding_models/protoc/embeddings_pb2.py +0 -33
- langroid/embedding_models/protoc/embeddings_pb2.pyi +0 -50
- langroid/embedding_models/protoc/embeddings_pb2_grpc.py +0 -79
- langroid/embedding_models/remote_embeds.py +0 -153
- langroid/exceptions.py +0 -65
- langroid/experimental/team-save.py +0 -391
- langroid/language_models/.chainlit/config.toml +0 -121
- langroid/language_models/.chainlit/translations/en-US.json +0 -231
- langroid/language_models/__init__.py +0 -53
- langroid/language_models/azure_openai.py +0 -153
- langroid/language_models/base.py +0 -678
- langroid/language_models/config.py +0 -18
- langroid/language_models/mock_lm.py +0 -124
- langroid/language_models/openai_gpt.py +0 -1923
- langroid/language_models/prompt_formatter/__init__.py +0 -16
- langroid/language_models/prompt_formatter/base.py +0 -40
- langroid/language_models/prompt_formatter/hf_formatter.py +0 -132
- langroid/language_models/prompt_formatter/llama2_formatter.py +0 -75
- langroid/language_models/utils.py +0 -147
- langroid/mytypes.py +0 -84
- langroid/parsing/__init__.py +0 -52
- langroid/parsing/agent_chats.py +0 -38
- langroid/parsing/code-parsing.md +0 -86
- langroid/parsing/code_parser.py +0 -121
- langroid/parsing/config.py +0 -0
- langroid/parsing/document_parser.py +0 -718
- langroid/parsing/image_text.py +0 -32
- langroid/parsing/para_sentence_split.py +0 -62
- langroid/parsing/parse_json.py +0 -155
- langroid/parsing/parser.py +0 -313
- langroid/parsing/repo_loader.py +0 -790
- langroid/parsing/routing.py +0 -36
- langroid/parsing/search.py +0 -275
- langroid/parsing/spider.py +0 -102
- langroid/parsing/table_loader.py +0 -94
- langroid/parsing/url_loader.py +0 -111
- langroid/parsing/url_loader_cookies.py +0 -73
- langroid/parsing/urls.py +0 -273
- langroid/parsing/utils.py +0 -373
- langroid/parsing/web_search.py +0 -155
- langroid/prompts/__init__.py +0 -9
- langroid/prompts/chat-gpt4-system-prompt.md +0 -68
- langroid/prompts/dialog.py +0 -17
- langroid/prompts/prompts_config.py +0 -5
- langroid/prompts/templates.py +0 -141
- langroid/pydantic_v1/__init__.py +0 -10
- langroid/pydantic_v1/main.py +0 -4
- langroid/utils/.chainlit/config.toml +0 -121
- langroid/utils/.chainlit/translations/en-US.json +0 -231
- langroid/utils/__init__.py +0 -19
- langroid/utils/algorithms/__init__.py +0 -3
- langroid/utils/algorithms/graph.py +0 -103
- langroid/utils/configuration.py +0 -98
- langroid/utils/constants.py +0 -30
- langroid/utils/docker.py +0 -37
- langroid/utils/git_utils.py +0 -252
- langroid/utils/globals.py +0 -49
- langroid/utils/llms/__init__.py +0 -0
- langroid/utils/llms/strings.py +0 -8
- langroid/utils/logging.py +0 -135
- langroid/utils/object_registry.py +0 -66
- langroid/utils/output/__init__.py +0 -20
- langroid/utils/output/citations.py +0 -41
- langroid/utils/output/printing.py +0 -99
- langroid/utils/output/status.py +0 -40
- langroid/utils/pandas_utils.py +0 -30
- langroid/utils/pydantic_utils.py +0 -602
- langroid/utils/system.py +0 -286
- langroid/utils/types.py +0 -93
- langroid/utils/web/__init__.py +0 -0
- langroid/utils/web/login.py +0 -83
- langroid/vector_store/__init__.py +0 -50
- langroid/vector_store/base.py +0 -357
- langroid/vector_store/chromadb.py +0 -214
- langroid/vector_store/lancedb.py +0 -401
- langroid/vector_store/meilisearch.py +0 -299
- langroid/vector_store/momento.py +0 -278
- langroid/vector_store/qdrant_cloud.py +0 -6
- langroid/vector_store/qdrantdb.py +0 -468
- langroid-0.31.2.dist-info/RECORD +0 -162
- {langroid-0.31.2.dist-info → langroid-0.33.3.dist-info/licenses}/LICENSE +0 -0
@@ -1,598 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Callbacks for Chainlit integration.
|
3
|
-
"""
|
4
|
-
|
5
|
-
import json
|
6
|
-
import logging
|
7
|
-
import textwrap
|
8
|
-
from typing import Any, Callable, Dict, List, Literal, Optional, no_type_check
|
9
|
-
|
10
|
-
from langroid.exceptions import LangroidImportError
|
11
|
-
from langroid.pydantic_v1 import BaseSettings
|
12
|
-
|
13
|
-
try:
|
14
|
-
import chainlit as cl
|
15
|
-
except ImportError:
|
16
|
-
raise LangroidImportError("chainlit", "chainlit")
|
17
|
-
|
18
|
-
from chainlit import run_sync
|
19
|
-
from chainlit.logger import logger
|
20
|
-
|
21
|
-
import langroid as lr
|
22
|
-
import langroid.language_models as lm
|
23
|
-
from langroid.utils.configuration import settings
|
24
|
-
from langroid.utils.constants import NO_ANSWER
|
25
|
-
|
26
|
-
# Attempt to reconfigure the root logger to your desired settings
|
27
|
-
log_level = logging.INFO if settings.debug else logging.WARNING
|
28
|
-
logger.setLevel(log_level)
|
29
|
-
logging.basicConfig(level=log_level)
|
30
|
-
|
31
|
-
logging.getLogger().setLevel(log_level)
|
32
|
-
|
33
|
-
USER_TIMEOUT = 60_000
|
34
|
-
SYSTEM = "System 🖥️"
|
35
|
-
LLM = "LLM 🧠"
|
36
|
-
AGENT = "Agent <>"
|
37
|
-
YOU = "You 😃"
|
38
|
-
ERROR = "Error 🚫"
|
39
|
-
|
40
|
-
|
41
|
-
@no_type_check
|
42
|
-
async def ask_helper(func, **kwargs):
|
43
|
-
res = await func(**kwargs).send()
|
44
|
-
while not res:
|
45
|
-
res = await func(**kwargs).send()
|
46
|
-
return res
|
47
|
-
|
48
|
-
|
49
|
-
@no_type_check
|
50
|
-
async def setup_llm() -> None:
|
51
|
-
"""From the session `llm_settings`, create new LLMConfig and LLM objects,
|
52
|
-
save them in session state."""
|
53
|
-
llm_settings = cl.user_session.get("llm_settings", {})
|
54
|
-
model = llm_settings.get("chat_model")
|
55
|
-
context_length = llm_settings.get("context_length", 16_000)
|
56
|
-
temperature = llm_settings.get("temperature", 0.2)
|
57
|
-
timeout = llm_settings.get("timeout", 90)
|
58
|
-
logger.info(f"Using model: {model}")
|
59
|
-
llm_config = lm.OpenAIGPTConfig(
|
60
|
-
chat_model=model or lm.OpenAIChatModel.GPT4o,
|
61
|
-
# or, other possibilities for example:
|
62
|
-
# "litellm/ollama_chat/mistral"
|
63
|
-
# "litellm/ollama_chat/mistral:7b-instruct-v0.2-q8_0"
|
64
|
-
# "litellm/ollama/llama2"
|
65
|
-
# "local/localhost:8000/v1"
|
66
|
-
# "local/localhost:8000"
|
67
|
-
chat_context_length=context_length, # adjust based on model
|
68
|
-
temperature=temperature,
|
69
|
-
timeout=timeout,
|
70
|
-
)
|
71
|
-
llm = lm.OpenAIGPT(llm_config)
|
72
|
-
cl.user_session.set("llm_config", llm_config)
|
73
|
-
cl.user_session.set("llm", llm)
|
74
|
-
|
75
|
-
|
76
|
-
@no_type_check
|
77
|
-
async def update_llm(new_settings: Dict[str, Any]) -> None:
|
78
|
-
"""Update LLMConfig and LLM from settings, and save in session state."""
|
79
|
-
cl.user_session.set("llm_settings", new_settings)
|
80
|
-
await inform_llm_settings()
|
81
|
-
await setup_llm()
|
82
|
-
|
83
|
-
|
84
|
-
async def make_llm_settings_widgets(
|
85
|
-
config: lm.OpenAIGPTConfig | None = None,
|
86
|
-
) -> None:
|
87
|
-
config = config or lm.OpenAIGPTConfig()
|
88
|
-
await cl.ChatSettings(
|
89
|
-
[
|
90
|
-
cl.input_widget.TextInput(
|
91
|
-
id="chat_model",
|
92
|
-
label="Model Name (Default GPT-4o)",
|
93
|
-
initial="",
|
94
|
-
placeholder="E.g. ollama/mistral or " "local/localhost:8000/v1",
|
95
|
-
),
|
96
|
-
cl.input_widget.NumberInput(
|
97
|
-
id="context_length",
|
98
|
-
label="Chat Context Length",
|
99
|
-
initial=config.chat_context_length,
|
100
|
-
placeholder="E.g. 16000",
|
101
|
-
),
|
102
|
-
cl.input_widget.Slider(
|
103
|
-
id="temperature",
|
104
|
-
label="LLM temperature",
|
105
|
-
min=0.0,
|
106
|
-
max=1.0,
|
107
|
-
step=0.1,
|
108
|
-
initial=config.temperature,
|
109
|
-
tooltip="Adjust based on model",
|
110
|
-
),
|
111
|
-
cl.input_widget.Slider(
|
112
|
-
id="timeout",
|
113
|
-
label="Timeout (seconds)",
|
114
|
-
min=10,
|
115
|
-
max=200,
|
116
|
-
step=10,
|
117
|
-
initial=config.timeout,
|
118
|
-
tooltip="Timeout for LLM response, in seconds.",
|
119
|
-
),
|
120
|
-
]
|
121
|
-
).send() # type: ignore
|
122
|
-
|
123
|
-
|
124
|
-
@no_type_check
|
125
|
-
async def inform_llm_settings() -> None:
|
126
|
-
llm_settings: Dict[str, Any] = cl.user_session.get("llm_settings", {})
|
127
|
-
settings_dict = dict(
|
128
|
-
model=llm_settings.get("chat_model"),
|
129
|
-
context_length=llm_settings.get("context_length"),
|
130
|
-
temperature=llm_settings.get("temperature"),
|
131
|
-
timeout=llm_settings.get("timeout"),
|
132
|
-
)
|
133
|
-
await cl.Message(
|
134
|
-
author=SYSTEM,
|
135
|
-
content="LLM settings updated",
|
136
|
-
elements=[
|
137
|
-
cl.Text(
|
138
|
-
name="settings",
|
139
|
-
display="side",
|
140
|
-
content=json.dumps(settings_dict, indent=4),
|
141
|
-
language="json",
|
142
|
-
)
|
143
|
-
],
|
144
|
-
).send()
|
145
|
-
|
146
|
-
|
147
|
-
async def add_instructions(
|
148
|
-
title: str = "Instructions",
|
149
|
-
content: str = "Enter your question/response in the dialog box below.",
|
150
|
-
display: Literal["side", "inline", "page"] = "inline",
|
151
|
-
) -> None:
|
152
|
-
await cl.Message(
|
153
|
-
author="",
|
154
|
-
content=title if display == "side" else "",
|
155
|
-
elements=[
|
156
|
-
cl.Text(
|
157
|
-
name=title,
|
158
|
-
content=content,
|
159
|
-
display=display,
|
160
|
-
)
|
161
|
-
],
|
162
|
-
).send()
|
163
|
-
|
164
|
-
|
165
|
-
async def add_image(
|
166
|
-
path: str,
|
167
|
-
name: str,
|
168
|
-
display: Literal["side", "inline", "page"] = "inline",
|
169
|
-
) -> None:
|
170
|
-
await cl.Message(
|
171
|
-
author="",
|
172
|
-
content=name if display == "side" else "",
|
173
|
-
elements=[
|
174
|
-
cl.Image(
|
175
|
-
name=name,
|
176
|
-
path=path,
|
177
|
-
display=display,
|
178
|
-
)
|
179
|
-
],
|
180
|
-
).send()
|
181
|
-
|
182
|
-
|
183
|
-
async def get_text_files(
|
184
|
-
message: cl.Message,
|
185
|
-
extensions: List[str] = [".txt", ".pdf", ".doc", ".docx"],
|
186
|
-
) -> Dict[str, str]:
|
187
|
-
"""Get dict (file_name -> file_path) from files uploaded in chat msg"""
|
188
|
-
|
189
|
-
files = [file for file in message.elements if file.path.endswith(tuple(extensions))]
|
190
|
-
return {file.name: file.path for file in files}
|
191
|
-
|
192
|
-
|
193
|
-
def wrap_text_preserving_structure(text: str, width: int = 90) -> str:
|
194
|
-
"""Wrap text preserving paragraph breaks. Typically used to
|
195
|
-
format an agent_response output, which may have long lines
|
196
|
-
with no newlines or paragraph breaks."""
|
197
|
-
|
198
|
-
paragraphs = text.split("\n\n") # Split the text into paragraphs
|
199
|
-
wrapped_text = []
|
200
|
-
|
201
|
-
for para in paragraphs:
|
202
|
-
if para.strip(): # If the paragraph is not just whitespace
|
203
|
-
# Wrap this paragraph and add it to the result
|
204
|
-
wrapped_paragraph = textwrap.fill(para, width=width)
|
205
|
-
wrapped_text.append(wrapped_paragraph)
|
206
|
-
else:
|
207
|
-
# Preserve paragraph breaks
|
208
|
-
wrapped_text.append("")
|
209
|
-
|
210
|
-
return "\n\n".join(wrapped_text)
|
211
|
-
|
212
|
-
|
213
|
-
class ChainlitCallbackConfig(BaseSettings):
|
214
|
-
user_has_agent_name: bool = True # show agent name in front of "YOU" ?
|
215
|
-
show_subtask_response: bool = True # show sub-task response as a step?
|
216
|
-
|
217
|
-
|
218
|
-
class ChainlitAgentCallbacks:
|
219
|
-
"""Inject Chainlit callbacks into a Langroid Agent"""
|
220
|
-
|
221
|
-
last_step: Optional[cl.Step] = None # used to display sub-steps under this
|
222
|
-
curr_step: Optional[cl.Step] = None # used to update an initiated step
|
223
|
-
stream: Optional[cl.Step] = None # pushed into openai_gpt.py to stream tokens
|
224
|
-
parent_agent: Optional[lr.Agent] = None # used to get parent id, for step nesting
|
225
|
-
|
226
|
-
def __init__(
|
227
|
-
self,
|
228
|
-
agent: lr.Agent,
|
229
|
-
config: ChainlitCallbackConfig = ChainlitCallbackConfig(),
|
230
|
-
):
|
231
|
-
"""Add callbacks to the agent, and save the initial message,
|
232
|
-
so we can alter the display of the first user message.
|
233
|
-
"""
|
234
|
-
agent.callbacks.start_llm_stream = self.start_llm_stream
|
235
|
-
agent.callbacks.start_llm_stream_async = self.start_llm_stream_async
|
236
|
-
agent.callbacks.cancel_llm_stream = self.cancel_llm_stream
|
237
|
-
agent.callbacks.finish_llm_stream = self.finish_llm_stream
|
238
|
-
agent.callbacks.show_llm_response = self.show_llm_response
|
239
|
-
agent.callbacks.show_agent_response = self.show_agent_response
|
240
|
-
agent.callbacks.get_user_response = self.get_user_response
|
241
|
-
agent.callbacks.get_user_response_async = self.get_user_response_async
|
242
|
-
agent.callbacks.get_last_step = self.get_last_step
|
243
|
-
agent.callbacks.set_parent_agent = self.set_parent_agent
|
244
|
-
agent.callbacks.show_error_message = self.show_error_message
|
245
|
-
agent.callbacks.show_start_response = self.show_start_response
|
246
|
-
self.config = config
|
247
|
-
self.agent: lr.Agent = agent
|
248
|
-
if self.agent.llm is not None:
|
249
|
-
# We don't want to suppress LLM output in async + streaming,
|
250
|
-
# since we often use chainlit async callbacks to display LLM output
|
251
|
-
self.agent.llm.config.async_stream_quiet = False
|
252
|
-
|
253
|
-
def _get_parent_id(self) -> str | None:
|
254
|
-
"""Get step id under which we need to nest the current step:
|
255
|
-
This should be the parent Agent's last_step.
|
256
|
-
"""
|
257
|
-
if self.parent_agent is None:
|
258
|
-
logger.info(f"No parent agent found for {self.agent.config.name}")
|
259
|
-
return None
|
260
|
-
logger.info(
|
261
|
-
f"Parent agent found for {self.agent.config.name} = "
|
262
|
-
f"{self.parent_agent.config.name}"
|
263
|
-
)
|
264
|
-
last_step = self.parent_agent.callbacks.get_last_step()
|
265
|
-
if last_step is None:
|
266
|
-
logger.info(f"No last step found for {self.parent_agent.config.name}")
|
267
|
-
return None
|
268
|
-
logger.info(
|
269
|
-
f"Last step found for {self.parent_agent.config.name} = {last_step.id}"
|
270
|
-
)
|
271
|
-
return last_step.id # type: ignore
|
272
|
-
|
273
|
-
def set_parent_agent(self, parent: lr.Agent) -> None:
|
274
|
-
self.parent_agent = parent
|
275
|
-
|
276
|
-
def get_last_step(self) -> Optional[cl.Step]:
|
277
|
-
return self.last_step
|
278
|
-
|
279
|
-
def start_llm_stream(self) -> Callable[[str], None]:
|
280
|
-
"""Returns a streaming fn that can be passed to the LLM class"""
|
281
|
-
self.stream = cl.Message(
|
282
|
-
content="",
|
283
|
-
id=self.curr_step.id if self.curr_step is not None else None,
|
284
|
-
author=self._entity_name("llm"),
|
285
|
-
type="assistant_message",
|
286
|
-
parent_id=self._get_parent_id(),
|
287
|
-
)
|
288
|
-
self.last_step = self.stream
|
289
|
-
self.curr_step = None
|
290
|
-
logger.info(
|
291
|
-
f"""
|
292
|
-
Starting LLM stream for {self.agent.config.name}
|
293
|
-
id = {self.stream.id}
|
294
|
-
under parent {self._get_parent_id()}
|
295
|
-
"""
|
296
|
-
)
|
297
|
-
|
298
|
-
def stream_token(t: str) -> None:
|
299
|
-
if self.stream is None:
|
300
|
-
raise ValueError("Stream not initialized")
|
301
|
-
run_sync(self.stream.stream_token(t))
|
302
|
-
|
303
|
-
return stream_token
|
304
|
-
|
305
|
-
async def start_llm_stream_async(self) -> Callable[[str], None]:
|
306
|
-
"""Returns a streaming fn that can be passed to the LLM class"""
|
307
|
-
self.stream = cl.Message(
|
308
|
-
content="",
|
309
|
-
id=self.curr_step.id if self.curr_step is not None else None,
|
310
|
-
author=self._entity_name("llm"),
|
311
|
-
type="assistant_message",
|
312
|
-
parent_id=self._get_parent_id(),
|
313
|
-
)
|
314
|
-
self.last_step = self.stream
|
315
|
-
self.curr_step = None
|
316
|
-
logger.info(
|
317
|
-
f"""
|
318
|
-
Starting LLM stream for {self.agent.config.name}
|
319
|
-
id = {self.stream.id}
|
320
|
-
under parent {self._get_parent_id()}
|
321
|
-
"""
|
322
|
-
)
|
323
|
-
|
324
|
-
async def stream_token(t: str) -> None:
|
325
|
-
if self.stream is None:
|
326
|
-
raise ValueError("Stream not initialized")
|
327
|
-
await self.stream.stream_token(t)
|
328
|
-
|
329
|
-
return stream_token
|
330
|
-
|
331
|
-
def cancel_llm_stream(self) -> None:
|
332
|
-
"""Called when cached response found."""
|
333
|
-
self.last_step = None
|
334
|
-
if self.stream is not None:
|
335
|
-
run_sync(self.stream.remove()) # type: ignore
|
336
|
-
|
337
|
-
def finish_llm_stream(self, content: str, is_tool: bool = False) -> None:
|
338
|
-
"""Update the stream, and display entire response in the right language."""
|
339
|
-
if self.agent.llm is None or self.stream is None:
|
340
|
-
raise ValueError("LLM or stream not initialized")
|
341
|
-
if content == "":
|
342
|
-
run_sync(self.stream.remove()) # type: ignore
|
343
|
-
else:
|
344
|
-
run_sync(self.stream.update()) # type: ignore
|
345
|
-
stream_id = self.stream.id if content else None
|
346
|
-
step = cl.Message(
|
347
|
-
content=textwrap.dedent(content) or NO_ANSWER,
|
348
|
-
id=stream_id,
|
349
|
-
author=self._entity_name("llm", tool=is_tool),
|
350
|
-
type="assistant_message",
|
351
|
-
parent_id=self._get_parent_id(),
|
352
|
-
language="json" if is_tool else None,
|
353
|
-
)
|
354
|
-
logger.info(
|
355
|
-
f"""
|
356
|
-
Finish STREAM LLM response for {self.agent.config.name}
|
357
|
-
id = {step.id}
|
358
|
-
under parent {self._get_parent_id()}
|
359
|
-
"""
|
360
|
-
)
|
361
|
-
run_sync(step.update()) # type: ignore
|
362
|
-
|
363
|
-
def show_llm_response(
|
364
|
-
self,
|
365
|
-
content: str,
|
366
|
-
is_tool: bool = False,
|
367
|
-
cached: bool = False,
|
368
|
-
language: str | None = None,
|
369
|
-
) -> None:
|
370
|
-
"""Show non-streaming LLM response."""
|
371
|
-
step = cl.Message(
|
372
|
-
content=textwrap.dedent(content) or NO_ANSWER,
|
373
|
-
id=self.curr_step.id if self.curr_step is not None else None,
|
374
|
-
author=self._entity_name("llm", tool=is_tool, cached=cached),
|
375
|
-
type="assistant_message",
|
376
|
-
language=language or ("json" if is_tool else None),
|
377
|
-
parent_id=self._get_parent_id(),
|
378
|
-
)
|
379
|
-
self.last_step = step
|
380
|
-
self.curr_step = None
|
381
|
-
logger.info(
|
382
|
-
f"""
|
383
|
-
Showing NON-STREAM LLM response for {self.agent.config.name}
|
384
|
-
id = {step.id}
|
385
|
-
under parent {self._get_parent_id()}
|
386
|
-
"""
|
387
|
-
)
|
388
|
-
run_sync(step.send()) # type: ignore
|
389
|
-
|
390
|
-
def show_error_message(self, error: str) -> None:
|
391
|
-
"""Show error message."""
|
392
|
-
step = cl.Message(
|
393
|
-
content=error,
|
394
|
-
author=self.agent.config.name + f"({ERROR})",
|
395
|
-
type="run",
|
396
|
-
language="text",
|
397
|
-
parent_id=self._get_parent_id(),
|
398
|
-
)
|
399
|
-
self.last_step = step
|
400
|
-
run_sync(step.send())
|
401
|
-
|
402
|
-
def show_agent_response(self, content: str, language="text") -> None:
|
403
|
-
"""Show message from agent (typically tool handler)."""
|
404
|
-
if language == "text":
|
405
|
-
content = wrap_text_preserving_structure(content, width=90)
|
406
|
-
step = cl.Message(
|
407
|
-
content=content,
|
408
|
-
id=self.curr_step.id if self.curr_step is not None else None,
|
409
|
-
author=self._entity_name("agent"),
|
410
|
-
type="tool",
|
411
|
-
language=language,
|
412
|
-
parent_id=self._get_parent_id(),
|
413
|
-
)
|
414
|
-
self.last_step = step
|
415
|
-
self.curr_step = None
|
416
|
-
logger.info(
|
417
|
-
f"""
|
418
|
-
Showing AGENT response for {self.agent.config.name}
|
419
|
-
id = {step.id}
|
420
|
-
under parent {self._get_parent_id()}
|
421
|
-
"""
|
422
|
-
)
|
423
|
-
run_sync(step.send()) # type: ignore
|
424
|
-
|
425
|
-
def show_start_response(self, entity: str) -> None:
|
426
|
-
"""When there's a potentially long-running process, start a step,
|
427
|
-
so that the UI displays a spinner while the process is running."""
|
428
|
-
if self.curr_step is not None:
|
429
|
-
run_sync(self.curr_step.remove()) # type: ignore
|
430
|
-
step = cl.Message(
|
431
|
-
content="",
|
432
|
-
author=self._entity_name(entity),
|
433
|
-
type="run",
|
434
|
-
parent_id=self._get_parent_id(),
|
435
|
-
language="text",
|
436
|
-
)
|
437
|
-
self.last_step = step
|
438
|
-
self.curr_step = step
|
439
|
-
logger.info(
|
440
|
-
f"""
|
441
|
-
Showing START response for {self.agent.config.name} ({entity})
|
442
|
-
id = {step.id}
|
443
|
-
under parent {self._get_parent_id()}
|
444
|
-
"""
|
445
|
-
)
|
446
|
-
run_sync(step.send()) # type: ignore
|
447
|
-
|
448
|
-
def _entity_name(
|
449
|
-
self, entity: str, tool: bool = False, cached: bool = False
|
450
|
-
) -> str:
|
451
|
-
"""Construct name of entity to display as Author of a step"""
|
452
|
-
tool_indicator = " => 🛠️" if tool else ""
|
453
|
-
cached = "(cached)" if cached else ""
|
454
|
-
match entity:
|
455
|
-
case "llm":
|
456
|
-
model = self.agent.config.llm.chat_model
|
457
|
-
return (
|
458
|
-
self.agent.config.name + f"({LLM} {model} {tool_indicator}){cached}"
|
459
|
-
)
|
460
|
-
case "agent":
|
461
|
-
return self.agent.config.name + f"({AGENT})"
|
462
|
-
case "user":
|
463
|
-
if self.config.user_has_agent_name:
|
464
|
-
return self.agent.config.name + f"({YOU})"
|
465
|
-
else:
|
466
|
-
return YOU
|
467
|
-
case _:
|
468
|
-
return self.agent.config.name + f"({entity})"
|
469
|
-
|
470
|
-
def _get_user_response_buttons(self, prompt: str) -> str:
|
471
|
-
"""Not used. Save for future reference"""
|
472
|
-
res = run_sync(
|
473
|
-
ask_helper(
|
474
|
-
cl.AskActionMessage,
|
475
|
-
content="Continue, exit or say something?",
|
476
|
-
actions=[
|
477
|
-
cl.Action(
|
478
|
-
name="continue",
|
479
|
-
value="continue",
|
480
|
-
label="✅ Continue",
|
481
|
-
),
|
482
|
-
cl.Action(
|
483
|
-
name="feedback",
|
484
|
-
value="feedback",
|
485
|
-
label="💬 Say something",
|
486
|
-
),
|
487
|
-
cl.Action(name="exit", value="exit", label="🔚 Exit Conversation"),
|
488
|
-
],
|
489
|
-
)
|
490
|
-
)
|
491
|
-
if res.get("value") == "continue":
|
492
|
-
return ""
|
493
|
-
if res.get("value") == "exit":
|
494
|
-
return "x"
|
495
|
-
if res.get("value") == "feedback":
|
496
|
-
return self.get_user_response(prompt)
|
497
|
-
return "" # process the "feedback" case here
|
498
|
-
|
499
|
-
def get_user_response(self, prompt: str) -> str:
|
500
|
-
"""Ask for user response, wait for it, and return it"""
|
501
|
-
|
502
|
-
return run_sync(self.ask_user(prompt=prompt, suppress_values=["c"]))
|
503
|
-
|
504
|
-
async def get_user_response_async(self, prompt: str) -> str:
|
505
|
-
"""Ask for user response, wait for it, and return it"""
|
506
|
-
|
507
|
-
return await self.ask_user(prompt=prompt, suppress_values=["c"])
|
508
|
-
|
509
|
-
async def ask_user(
|
510
|
-
self,
|
511
|
-
prompt: str,
|
512
|
-
timeout: int = USER_TIMEOUT,
|
513
|
-
suppress_values: List[str] = ["c"],
|
514
|
-
) -> str:
|
515
|
-
"""
|
516
|
-
Ask user for input.
|
517
|
-
|
518
|
-
Args:
|
519
|
-
prompt (str): Prompt to display to user
|
520
|
-
timeout (int): Timeout in seconds
|
521
|
-
suppress_values (List[str]): List of values to suppress from display
|
522
|
-
(e.g. "c" for continue)
|
523
|
-
|
524
|
-
Returns:
|
525
|
-
str: User response
|
526
|
-
"""
|
527
|
-
ask_msg = cl.AskUserMessage(
|
528
|
-
content=prompt,
|
529
|
-
author=f"{self.agent.config.name}(Awaiting user input...)",
|
530
|
-
type="assistant_message",
|
531
|
-
timeout=timeout,
|
532
|
-
)
|
533
|
-
res = await ask_msg.send()
|
534
|
-
if prompt == "":
|
535
|
-
# if there was no actual prompt, clear the row from the UI for clarity.
|
536
|
-
await ask_msg.remove()
|
537
|
-
|
538
|
-
if res is None:
|
539
|
-
run_sync(
|
540
|
-
cl.Message(
|
541
|
-
content=f"Timed out after {USER_TIMEOUT} seconds. Exiting."
|
542
|
-
).send()
|
543
|
-
)
|
544
|
-
return "x"
|
545
|
-
|
546
|
-
# Finally, reproduce the user response at right nesting level
|
547
|
-
if res["output"] in suppress_values:
|
548
|
-
return ""
|
549
|
-
|
550
|
-
return res["output"]
|
551
|
-
|
552
|
-
|
553
|
-
class ChainlitTaskCallbacks(ChainlitAgentCallbacks):
|
554
|
-
"""
|
555
|
-
Recursively inject ChainlitAgentCallbacks into a Langroid Task's agent and
|
556
|
-
agents of sub-tasks.
|
557
|
-
"""
|
558
|
-
|
559
|
-
def __init__(
|
560
|
-
self,
|
561
|
-
task: lr.Task,
|
562
|
-
config: ChainlitCallbackConfig = ChainlitCallbackConfig(),
|
563
|
-
):
|
564
|
-
"""Inject callbacks recursively, ensuring msg is passed to the
|
565
|
-
top-level agent"""
|
566
|
-
|
567
|
-
super().__init__(task.agent, config)
|
568
|
-
self._inject_callbacks(task)
|
569
|
-
self.task = task
|
570
|
-
if config.show_subtask_response:
|
571
|
-
self.task.callbacks.show_subtask_response = self.show_subtask_response
|
572
|
-
|
573
|
-
@classmethod
|
574
|
-
def _inject_callbacks(
|
575
|
-
cls, task: lr.Task, config: ChainlitCallbackConfig = ChainlitCallbackConfig()
|
576
|
-
) -> None:
|
577
|
-
# recursively apply ChainlitAgentCallbacks to agents of sub-tasks
|
578
|
-
for t in task.sub_tasks:
|
579
|
-
cls(t, config=config)
|
580
|
-
# ChainlitTaskCallbacks(t, config=config)
|
581
|
-
|
582
|
-
def show_subtask_response(
|
583
|
-
self, task: lr.Task, content: str, is_tool: bool = False
|
584
|
-
) -> None:
|
585
|
-
"""Show sub-task response as a step, nested at the right level."""
|
586
|
-
|
587
|
-
# The step should nest under the calling agent's last step
|
588
|
-
step = cl.Message(
|
589
|
-
content=content or NO_ANSWER,
|
590
|
-
author=(
|
591
|
-
self.task.agent.config.name + f"( ⏎ From {task.agent.config.name})"
|
592
|
-
),
|
593
|
-
type="run",
|
594
|
-
parent_id=self._get_parent_id(),
|
595
|
-
language="json" if is_tool else None,
|
596
|
-
)
|
597
|
-
self.last_step = step
|
598
|
-
run_sync(step.send())
|