ag2 0.9.2__py3-none-any.whl → 0.9.4__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.
Potentially problematic release.
This version of ag2 might be problematic. Click here for more details.
- {ag2-0.9.2.dist-info → ag2-0.9.4.dist-info}/METADATA +14 -10
- {ag2-0.9.2.dist-info → ag2-0.9.4.dist-info}/RECORD +35 -29
- autogen/agentchat/contrib/agent_optimizer.py +6 -3
- autogen/agentchat/contrib/capabilities/transforms.py +22 -9
- autogen/agentchat/conversable_agent.py +51 -5
- autogen/agentchat/group/group_utils.py +81 -27
- autogen/agentchat/group/guardrails.py +171 -0
- autogen/agentchat/group/handoffs.py +81 -5
- autogen/agentchat/group/on_context_condition.py +2 -2
- autogen/agentchat/group/patterns/pattern.py +7 -1
- autogen/agentchat/group/targets/transition_target.py +10 -0
- autogen/agentchat/groupchat.py +95 -8
- autogen/agentchat/realtime/experimental/realtime_swarm.py +12 -4
- autogen/agents/experimental/document_agent/document_agent.py +232 -40
- autogen/agents/experimental/websurfer/websurfer.py +9 -1
- autogen/events/agent_events.py +6 -0
- autogen/events/helpers.py +8 -0
- autogen/mcp/helpers.py +45 -0
- autogen/mcp/mcp_proxy/mcp_proxy.py +2 -3
- autogen/messages/agent_messages.py +1 -1
- autogen/oai/gemini.py +41 -17
- autogen/oai/gemini_types.py +2 -1
- autogen/oai/oai_models/chat_completion.py +1 -1
- autogen/tools/experimental/__init__.py +4 -0
- autogen/tools/experimental/browser_use/browser_use.py +4 -11
- autogen/tools/experimental/firecrawl/__init__.py +7 -0
- autogen/tools/experimental/firecrawl/firecrawl_tool.py +853 -0
- autogen/tools/experimental/searxng/__init__.py +7 -0
- autogen/tools/experimental/searxng/searxng_search.py +141 -0
- autogen/version.py +1 -1
- templates/client_template/main.jinja2 +5 -2
- templates/main.jinja2 +1 -1
- {ag2-0.9.2.dist-info → ag2-0.9.4.dist-info}/WHEEL +0 -0
- {ag2-0.9.2.dist-info → ag2-0.9.4.dist-info}/licenses/LICENSE +0 -0
- {ag2-0.9.2.dist-info → ag2-0.9.4.dist-info}/licenses/NOTICE.md +0 -0
|
@@ -104,7 +104,7 @@ def parse_oai_message(message: Union[dict[str, Any], str], role: str, adressee:
|
|
|
104
104
|
return oai_message
|
|
105
105
|
|
|
106
106
|
|
|
107
|
-
class SwarmableAgent:
|
|
107
|
+
class SwarmableAgent(Agent):
|
|
108
108
|
"""A class for an agent that can participate in a swarm chat."""
|
|
109
109
|
|
|
110
110
|
def __init__(
|
|
@@ -239,7 +239,7 @@ class SwarmableAgent:
|
|
|
239
239
|
sender: Optional["Agent"] = None,
|
|
240
240
|
**kwargs: Any,
|
|
241
241
|
) -> Union[str, dict[str, Any], None]:
|
|
242
|
-
|
|
242
|
+
return self.generate_reply(messages=messages, sender=sender, **kwargs)
|
|
243
243
|
|
|
244
244
|
async def a_receive(
|
|
245
245
|
self,
|
|
@@ -247,7 +247,7 @@ class SwarmableAgent:
|
|
|
247
247
|
sender: "Agent",
|
|
248
248
|
request_reply: Optional[bool] = None,
|
|
249
249
|
) -> None:
|
|
250
|
-
|
|
250
|
+
self.receive(message, sender, request_reply)
|
|
251
251
|
|
|
252
252
|
async def a_send(
|
|
253
253
|
self,
|
|
@@ -255,7 +255,7 @@ class SwarmableAgent:
|
|
|
255
255
|
recipient: "Agent",
|
|
256
256
|
request_reply: Optional[bool] = None,
|
|
257
257
|
) -> None:
|
|
258
|
-
|
|
258
|
+
self.send(message, recipient, request_reply)
|
|
259
259
|
|
|
260
260
|
@property
|
|
261
261
|
def chat_messages(self) -> dict[Agent, list[dict[str, Any]]]:
|
|
@@ -293,6 +293,14 @@ class SwarmableAgent:
|
|
|
293
293
|
def _raise_exception_on_async_reply_functions(self) -> None:
|
|
294
294
|
pass
|
|
295
295
|
|
|
296
|
+
def set_ui_tools(self, tools: Optional[list] = None) -> None:
|
|
297
|
+
"""Set UI tools for the agent."""
|
|
298
|
+
pass
|
|
299
|
+
|
|
300
|
+
def unset_ui_tools(self) -> None:
|
|
301
|
+
"""Unset UI tools for the agent."""
|
|
302
|
+
pass
|
|
303
|
+
|
|
296
304
|
@staticmethod
|
|
297
305
|
def _last_msg_as_summary(sender: Agent, recipient: Agent, summary_args: Optional[dict[str, Any]]) -> str:
|
|
298
306
|
"""Get a chat summary from the last message of the recipient."""
|
|
@@ -42,7 +42,8 @@ TASK_MANAGER_NAME = "TaskManagerAgent"
|
|
|
42
42
|
TASK_MANAGER_SYSTEM_MESSAGE = """
|
|
43
43
|
You are a task manager agent. You have 2 priorities:
|
|
44
44
|
1. You initiate the tasks which updates the context variables based on the task decisions (DocumentTask) from the DocumentTriageAgent.
|
|
45
|
-
|
|
45
|
+
ALWAYS call initiate_tasks first when you receive a message from the DocumentTriageAgent, even if you think there are no new tasks.
|
|
46
|
+
This ensures that any new ingestions or queries from the triage agent are properly recorded.
|
|
46
47
|
Put all ingestion and query tasks into the one tool call.
|
|
47
48
|
i.e. output
|
|
48
49
|
{
|
|
@@ -75,7 +76,7 @@ TASK_MANAGER_SYSTEM_MESSAGE = """
|
|
|
75
76
|
Transfer to the summary agent if all ingestion and query tasks are done.
|
|
76
77
|
"""
|
|
77
78
|
|
|
78
|
-
|
|
79
|
+
DEFAULT_ERROR_GROUP_CHAT_MESSAGE: str = """
|
|
79
80
|
Document Agent failed to perform task.
|
|
80
81
|
"""
|
|
81
82
|
|
|
@@ -147,7 +148,7 @@ class DocAgent(ConversableAgent):
|
|
|
147
148
|
"""
|
|
148
149
|
The DocAgent is responsible for ingest and querying documents.
|
|
149
150
|
|
|
150
|
-
Internally, it generates a group of
|
|
151
|
+
Internally, it generates a group chat with a set of agents to ingest, query, and summarize.
|
|
151
152
|
"""
|
|
152
153
|
|
|
153
154
|
def __init__(
|
|
@@ -196,7 +197,7 @@ class DocAgent(ConversableAgent):
|
|
|
196
197
|
llm_config=llm_config,
|
|
197
198
|
human_input_mode="NEVER",
|
|
198
199
|
)
|
|
199
|
-
self.register_reply([ConversableAgent, None], self.
|
|
200
|
+
self.register_reply([ConversableAgent, None], self.generate_inner_group_chat_reply, position=0)
|
|
200
201
|
|
|
201
202
|
self.context_variables: ContextVariables = ContextVariables(
|
|
202
203
|
data={
|
|
@@ -210,7 +211,15 @@ class DocAgent(ConversableAgent):
|
|
|
210
211
|
self._triage_agent = DocumentTriageAgent(llm_config=llm_config)
|
|
211
212
|
|
|
212
213
|
def create_error_agent_prompt(agent: ConversableAgent, messages: list[dict[str, Any]]) -> str:
|
|
213
|
-
"""Create the error agent prompt, primarily used to update ingested documents for ending
|
|
214
|
+
"""Create the error agent prompt, primarily used to update ingested documents for ending.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
agent: The conversable agent requesting the prompt
|
|
218
|
+
messages: List of conversation messages
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
str: The error manager system message
|
|
222
|
+
"""
|
|
214
223
|
update_ingested_documents()
|
|
215
224
|
|
|
216
225
|
return ERROR_MANAGER_SYSTEM_MESSAGE
|
|
@@ -223,7 +232,11 @@ class DocAgent(ConversableAgent):
|
|
|
223
232
|
)
|
|
224
233
|
|
|
225
234
|
def update_ingested_documents() -> None:
|
|
226
|
-
"""Updates the list of ingested documents, persisted so we can keep a list over multiple replies
|
|
235
|
+
"""Updates the list of ingested documents, persisted so we can keep a list over multiple replies.
|
|
236
|
+
|
|
237
|
+
This function updates self.documents_ingested with any new documents that have been ingested
|
|
238
|
+
by the triage agent, ensuring persistence across multiple DocAgent interactions.
|
|
239
|
+
"""
|
|
227
240
|
agent_documents_ingested = self._triage_agent.context_variables.get("DocumentsIngested", [])
|
|
228
241
|
# Update self.documents_ingested with any new documents ingested
|
|
229
242
|
for doc in agent_documents_ingested: # type: ignore[union-attr]
|
|
@@ -234,21 +247,162 @@ class DocAgent(ConversableAgent):
|
|
|
234
247
|
ingestions: Annotated[list[Ingest], Field(description="List of documents, files, and URLs to ingest")]
|
|
235
248
|
queries: Annotated[list[Query], Field(description="List of queries to run")]
|
|
236
249
|
|
|
250
|
+
def _deduplicate_ingestions(
|
|
251
|
+
new_ingestions: list[Ingest], existing_ingestions: list[Ingest], documents_ingested: list[str]
|
|
252
|
+
) -> tuple[list[Ingest], list[str]]:
|
|
253
|
+
"""Deduplicate ingestions against existing pending and already ingested documents.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
new_ingestions: List of new ingestion requests to process
|
|
257
|
+
existing_ingestions: List of ingestions already pending
|
|
258
|
+
documents_ingested: List of document paths already ingested
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
tuple: (new_unique_ingestions, ignored_duplicate_paths)
|
|
262
|
+
"""
|
|
263
|
+
unique_ingestions = []
|
|
264
|
+
ignored_paths = []
|
|
265
|
+
|
|
266
|
+
for ingestion in new_ingestions:
|
|
267
|
+
ingestion_path = ingestion.path_or_url
|
|
268
|
+
# Check if already in pending ingestions
|
|
269
|
+
already_pending = any(existing.path_or_url == ingestion_path for existing in existing_ingestions)
|
|
270
|
+
# Check if already ingested
|
|
271
|
+
already_ingested = ingestion_path in documents_ingested
|
|
272
|
+
|
|
273
|
+
if already_pending or already_ingested:
|
|
274
|
+
ignored_paths.append(ingestion_path)
|
|
275
|
+
else:
|
|
276
|
+
unique_ingestions.append(ingestion)
|
|
277
|
+
|
|
278
|
+
return unique_ingestions, ignored_paths
|
|
279
|
+
|
|
280
|
+
def _deduplicate_queries(
|
|
281
|
+
new_queries: list[Query], existing_queries: list[Query]
|
|
282
|
+
) -> tuple[list[Query], list[str]]:
|
|
283
|
+
"""Deduplicate queries against existing pending queries.
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
new_queries: List of new query requests to process
|
|
287
|
+
existing_queries: List of queries already pending
|
|
288
|
+
|
|
289
|
+
Returns:
|
|
290
|
+
tuple: (new_unique_queries, ignored_duplicate_query_texts)
|
|
291
|
+
"""
|
|
292
|
+
unique_queries = []
|
|
293
|
+
ignored_query_texts = []
|
|
294
|
+
|
|
295
|
+
for query in new_queries:
|
|
296
|
+
query_text = query.query
|
|
297
|
+
# Check if query already exists in pending queries
|
|
298
|
+
already_pending = any(existing.query == query_text for existing in existing_queries)
|
|
299
|
+
|
|
300
|
+
if already_pending:
|
|
301
|
+
ignored_query_texts.append(query_text)
|
|
302
|
+
else:
|
|
303
|
+
unique_queries.append(query)
|
|
304
|
+
|
|
305
|
+
return unique_queries, ignored_query_texts
|
|
306
|
+
|
|
307
|
+
def _build_response_message(
|
|
308
|
+
added_ingestions: int, ignored_ingestions: list[str], added_queries: int, ignored_queries: list[str]
|
|
309
|
+
) -> str:
|
|
310
|
+
"""Build a descriptive response message about what was added/ignored.
|
|
311
|
+
|
|
312
|
+
Args:
|
|
313
|
+
added_ingestions: Number of unique ingestions added
|
|
314
|
+
ignored_ingestions: List of duplicate ingestion paths ignored
|
|
315
|
+
added_queries: Number of unique queries added
|
|
316
|
+
ignored_queries: List of duplicate query texts ignored
|
|
317
|
+
|
|
318
|
+
Returns:
|
|
319
|
+
str: Formatted message describing the results
|
|
320
|
+
"""
|
|
321
|
+
messages = []
|
|
322
|
+
|
|
323
|
+
if added_ingestions > 0:
|
|
324
|
+
messages.append(f"Added {added_ingestions} new document(s) for ingestion")
|
|
325
|
+
|
|
326
|
+
if ignored_ingestions:
|
|
327
|
+
messages.append(
|
|
328
|
+
f"Ignored {len(ignored_ingestions)} duplicate document(s): {', '.join(ignored_ingestions)}"
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
if added_queries > 0:
|
|
332
|
+
messages.append(f"Added {added_queries} new query/queries")
|
|
333
|
+
|
|
334
|
+
if ignored_queries:
|
|
335
|
+
messages.append(f"Ignored {len(ignored_queries)} duplicate query/queries: {', '.join(ignored_queries)}")
|
|
336
|
+
|
|
337
|
+
if messages:
|
|
338
|
+
return "; ".join(messages)
|
|
339
|
+
else:
|
|
340
|
+
return "All requested tasks were duplicates and ignored"
|
|
341
|
+
|
|
237
342
|
def initiate_tasks(
|
|
238
343
|
task_init_info: Annotated[TaskInitInfo, "Documents, Files, URLs to ingest and the queries to run"],
|
|
239
344
|
context_variables: Annotated[ContextVariables, "Context variables"],
|
|
240
345
|
) -> ReplyResult:
|
|
241
|
-
"""Add documents to ingest and queries to answer when received.
|
|
346
|
+
"""Add documents to ingest and queries to answer when received.
|
|
347
|
+
|
|
348
|
+
Args:
|
|
349
|
+
task_init_info: Information about documents to ingest and queries to run
|
|
350
|
+
context_variables: The current context variables containing task state
|
|
351
|
+
|
|
352
|
+
Returns:
|
|
353
|
+
ReplyResult: Contains response message, updated context, and target agent
|
|
354
|
+
"""
|
|
242
355
|
ingestions = task_init_info.ingestions
|
|
243
356
|
queries = task_init_info.queries
|
|
244
357
|
|
|
245
358
|
if "TaskInitiated" in context_variables:
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
359
|
+
# Handle follow-up tasks with deduplication
|
|
360
|
+
added_ingestions_count = 0
|
|
361
|
+
ignored_ingestions = []
|
|
362
|
+
added_queries_count = 0
|
|
363
|
+
ignored_queries = []
|
|
364
|
+
|
|
365
|
+
if ingestions:
|
|
366
|
+
existing_ingestions: list[Ingest] = context_variables.get("DocumentsToIngest", []) # type: ignore[assignment]
|
|
367
|
+
documents_ingested: list[str] = context_variables.get("DocumentsIngested", []) # type: ignore[assignment]
|
|
368
|
+
|
|
369
|
+
unique_ingestions, ignored_ingestion_paths = _deduplicate_ingestions(
|
|
370
|
+
ingestions, existing_ingestions, documents_ingested
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
if unique_ingestions:
|
|
374
|
+
context_variables["DocumentsToIngest"] = existing_ingestions + unique_ingestions
|
|
375
|
+
added_ingestions_count = len(unique_ingestions)
|
|
376
|
+
|
|
377
|
+
ignored_ingestions = ignored_ingestion_paths
|
|
378
|
+
|
|
379
|
+
if queries:
|
|
380
|
+
existing_queries: list[Query] = context_variables.get("QueriesToRun", []) # type: ignore[assignment]
|
|
381
|
+
|
|
382
|
+
unique_queries, ignored_query_texts = _deduplicate_queries(queries, existing_queries)
|
|
383
|
+
|
|
384
|
+
if unique_queries:
|
|
385
|
+
context_variables["QueriesToRun"] = existing_queries + unique_queries
|
|
386
|
+
added_queries_count = len(unique_queries)
|
|
387
|
+
|
|
388
|
+
ignored_queries = ignored_query_texts
|
|
389
|
+
|
|
390
|
+
if not ingestions and not queries:
|
|
391
|
+
return ReplyResult(message="No new tasks to initiate", context_variables=context_variables)
|
|
392
|
+
|
|
393
|
+
response_message = _build_response_message(
|
|
394
|
+
added_ingestions_count, ignored_ingestions, added_queries_count, ignored_queries
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
else:
|
|
398
|
+
# First time initialization - no deduplication needed
|
|
399
|
+
context_variables["DocumentsToIngest"] = ingestions
|
|
400
|
+
context_variables["QueriesToRun"] = [query for query in queries]
|
|
401
|
+
context_variables["TaskInitiated"] = True
|
|
402
|
+
response_message = "Updated context variables with task decisions"
|
|
403
|
+
|
|
250
404
|
return ReplyResult(
|
|
251
|
-
message=
|
|
405
|
+
message=response_message,
|
|
252
406
|
context_variables=context_variables,
|
|
253
407
|
target=AgentNameTarget(agent_name=TASK_MANAGER_NAME),
|
|
254
408
|
)
|
|
@@ -271,7 +425,14 @@ class DocAgent(ConversableAgent):
|
|
|
271
425
|
)
|
|
272
426
|
|
|
273
427
|
def execute_rag_query(context_variables: ContextVariables) -> ReplyResult: # type: ignore[type-arg]
|
|
274
|
-
"""Execute outstanding RAG queries, call the tool once for each outstanding query. Call this tool with no arguments.
|
|
428
|
+
"""Execute outstanding RAG queries, call the tool once for each outstanding query. Call this tool with no arguments.
|
|
429
|
+
|
|
430
|
+
Args:
|
|
431
|
+
context_variables: The current context variables containing queries to run
|
|
432
|
+
|
|
433
|
+
Returns:
|
|
434
|
+
ReplyResult: Contains query answer, updated context, and target agent
|
|
435
|
+
"""
|
|
275
436
|
if len(context_variables["QueriesToRun"]) == 0:
|
|
276
437
|
return ReplyResult(
|
|
277
438
|
target=AgentNameTarget(agent_name=TASK_MANAGER_NAME),
|
|
@@ -303,6 +464,9 @@ class DocAgent(ConversableAgent):
|
|
|
303
464
|
context_variables["QueriesToRun"].pop(0)
|
|
304
465
|
context_variables["CompletedTaskCount"] += 1
|
|
305
466
|
context_variables["QueryResults"].append({"query": query, "answer": answer, "citations": txt_citations})
|
|
467
|
+
|
|
468
|
+
# Query completed
|
|
469
|
+
|
|
306
470
|
return ReplyResult(message=answer, context_variables=context_variables)
|
|
307
471
|
except Exception as e:
|
|
308
472
|
return ReplyResult(
|
|
@@ -322,9 +486,17 @@ class DocAgent(ConversableAgent):
|
|
|
322
486
|
functions=[execute_rag_query],
|
|
323
487
|
)
|
|
324
488
|
|
|
325
|
-
# Summary agent prompt will include the results of the ingestions and
|
|
489
|
+
# Summary agent prompt will include the results of the ingestions and queries
|
|
326
490
|
def create_summary_agent_prompt(agent: ConversableAgent, messages: list[dict[str, Any]]) -> str:
|
|
327
|
-
"""Create the summary agent prompt and updates ingested documents
|
|
491
|
+
"""Create the summary agent prompt and updates ingested documents.
|
|
492
|
+
|
|
493
|
+
Args:
|
|
494
|
+
agent: The conversable agent requesting the prompt
|
|
495
|
+
messages: List of conversation messages
|
|
496
|
+
|
|
497
|
+
Returns:
|
|
498
|
+
str: The summary agent system message with context information
|
|
499
|
+
"""
|
|
328
500
|
update_ingested_documents()
|
|
329
501
|
|
|
330
502
|
documents_to_ingest: list[Ingest] = cast(list[Ingest], agent.context_variables.get("DocumentsToIngest", []))
|
|
@@ -368,14 +540,7 @@ class DocAgent(ConversableAgent):
|
|
|
368
540
|
expression=ContextExpression(expression="len(${QueriesToRun}) > 0")
|
|
369
541
|
),
|
|
370
542
|
),
|
|
371
|
-
|
|
372
|
-
target=AgentTarget(agent=self._summary_agent),
|
|
373
|
-
condition=ExpressionContextCondition(
|
|
374
|
-
expression=ContextExpression(
|
|
375
|
-
expression="len(${DocumentsToIngest}) == 0 and len(${QueriesToRun}) == 0 and len(${QueryResults}) > 0"
|
|
376
|
-
)
|
|
377
|
-
),
|
|
378
|
-
),
|
|
543
|
+
# Removed automatic context condition - let task manager decide when to summarize
|
|
379
544
|
OnCondition(
|
|
380
545
|
target=AgentTarget(agent=self._summary_agent),
|
|
381
546
|
condition=StringLLMCondition(
|
|
@@ -396,28 +561,45 @@ class DocAgent(ConversableAgent):
|
|
|
396
561
|
# The Error Agent always terminates the DocumentAgent
|
|
397
562
|
self._error_agent.handoffs.set_after_work(target=TerminateTarget())
|
|
398
563
|
|
|
399
|
-
self.register_reply([Agent, None], DocAgent.
|
|
564
|
+
self.register_reply([Agent, None], DocAgent.generate_inner_group_chat_reply)
|
|
400
565
|
|
|
401
566
|
self.documents_ingested: list[str] = []
|
|
567
|
+
self._group_chat_context_variables: Optional[ContextVariables] = None
|
|
402
568
|
|
|
403
|
-
def
|
|
569
|
+
def generate_inner_group_chat_reply(
|
|
404
570
|
self,
|
|
405
571
|
messages: Optional[Union[list[dict[str, Any]], str]] = None,
|
|
406
572
|
sender: Optional[Agent] = None,
|
|
407
573
|
config: Optional[OpenAIWrapper] = None,
|
|
408
574
|
) -> tuple[bool, Optional[Union[str, dict[str, Any]]]]:
|
|
409
|
-
"""Reply function that generates the inner
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
575
|
+
"""Reply function that generates the inner group chat reply for the DocAgent.
|
|
576
|
+
|
|
577
|
+
Args:
|
|
578
|
+
messages: Input messages to process
|
|
579
|
+
sender: The agent that sent the message
|
|
580
|
+
config: OpenAI wrapper configuration
|
|
581
|
+
|
|
582
|
+
Returns:
|
|
583
|
+
tuple: (should_terminate, reply_message)
|
|
584
|
+
"""
|
|
585
|
+
# Use existing context_variables if available, otherwise create new ones
|
|
586
|
+
if hasattr(self, "_group_chat_context_variables") and self._group_chat_context_variables is not None:
|
|
587
|
+
context_variables = self._group_chat_context_variables
|
|
588
|
+
# Reset for the new run
|
|
589
|
+
context_variables["DocumentsToIngest"] = [] # type: ignore[index]
|
|
590
|
+
else:
|
|
591
|
+
context_variables = ContextVariables(
|
|
592
|
+
data={
|
|
593
|
+
"CompletedTaskCount": 0,
|
|
594
|
+
"DocumentsToIngest": [],
|
|
595
|
+
"DocumentsIngested": self.documents_ingested,
|
|
596
|
+
"QueriesToRun": [],
|
|
597
|
+
"QueryResults": [],
|
|
598
|
+
}
|
|
599
|
+
)
|
|
600
|
+
self._group_chat_context_variables = context_variables
|
|
419
601
|
|
|
420
|
-
|
|
602
|
+
group_chat_agents = [
|
|
421
603
|
self._triage_agent,
|
|
422
604
|
self._task_manager_agent,
|
|
423
605
|
self._data_ingestion_agent,
|
|
@@ -428,7 +610,7 @@ class DocAgent(ConversableAgent):
|
|
|
428
610
|
|
|
429
611
|
agent_pattern = DefaultPattern(
|
|
430
612
|
initial_agent=self._triage_agent,
|
|
431
|
-
agents=
|
|
613
|
+
agents=group_chat_agents,
|
|
432
614
|
context_variables=context_variables,
|
|
433
615
|
group_after_work=TerminateTarget(),
|
|
434
616
|
)
|
|
@@ -441,13 +623,23 @@ class DocAgent(ConversableAgent):
|
|
|
441
623
|
# If we finish with the error agent, we return their message which contains the error
|
|
442
624
|
return True, chat_result.summary
|
|
443
625
|
if last_speaker != self._summary_agent:
|
|
444
|
-
# If the
|
|
445
|
-
return True,
|
|
626
|
+
# If the group chat finished but not with the summary agent, we assume something has gone wrong with the flow
|
|
627
|
+
return True, DEFAULT_ERROR_GROUP_CHAT_MESSAGE
|
|
446
628
|
|
|
447
629
|
return True, chat_result.summary
|
|
448
630
|
|
|
449
631
|
def _get_document_input_message(self, messages: Optional[Union[list[dict[str, Any]], str]]) -> str: # type: ignore[type-arg]
|
|
450
|
-
"""Gets and validates the input message(s) for the document agent.
|
|
632
|
+
"""Gets and validates the input message(s) for the document agent.
|
|
633
|
+
|
|
634
|
+
Args:
|
|
635
|
+
messages: Input messages as string or list of message dictionaries
|
|
636
|
+
|
|
637
|
+
Returns:
|
|
638
|
+
str: The extracted message content
|
|
639
|
+
|
|
640
|
+
Raises:
|
|
641
|
+
NotImplementedError: If messages format is invalid
|
|
642
|
+
"""
|
|
451
643
|
if isinstance(messages, str):
|
|
452
644
|
return messages
|
|
453
645
|
elif (
|
|
@@ -12,7 +12,9 @@ from ....tools.experimental import (
|
|
|
12
12
|
BrowserUseTool,
|
|
13
13
|
Crawl4AITool,
|
|
14
14
|
DuckDuckGoSearchTool,
|
|
15
|
+
FirecrawlTool,
|
|
15
16
|
PerplexitySearchTool,
|
|
17
|
+
SearxngSearchTool,
|
|
16
18
|
TavilySearchTool,
|
|
17
19
|
)
|
|
18
20
|
|
|
@@ -28,7 +30,9 @@ class WebSurferAgent(ConversableAgent):
|
|
|
28
30
|
*,
|
|
29
31
|
llm_config: Optional[Union[LLMConfig, dict[str, Any]]] = None,
|
|
30
32
|
web_tool_llm_config: Optional[Union[LLMConfig, dict[str, Any]]] = None,
|
|
31
|
-
web_tool: Literal[
|
|
33
|
+
web_tool: Literal[
|
|
34
|
+
"browser_use", "crawl4ai", "duckduckgo", "firecrawl", "perplexity", "tavily", "searxng"
|
|
35
|
+
] = "browser_use",
|
|
32
36
|
web_tool_kwargs: Optional[dict[str, Any]] = None,
|
|
33
37
|
**kwargs: Any,
|
|
34
38
|
) -> None:
|
|
@@ -48,12 +52,16 @@ class WebSurferAgent(ConversableAgent):
|
|
|
48
52
|
self.tool: Tool = BrowserUseTool(llm_config=web_tool_llm_config, **web_tool_kwargs) # type: ignore[arg-type]
|
|
49
53
|
elif web_tool == "crawl4ai":
|
|
50
54
|
self.tool = Crawl4AITool(llm_config=web_tool_llm_config, **web_tool_kwargs)
|
|
55
|
+
elif web_tool == "firecrawl":
|
|
56
|
+
self.tool = FirecrawlTool(llm_config=web_tool_llm_config, **web_tool_kwargs)
|
|
51
57
|
elif web_tool == "perplexity":
|
|
52
58
|
self.tool = PerplexitySearchTool(**web_tool_kwargs)
|
|
53
59
|
elif web_tool == "tavily":
|
|
54
60
|
self.tool = TavilySearchTool(llm_config=web_tool_llm_config, **web_tool_kwargs)
|
|
55
61
|
elif web_tool == "duckduckgo":
|
|
56
62
|
self.tool = DuckDuckGoSearchTool(**web_tool_kwargs)
|
|
63
|
+
elif web_tool == "searxng":
|
|
64
|
+
self.tool = SearxngSearchTool(**web_tool_kwargs)
|
|
57
65
|
else:
|
|
58
66
|
raise ValueError(f"Unsupported {web_tool=}.")
|
|
59
67
|
|
autogen/events/agent_events.py
CHANGED
|
@@ -669,16 +669,22 @@ class TerminationEvent(BaseEvent):
|
|
|
669
669
|
"""When a workflow termination condition is met"""
|
|
670
670
|
|
|
671
671
|
termination_reason: str
|
|
672
|
+
sender: str
|
|
673
|
+
recipient: Optional[str] = None
|
|
672
674
|
|
|
673
675
|
def __init__(
|
|
674
676
|
self,
|
|
675
677
|
*,
|
|
676
678
|
uuid: Optional[UUID] = None,
|
|
679
|
+
sender: Union["Agent", str],
|
|
680
|
+
recipient: Optional[Union["Agent", str]] = None,
|
|
677
681
|
termination_reason: str,
|
|
678
682
|
):
|
|
679
683
|
super().__init__(
|
|
680
684
|
uuid=uuid,
|
|
681
685
|
termination_reason=termination_reason,
|
|
686
|
+
sender=sender.name if hasattr(sender, "name") else sender,
|
|
687
|
+
recipient=recipient.name if hasattr(recipient, "name") else recipient if recipient else None,
|
|
682
688
|
)
|
|
683
689
|
|
|
684
690
|
def print(self, f: Optional[Callable[..., Any]] = None) -> None:
|
autogen/events/helpers.py
CHANGED
|
@@ -13,12 +13,15 @@ logger = logging.getLogger(__name__)
|
|
|
13
13
|
def deprecated_by(
|
|
14
14
|
new_class: type[BaseModel],
|
|
15
15
|
param_mapping: dict[str, str] = None,
|
|
16
|
+
default_params: dict[str, any] = None,
|
|
16
17
|
) -> Callable[[type[BaseModel]], Callable[..., BaseModel]]:
|
|
17
18
|
param_mapping = param_mapping or {}
|
|
19
|
+
default_params = default_params or {}
|
|
18
20
|
|
|
19
21
|
def decorator(
|
|
20
22
|
old_class: type[BaseModel],
|
|
21
23
|
param_mapping: dict[str, str] = param_mapping,
|
|
24
|
+
default_params: dict[str, any] = default_params,
|
|
22
25
|
) -> Callable[..., BaseModel]:
|
|
23
26
|
@wraps(old_class)
|
|
24
27
|
def wrapper(*args, **kwargs) -> BaseModel:
|
|
@@ -28,6 +31,11 @@ def deprecated_by(
|
|
|
28
31
|
# Translate old parameters to new parameters
|
|
29
32
|
new_kwargs = {param_mapping.get(k, k): v for k, v in kwargs.items()}
|
|
30
33
|
|
|
34
|
+
# Add default parameters if not already present
|
|
35
|
+
for key, value in default_params.items():
|
|
36
|
+
if key not in new_kwargs:
|
|
37
|
+
new_kwargs[key] = value
|
|
38
|
+
|
|
31
39
|
# Pass the translated parameters to the new class
|
|
32
40
|
return new_class(*args, **new_kwargs)
|
|
33
41
|
|
autogen/mcp/helpers.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Copyright (c) 2023 - 2025, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
import asyncio
|
|
5
|
+
import os
|
|
6
|
+
import signal
|
|
7
|
+
from asyncio.subprocess import PIPE, Process, create_subprocess_exec
|
|
8
|
+
from contextlib import asynccontextmanager
|
|
9
|
+
from typing import AsyncGenerator, Dict, Optional
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@asynccontextmanager
|
|
13
|
+
async def run_streamable_http_client(
|
|
14
|
+
*, mcp_server_path: str, env_vars: Optional[Dict[str, str]] = None, startup_wait_secs: float = 5.0
|
|
15
|
+
) -> AsyncGenerator[Process, None]:
|
|
16
|
+
"""
|
|
17
|
+
Async context manager to run a Python subprocess for streamable-http with custom env vars.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
mcp_server_path: Path to the Python script to run.
|
|
21
|
+
env_vars: Environment variables to export to the subprocess.
|
|
22
|
+
startup_wait_secs: Time to wait for the server to start (in seconds).
|
|
23
|
+
Yields:
|
|
24
|
+
An asyncio.subprocess.Process object.
|
|
25
|
+
"""
|
|
26
|
+
env = os.environ.copy()
|
|
27
|
+
if env_vars:
|
|
28
|
+
env.update(env_vars)
|
|
29
|
+
|
|
30
|
+
process = await create_subprocess_exec(
|
|
31
|
+
"python", mcp_server_path, "streamable-http", env=env, stdout=PIPE, stderr=PIPE
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# Optional startup delay to let the server initialize
|
|
35
|
+
await asyncio.sleep(startup_wait_secs)
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
yield process
|
|
39
|
+
finally:
|
|
40
|
+
if process.returncode is None:
|
|
41
|
+
process.send_signal(signal.SIGINT)
|
|
42
|
+
try:
|
|
43
|
+
await asyncio.wait_for(process.wait(), timeout=5.0)
|
|
44
|
+
except asyncio.TimeoutError:
|
|
45
|
+
process.kill()
|
|
@@ -117,9 +117,8 @@ class MCPProxy:
|
|
|
117
117
|
|
|
118
118
|
return q_params, path_params, body, security
|
|
119
119
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
mcp = FastMCP(title=self._title)
|
|
120
|
+
def get_mcp(self, **settings: Any) -> "FastMCP":
|
|
121
|
+
mcp = FastMCP(title=self._title, **settings)
|
|
123
122
|
|
|
124
123
|
for func in self._registered_funcs:
|
|
125
124
|
try:
|
|
@@ -647,7 +647,7 @@ class UsingAutoReplyMessage(BaseMessage):
|
|
|
647
647
|
f(colored("\n>>>>>>>> USING AUTO REPLY...", "red"), flush=True)
|
|
648
648
|
|
|
649
649
|
|
|
650
|
-
@deprecated_by(TerminationEvent)
|
|
650
|
+
@deprecated_by(TerminationEvent, default_params={"sender": "system"})
|
|
651
651
|
@wrap_message
|
|
652
652
|
class TerminationMessage(BaseMessage):
|
|
653
653
|
"""When a workflow termination condition is met"""
|