mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-30 03:00:41 -04:00
fix(classic): always use native tool calling, fix N/A command loop
- Remove openai_functions config option - native tool calling is now always enabled - Remove use_functions_api from BaseAgentConfiguration and prompt strategy - Add use_prefill config to disable prefill for Anthropic (prefill + tools incompatible) - Update anthropic dependency to ^0.45.0 for tools API support - Simplify prompt strategy to always expect tool_calls from LLM response This fixes the N/A command loop bug where models would output "N/A" as a command name when function calling was disabled. With native tool calling always enabled, models are forced to pick from valid tools only. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
190
classic/benchmark/poetry.lock
generated
190
classic/benchmark/poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -102,7 +102,6 @@ smart_llm: ModelName = "gpt-4"
|
||||
big_brain: bool = True # Use smart_llm
|
||||
cycle_budget: Optional[int] = 1 # Steps before approval needed
|
||||
send_token_limit: Optional[int] # Prompt token budget
|
||||
use_functions_api: bool = False
|
||||
```
|
||||
|
||||
### Component System (`agent/components.py`)
|
||||
|
||||
@@ -18,7 +18,7 @@ from typing import (
|
||||
)
|
||||
|
||||
from colorama import Fore
|
||||
from pydantic import BaseModel, Field, ValidationInfo, field_validator
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic_core import from_json, to_json
|
||||
|
||||
from forge.agent import protocols
|
||||
@@ -53,7 +53,6 @@ class BaseAgentConfiguration(SystemConfiguration):
|
||||
|
||||
fast_llm: ModelName = UserConfigurable(default=OpenAIModelName.GPT3_16k)
|
||||
smart_llm: ModelName = UserConfigurable(default=OpenAIModelName.GPT4)
|
||||
use_functions_api: bool = UserConfigurable(default=False)
|
||||
|
||||
default_cycle_instruction: str = DEFAULT_TRIGGERING_PROMPT
|
||||
"""The default instruction passed to the AI for a thinking cycle."""
|
||||
@@ -85,22 +84,6 @@ class BaseAgentConfiguration(SystemConfiguration):
|
||||
defaults to 75% of `llm.max_tokens`.
|
||||
"""
|
||||
|
||||
@field_validator("use_functions_api")
|
||||
def validate_openai_functions(cls, value: bool, info: ValidationInfo):
|
||||
if value:
|
||||
smart_llm = info.data["smart_llm"]
|
||||
fast_llm = info.data["fast_llm"]
|
||||
assert all(
|
||||
[
|
||||
not any(s in name for s in {"-0301", "-0314"})
|
||||
for name in {smart_llm, fast_llm}
|
||||
]
|
||||
), (
|
||||
f"Model {smart_llm} does not support OpenAI Functions. "
|
||||
"Please disable OPENAI_FUNCTIONS or choose a suitable model."
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
class BaseAgentSettings(SystemSettings):
|
||||
agent_id: str = ""
|
||||
|
||||
8950
classic/forge/poetry.lock
generated
8950
classic/forge/poetry.lock
generated
File diff suppressed because one or more lines are too long
@@ -27,7 +27,7 @@ python = "^3.10"
|
||||
agbenchmark = { path = "../benchmark", optional = true }
|
||||
# agbenchmark = {git = "https://github.com/Significant-Gravitas/AutoGPT.git", subdirectory = "benchmark", optional = true}
|
||||
aiohttp = "^3.8.5"
|
||||
anthropic = ">=0.40,<1.0"
|
||||
anthropic = "^0.45.0"
|
||||
beautifulsoup4 = "^4.12.2"
|
||||
boto3 = "^1.33.6"
|
||||
charset-normalizer = "^3.1.0"
|
||||
|
||||
@@ -109,7 +109,6 @@ def create_agent_state(
|
||||
fast_llm=app_config.fast_llm,
|
||||
smart_llm=app_config.smart_llm,
|
||||
allow_fs_access=not app_config.restrict_to_workspace,
|
||||
use_functions_api=app_config.openai_functions,
|
||||
),
|
||||
history=Agent.default_settings.history.model_copy(deep=True),
|
||||
)
|
||||
|
||||
@@ -5,8 +5,6 @@ import logging
|
||||
from typing import TYPE_CHECKING, Any, ClassVar, Optional
|
||||
|
||||
import sentry_sdk
|
||||
from pydantic import Field
|
||||
|
||||
from forge.agent.base import BaseAgent, BaseAgentConfiguration, BaseAgentSettings
|
||||
from forge.agent.protocols import (
|
||||
AfterExecute,
|
||||
@@ -64,6 +62,7 @@ from forge.utils.exceptions import (
|
||||
CommandExecutionError,
|
||||
UnknownCommandError,
|
||||
)
|
||||
from pydantic import Field
|
||||
|
||||
from .prompt_strategies.one_shot import (
|
||||
OneShotAgentActionProposal,
|
||||
@@ -113,11 +112,8 @@ class Agent(BaseAgent[OneShotAgentActionProposal], Configurable[AgentSettings]):
|
||||
prompt_config = OneShotAgentPromptStrategy.default_configuration.model_copy(
|
||||
deep=True
|
||||
)
|
||||
prompt_config.use_functions_api = (
|
||||
settings.config.use_functions_api
|
||||
# Anthropic currently doesn't support tools + prefilling :(
|
||||
and self.llm.provider_name != "anthropic"
|
||||
)
|
||||
# Anthropic doesn't support tools + prefilling, so disable prefill for Anthropic
|
||||
prompt_config.use_prefill = self.llm.provider_name != "anthropic"
|
||||
self.prompt_strategy = OneShotAgentPromptStrategy(prompt_config, logger)
|
||||
self.commands: list[Command] = []
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ class OneShotAgentPromptConfiguration(SystemConfiguration):
|
||||
choose_action_instruction: str = UserConfigurable(
|
||||
default=DEFAULT_CHOOSE_ACTION_INSTRUCTION
|
||||
)
|
||||
use_functions_api: bool = UserConfigurable(default=False)
|
||||
use_prefill: bool = True
|
||||
|
||||
#########
|
||||
# State #
|
||||
@@ -134,8 +134,8 @@ class OneShotAgentPromptStrategy(PromptStrategy):
|
||||
*messages,
|
||||
final_instruction_msg,
|
||||
],
|
||||
prefill_response=response_prefill,
|
||||
functions=commands if self.config.use_functions_api else [],
|
||||
prefill_response=response_prefill if self.config.use_prefill else "",
|
||||
functions=commands,
|
||||
)
|
||||
|
||||
def build_system_prompt(
|
||||
@@ -152,9 +152,7 @@ class OneShotAgentPromptStrategy(PromptStrategy):
|
||||
str: The system prompt body
|
||||
str: The desired start for the LLM's response; used to steer the output
|
||||
"""
|
||||
response_fmt_instruction, response_prefill = self.response_format_instruction(
|
||||
self.config.use_functions_api
|
||||
)
|
||||
response_fmt_instruction, response_prefill = self.response_format_instruction()
|
||||
system_prompt_parts = (
|
||||
self._generate_intro_prompt(ai_profile)
|
||||
+ (self._generate_os_info() if include_os_info else [])
|
||||
@@ -181,10 +179,11 @@ class OneShotAgentPromptStrategy(PromptStrategy):
|
||||
response_prefill,
|
||||
)
|
||||
|
||||
def response_format_instruction(self, use_functions_api: bool) -> tuple[str, str]:
|
||||
def response_format_instruction(self) -> tuple[str, str]:
|
||||
response_schema = self.response_schema.model_copy(deep=True)
|
||||
assert response_schema.properties
|
||||
if use_functions_api and "use_tool" in response_schema.properties:
|
||||
# Always use tool calling - remove use_tool from schema since it comes from tool_calls
|
||||
if "use_tool" in response_schema.properties:
|
||||
del response_schema.properties["use_tool"]
|
||||
|
||||
# Unindent for performance
|
||||
@@ -199,7 +198,7 @@ class OneShotAgentPromptStrategy(PromptStrategy):
|
||||
(
|
||||
f"YOU MUST ALWAYS RESPOND WITH A JSON OBJECT OF THE FOLLOWING TYPE:\n"
|
||||
f"{response_format}"
|
||||
+ ("\n\nYOU MUST ALSO INVOKE A TOOL!" if use_functions_api else "")
|
||||
"\n\nYOU MUST ALSO INVOKE A TOOL!"
|
||||
),
|
||||
response_prefill,
|
||||
)
|
||||
@@ -269,13 +268,13 @@ class OneShotAgentPromptStrategy(PromptStrategy):
|
||||
"Parsing object extracted from LLM response:\n"
|
||||
f"{json.dumps(assistant_reply_dict, indent=4)}"
|
||||
)
|
||||
if self.config.use_functions_api:
|
||||
if not response.tool_calls:
|
||||
raise InvalidAgentResponseError("Assistant did not use a tool")
|
||||
assistant_reply_dict["use_tool"] = response.tool_calls[0].function
|
||||
# Always expect tool calls - native tool calling is always enabled
|
||||
if not response.tool_calls:
|
||||
raise InvalidAgentResponseError("Assistant did not use a tool")
|
||||
assistant_reply_dict["use_tool"] = response.tool_calls[0].function
|
||||
|
||||
parsed_response = OneShotAgentActionProposal.model_validate(
|
||||
assistant_reply_dict
|
||||
)
|
||||
parsed_response.raw_message = response.copy()
|
||||
parsed_response.raw_message = response.model_copy()
|
||||
return parsed_response
|
||||
|
||||
@@ -9,18 +9,20 @@ from pathlib import Path
|
||||
from typing import Optional, Union
|
||||
|
||||
from forge.config.base import BaseConfig
|
||||
from forge.llm.providers import CHAT_MODELS, ModelName
|
||||
from forge.llm.providers import ModelName
|
||||
from forge.llm.providers.openai import OpenAICredentials, OpenAIModelName
|
||||
from forge.logging.config import LoggingConfig
|
||||
from forge.models.config import Configurable, UserConfigurable
|
||||
from pydantic import SecretStr, ValidationInfo, field_validator
|
||||
from pydantic import SecretStr
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
AZURE_CONFIG_FILE = Path("azure.yaml")
|
||||
|
||||
GPT_4_MODEL = OpenAIModelName.GPT4_O
|
||||
GPT_3_MODEL = OpenAIModelName.GPT4_O_MINI # Fallback model for when configured model is unavailable
|
||||
GPT_3_MODEL = (
|
||||
OpenAIModelName.GPT4_O_MINI
|
||||
) # Fallback model for when configured model is unavailable
|
||||
|
||||
|
||||
class AppConfig(BaseConfig):
|
||||
@@ -55,9 +57,6 @@ class AppConfig(BaseConfig):
|
||||
from_env="SMART_LLM",
|
||||
)
|
||||
temperature: float = UserConfigurable(default=0, from_env="TEMPERATURE")
|
||||
openai_functions: bool = UserConfigurable(
|
||||
default=False, from_env=lambda: os.getenv("OPENAI_FUNCTIONS", "False") == "True"
|
||||
)
|
||||
embedding_model: str = UserConfigurable(
|
||||
default="text-embedding-3-small", from_env="EMBEDDING_MODEL"
|
||||
)
|
||||
@@ -90,16 +89,6 @@ class AppConfig(BaseConfig):
|
||||
default=AZURE_CONFIG_FILE, from_env="AZURE_CONFIG_FILE"
|
||||
)
|
||||
|
||||
@field_validator("openai_functions")
|
||||
def validate_openai_functions(cls, value: bool, info: ValidationInfo):
|
||||
if value:
|
||||
smart_llm = info.data["smart_llm"]
|
||||
assert CHAT_MODELS[smart_llm].has_function_call_api, (
|
||||
f"Model {smart_llm} does not support tool calling. "
|
||||
"Please disable OPENAI_FUNCTIONS or choose a suitable model."
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
class ConfigBuilder(Configurable[AppConfig]):
|
||||
default_settings = AppConfig()
|
||||
|
||||
1885
classic/original_autogpt/poetry.lock
generated
1885
classic/original_autogpt/poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -93,7 +93,6 @@ def agent(
|
||||
fast_llm=config.fast_llm,
|
||||
smart_llm=config.smart_llm,
|
||||
allow_fs_access=not config.restrict_to_workspace,
|
||||
use_functions_api=config.openai_functions,
|
||||
),
|
||||
history=Agent.default_settings.history.model_copy(deep=True),
|
||||
)
|
||||
|
||||
@@ -26,7 +26,6 @@ def dummy_agent(config: AppConfig, llm_provider: MultiProvider):
|
||||
config=AgentConfiguration(
|
||||
fast_llm=config.fast_llm,
|
||||
smart_llm=config.smart_llm,
|
||||
use_functions_api=config.openai_functions,
|
||||
),
|
||||
history=Agent.default_settings.history.model_copy(deep=True),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user