|
55 | 55 | except ImportError: |
56 | 56 | pass |
57 | 57 |
|
| 58 | +AG2_AVAILABLE = False |
| 59 | +try: |
| 60 | + import importlib.metadata as _importlib_metadata |
| 61 | + _importlib_metadata.distribution('ag2') |
| 62 | + from autogen import LLMConfig as _AG2LLMConfig # noqa: F401 — AG2-exclusive class |
| 63 | + AG2_AVAILABLE = True |
| 64 | + del _AG2LLMConfig, _importlib_metadata |
| 65 | +except Exception: |
| 66 | + pass |
| 67 | + |
58 | 68 | try: |
59 | 69 | import agentops |
60 | 70 | AGENTOPS_AVAILABLE = True |
|
65 | 75 | pass |
66 | 76 |
|
67 | 77 | # Only try to import praisonai_tools if either CrewAI or AutoGen is available |
68 | | -if CREWAI_AVAILABLE or AUTOGEN_AVAILABLE or PRAISONAI_AVAILABLE: |
| 78 | +if CREWAI_AVAILABLE or AUTOGEN_AVAILABLE or PRAISONAI_AVAILABLE or AG2_AVAILABLE: |
69 | 79 | try: |
70 | 80 | from praisonai_tools import ( |
71 | 81 | CodeDocsSearchTool, CSVSearchTool, DirectorySearchTool, DOCXSearchTool, DirectoryReadTool, |
@@ -216,6 +226,8 @@ def __init__(self, agent_file, framework, config_list, log_level=None, agent_cal |
216 | 226 | raise ImportError("AutoGen is not installed. Please install it with 'pip install praisonai[autogen]' for v0.2 or 'pip install praisonai[autogen-v4]' for v0.4") |
217 | 227 | elif framework == "praisonai" and not PRAISONAI_AVAILABLE: |
218 | 228 | raise ImportError("PraisonAI is not installed. Please install it with 'pip install praisonaiagents'") |
| 229 | + elif framework == "ag2" and not AG2_AVAILABLE: |
| 230 | + raise ImportError("AG2 is not installed. Please install it with 'pip install praisonai[ag2]'") |
219 | 231 |
|
220 | 232 | def is_function_or_decorated(self, obj): |
221 | 233 | """ |
@@ -391,7 +403,7 @@ def generate_crew_and_kickoff(self): |
391 | 403 | tools_dict = {} |
392 | 404 |
|
393 | 405 | # Only try to use praisonai_tools if it's available and needed |
394 | | - if PRAISONAI_TOOLS_AVAILABLE and (CREWAI_AVAILABLE or AUTOGEN_AVAILABLE or PRAISONAI_AVAILABLE): |
| 406 | + if PRAISONAI_TOOLS_AVAILABLE and (CREWAI_AVAILABLE or AUTOGEN_AVAILABLE or PRAISONAI_AVAILABLE or AG2_AVAILABLE): |
395 | 407 | tools_dict = { |
396 | 408 | 'CodeDocsSearchTool': CodeDocsSearchTool(), |
397 | 409 | 'CSVSearchTool': CSVSearchTool(), |
@@ -462,6 +474,12 @@ def generate_crew_and_kickoff(self): |
462 | 474 | else: |
463 | 475 | self.logger.info("Using AutoGen v0.2") |
464 | 476 | return self._run_autogen(config, topic, tools_dict) |
| 477 | + elif framework == "ag2": |
| 478 | + if not AG2_AVAILABLE: |
| 479 | + raise ImportError("AG2 is not installed. Please install it with 'pip install praisonai[ag2]'") |
| 480 | + if AGENTOPS_AVAILABLE: |
| 481 | + agentops.init(os.environ.get("AGENTOPS_API_KEY"), default_tags=["ag2"]) |
| 482 | + return self._run_ag2(config, topic, tools_dict) |
465 | 483 | elif framework == "praisonai": |
466 | 484 | if not PRAISONAI_AVAILABLE: |
467 | 485 | raise ImportError("PraisonAI is not installed. Please install it with 'pip install praisonaiagents'") |
@@ -711,6 +729,148 @@ async def run_autogen_v4_async(): |
711 | 729 | self.logger.error(f"Error running AutoGen v0.4: {str(e)}") |
712 | 730 | return f"### AutoGen v0.4 Error ###\n{str(e)}" |
713 | 731 |
|
| 732 | + def _run_ag2(self, config, topic, tools_dict): |
| 733 | + """ |
| 734 | + Run agents using the AG2 framework (community fork of AutoGen, PyPI: ag2). |
| 735 | +
|
| 736 | + AG2 installs under the 'autogen' namespace — there is no 'import ag2'. |
| 737 | + Uses LLMConfig context manager + AssistantAgent + GroupChat pattern. |
| 738 | +
|
| 739 | + Args: |
| 740 | + config (dict): Configuration dictionary parsed from YAML |
| 741 | + topic (str): The topic/task to process |
| 742 | + tools_dict (dict): Dictionary of available tools |
| 743 | +
|
| 744 | + Returns: |
| 745 | + str: Result prefixed with '### AG2 Output ###' |
| 746 | + """ |
| 747 | + import re as _re |
| 748 | + from autogen import ( |
| 749 | + AssistantAgent, UserProxyAgent, GroupChat, GroupChatManager, LLMConfig |
| 750 | + ) |
| 751 | + |
| 752 | + model_config = self.config_list[0] if self.config_list else {} |
| 753 | + |
| 754 | + # Allow YAML top-level llm block to override config_list values |
| 755 | + yaml_llm = config.get("llm", {}) or {} |
| 756 | + # Also check first role's llm block as a fallback |
| 757 | + first_role_llm = {} |
| 758 | + for role_details in config.get("roles", {}).values(): |
| 759 | + first_role_llm = role_details.get("llm", {}) or {} |
| 760 | + break |
| 761 | + |
| 762 | + # Priority: YAML top-level llm > first role llm > config_list > env vars |
| 763 | + def _resolve(key, env_var=None, default=None): |
| 764 | + return (yaml_llm.get(key) or first_role_llm.get(key) |
| 765 | + or model_config.get(key) |
| 766 | + or (os.environ.get(env_var) if env_var else None) |
| 767 | + or default) |
| 768 | + |
| 769 | + api_type = _resolve("api_type", default="openai").lower() |
| 770 | + model_name = _resolve("model", default="gpt-4o-mini") |
| 771 | + api_key = _resolve("api_key", env_var="OPENAI_API_KEY") |
| 772 | + base_url = (model_config.get("base_url") |
| 773 | + or yaml_llm.get("base_url") |
| 774 | + or os.environ.get("OPENAI_BASE_URL") |
| 775 | + or os.environ.get("OPENAI_API_BASE")) |
| 776 | + |
| 777 | + # Build LLMConfig — Bedrock needs no api_key |
| 778 | + if api_type == "bedrock": |
| 779 | + llm_config_entry = {"api_type": "bedrock", "model": model_name} |
| 780 | + else: |
| 781 | + llm_config_entry = {"model": model_name} |
| 782 | + if api_key: |
| 783 | + llm_config_entry["api_key"] = api_key |
| 784 | + if base_url and base_url not in ("https://api.openai.com/v1", "https://api.openai.com/v1/"): |
| 785 | + llm_config_entry["base_url"] = base_url |
| 786 | + llm_config = LLMConfig(llm_config_entry) |
| 787 | + |
| 788 | + user_proxy = UserProxyAgent( |
| 789 | + name="User", |
| 790 | + human_input_mode="NEVER", |
| 791 | + is_termination_msg=lambda x: "TERMINATE" in (x.get("content") or ""), |
| 792 | + code_execution_config=False, |
| 793 | + ) |
| 794 | + |
| 795 | + # Create one AssistantAgent per role |
| 796 | + ag2_agent_entries = [] |
| 797 | + for role, details in config["roles"].items(): |
| 798 | + agent_name = details.get("role", role).replace("{topic}", topic) |
| 799 | + backstory = details.get("backstory", "").replace("{topic}", topic) |
| 800 | + agent_name_safe = _re.sub(r"[^a-zA-Z0-9_\-]", "_", agent_name) |
| 801 | + assistant = AssistantAgent( |
| 802 | + name=agent_name_safe, |
| 803 | + system_message=backstory + "\nWhen the task is done, reply 'TERMINATE'.", |
| 804 | + llm_config=llm_config, |
| 805 | + ) |
| 806 | + ag2_agent_entries.append((role, details, assistant)) |
| 807 | + |
| 808 | + # Register tools via AG2 decorator pattern |
| 809 | + for role, details, assistant in ag2_agent_entries: |
| 810 | + for tool_name in details.get("tools", []): |
| 811 | + tool = tools_dict.get(tool_name) |
| 812 | + if tool is None: |
| 813 | + continue |
| 814 | + func = tool if callable(tool) else getattr(tool, "run", None) |
| 815 | + if func is None: |
| 816 | + continue |
| 817 | + |
| 818 | + def make_tool_fn(f): |
| 819 | + def tool_fn(**kwargs): |
| 820 | + return f(**kwargs) if callable(f) else str(f) |
| 821 | + tool_fn.__name__ = tool_name |
| 822 | + return tool_fn |
| 823 | + |
| 824 | + wrapped = make_tool_fn(func) |
| 825 | + assistant.register_for_llm(description=f"Tool: {tool_name}")(wrapped) |
| 826 | + user_proxy.register_for_execution()(wrapped) |
| 827 | + |
| 828 | + all_assistants = [a for _, _, a in ag2_agent_entries] |
| 829 | + if not all_assistants: |
| 830 | + return "### AG2 Output ###\nNo agents created from configuration." |
| 831 | + |
| 832 | + # Build initial message from all task descriptions |
| 833 | + task_lines = [] |
| 834 | + for role, details, _ in ag2_agent_entries: |
| 835 | + for task_name, task_details in details.get("tasks", {}).items(): |
| 836 | + desc = task_details.get("description", "").replace("{topic}", topic) |
| 837 | + if desc: |
| 838 | + task_lines.append(desc) |
| 839 | + initial_message = "\n".join(task_lines) if task_lines else topic |
| 840 | + |
| 841 | + groupchat = GroupChat( |
| 842 | + agents=[user_proxy] + all_assistants, |
| 843 | + messages=[], |
| 844 | + max_round=12, |
| 845 | + ) |
| 846 | + manager = GroupChatManager(groupchat=groupchat, llm_config=llm_config) |
| 847 | + |
| 848 | + try: |
| 849 | + chat_result = user_proxy.initiate_chat(manager, message=initial_message) |
| 850 | + except Exception as e: |
| 851 | + return f"### AG2 Error ###\n{str(e)}" |
| 852 | + |
| 853 | + # Prefer ChatResult.summary if available, otherwise scan messages |
| 854 | + result_content = "" |
| 855 | + summary = getattr(chat_result, "summary", None) |
| 856 | + if summary and isinstance(summary, str) and summary.strip(): |
| 857 | + result_content = _re.sub(r'[\s\.\,]*TERMINATE[\s\.\,]*$', '', summary, flags=_re.IGNORECASE).strip().rstrip('.') |
| 858 | + |
| 859 | + if not result_content: |
| 860 | + for msg in reversed(groupchat.messages): |
| 861 | + if msg.get("name") == "User": |
| 862 | + continue |
| 863 | + content = (msg.get("content") or "").strip() |
| 864 | + if content: |
| 865 | + result_content = _re.sub(r'[\s\.\,]*TERMINATE[\s\.\,]*$', '', content, flags=_re.IGNORECASE).strip().rstrip('.') |
| 866 | + if result_content: |
| 867 | + break |
| 868 | + |
| 869 | + if not result_content: |
| 870 | + result_content = "Task completed." |
| 871 | + |
| 872 | + return f"### AG2 Output ###\n{result_content}" |
| 873 | + |
714 | 874 | def _run_crewai(self, config, topic, tools_dict): |
715 | 875 | """ |
716 | 876 | Run agents using the CrewAI framework. |
|
0 commit comments