fast-agent-mcp 0.2.58__py3-none-any.whl → 0.3.1__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 fast-agent-mcp might be problematic. Click here for more details.
- fast_agent/__init__.py +127 -0
- fast_agent/agents/__init__.py +36 -0
- {mcp_agent/core → fast_agent/agents}/agent_types.py +2 -1
- fast_agent/agents/llm_agent.py +217 -0
- fast_agent/agents/llm_decorator.py +486 -0
- mcp_agent/agents/base_agent.py → fast_agent/agents/mcp_agent.py +377 -385
- fast_agent/agents/tool_agent.py +168 -0
- {mcp_agent → fast_agent}/agents/workflow/chain_agent.py +43 -33
- {mcp_agent → fast_agent}/agents/workflow/evaluator_optimizer.py +31 -35
- {mcp_agent → fast_agent}/agents/workflow/iterative_planner.py +56 -47
- {mcp_agent → fast_agent}/agents/workflow/orchestrator_models.py +4 -4
- {mcp_agent → fast_agent}/agents/workflow/parallel_agent.py +34 -41
- {mcp_agent → fast_agent}/agents/workflow/router_agent.py +54 -39
- {mcp_agent → fast_agent}/cli/__main__.py +5 -3
- {mcp_agent → fast_agent}/cli/commands/check_config.py +95 -66
- {mcp_agent → fast_agent}/cli/commands/go.py +20 -11
- {mcp_agent → fast_agent}/cli/commands/quickstart.py +4 -4
- {mcp_agent → fast_agent}/cli/commands/server_helpers.py +1 -1
- {mcp_agent → fast_agent}/cli/commands/setup.py +75 -134
- {mcp_agent → fast_agent}/cli/commands/url_parser.py +9 -8
- {mcp_agent → fast_agent}/cli/main.py +36 -16
- {mcp_agent → fast_agent}/cli/terminal.py +2 -2
- {mcp_agent → fast_agent}/config.py +10 -2
- fast_agent/constants.py +8 -0
- {mcp_agent → fast_agent}/context.py +24 -19
- {mcp_agent → fast_agent}/context_dependent.py +9 -5
- fast_agent/core/__init__.py +52 -0
- {mcp_agent → fast_agent}/core/agent_app.py +39 -36
- fast_agent/core/core_app.py +135 -0
- {mcp_agent → fast_agent}/core/direct_decorators.py +12 -26
- {mcp_agent → fast_agent}/core/direct_factory.py +95 -73
- {mcp_agent → fast_agent/core}/executor/executor.py +4 -5
- {mcp_agent → fast_agent}/core/fastagent.py +32 -32
- fast_agent/core/logging/__init__.py +5 -0
- {mcp_agent → fast_agent/core}/logging/events.py +3 -3
- {mcp_agent → fast_agent/core}/logging/json_serializer.py +1 -1
- {mcp_agent → fast_agent/core}/logging/listeners.py +85 -7
- {mcp_agent → fast_agent/core}/logging/logger.py +7 -7
- {mcp_agent → fast_agent/core}/logging/transport.py +10 -11
- fast_agent/core/prompt.py +9 -0
- {mcp_agent → fast_agent}/core/validation.py +4 -4
- fast_agent/event_progress.py +61 -0
- fast_agent/history/history_exporter.py +44 -0
- {mcp_agent → fast_agent}/human_input/__init__.py +9 -12
- {mcp_agent → fast_agent}/human_input/elicitation_handler.py +26 -8
- {mcp_agent → fast_agent}/human_input/elicitation_state.py +7 -7
- {mcp_agent → fast_agent}/human_input/simple_form.py +6 -4
- {mcp_agent → fast_agent}/human_input/types.py +1 -18
- fast_agent/interfaces.py +228 -0
- fast_agent/llm/__init__.py +9 -0
- mcp_agent/llm/augmented_llm.py → fast_agent/llm/fastagent_llm.py +127 -218
- fast_agent/llm/internal/passthrough.py +137 -0
- mcp_agent/llm/augmented_llm_playback.py → fast_agent/llm/internal/playback.py +29 -25
- mcp_agent/llm/augmented_llm_silent.py → fast_agent/llm/internal/silent.py +10 -17
- fast_agent/llm/internal/slow.py +38 -0
- {mcp_agent → fast_agent}/llm/memory.py +40 -30
- {mcp_agent → fast_agent}/llm/model_database.py +35 -2
- {mcp_agent → fast_agent}/llm/model_factory.py +103 -77
- fast_agent/llm/model_info.py +126 -0
- {mcp_agent/llm/providers → fast_agent/llm/provider/anthropic}/anthropic_utils.py +7 -7
- fast_agent/llm/provider/anthropic/llm_anthropic.py +603 -0
- {mcp_agent/llm/providers → fast_agent/llm/provider/anthropic}/multipart_converter_anthropic.py +79 -86
- {mcp_agent/llm/providers → fast_agent/llm/provider/bedrock}/bedrock_utils.py +3 -1
- mcp_agent/llm/providers/augmented_llm_bedrock.py → fast_agent/llm/provider/bedrock/llm_bedrock.py +833 -717
- {mcp_agent/llm/providers → fast_agent/llm/provider/google}/google_converter.py +66 -14
- fast_agent/llm/provider/google/llm_google_native.py +431 -0
- mcp_agent/llm/providers/augmented_llm_aliyun.py → fast_agent/llm/provider/openai/llm_aliyun.py +6 -7
- mcp_agent/llm/providers/augmented_llm_azure.py → fast_agent/llm/provider/openai/llm_azure.py +4 -4
- mcp_agent/llm/providers/augmented_llm_deepseek.py → fast_agent/llm/provider/openai/llm_deepseek.py +10 -11
- mcp_agent/llm/providers/augmented_llm_generic.py → fast_agent/llm/provider/openai/llm_generic.py +4 -4
- mcp_agent/llm/providers/augmented_llm_google_oai.py → fast_agent/llm/provider/openai/llm_google_oai.py +4 -4
- mcp_agent/llm/providers/augmented_llm_groq.py → fast_agent/llm/provider/openai/llm_groq.py +14 -16
- mcp_agent/llm/providers/augmented_llm_openai.py → fast_agent/llm/provider/openai/llm_openai.py +133 -207
- mcp_agent/llm/providers/augmented_llm_openrouter.py → fast_agent/llm/provider/openai/llm_openrouter.py +6 -6
- mcp_agent/llm/providers/augmented_llm_tensorzero_openai.py → fast_agent/llm/provider/openai/llm_tensorzero_openai.py +17 -16
- mcp_agent/llm/providers/augmented_llm_xai.py → fast_agent/llm/provider/openai/llm_xai.py +6 -6
- {mcp_agent/llm/providers → fast_agent/llm/provider/openai}/multipart_converter_openai.py +125 -63
- {mcp_agent/llm/providers → fast_agent/llm/provider/openai}/openai_multipart.py +12 -12
- {mcp_agent/llm/providers → fast_agent/llm/provider/openai}/openai_utils.py +18 -16
- {mcp_agent → fast_agent}/llm/provider_key_manager.py +2 -2
- {mcp_agent → fast_agent}/llm/provider_types.py +2 -0
- {mcp_agent → fast_agent}/llm/sampling_converter.py +15 -12
- {mcp_agent → fast_agent}/llm/usage_tracking.py +23 -5
- fast_agent/mcp/__init__.py +54 -0
- {mcp_agent → fast_agent}/mcp/elicitation_factory.py +3 -3
- {mcp_agent → fast_agent}/mcp/elicitation_handlers.py +19 -10
- {mcp_agent → fast_agent}/mcp/gen_client.py +3 -3
- fast_agent/mcp/helpers/__init__.py +36 -0
- fast_agent/mcp/helpers/content_helpers.py +183 -0
- {mcp_agent → fast_agent}/mcp/helpers/server_config_helpers.py +8 -8
- {mcp_agent → fast_agent}/mcp/hf_auth.py +25 -23
- fast_agent/mcp/interfaces.py +93 -0
- {mcp_agent → fast_agent}/mcp/logger_textio.py +4 -4
- {mcp_agent → fast_agent}/mcp/mcp_agent_client_session.py +49 -44
- {mcp_agent → fast_agent}/mcp/mcp_aggregator.py +66 -115
- {mcp_agent → fast_agent}/mcp/mcp_connection_manager.py +16 -23
- {mcp_agent/core → fast_agent/mcp}/mcp_content.py +23 -15
- {mcp_agent → fast_agent}/mcp/mime_utils.py +39 -0
- fast_agent/mcp/prompt.py +159 -0
- mcp_agent/mcp/prompt_message_multipart.py → fast_agent/mcp/prompt_message_extended.py +27 -20
- {mcp_agent → fast_agent}/mcp/prompt_render.py +21 -19
- {mcp_agent → fast_agent}/mcp/prompt_serialization.py +46 -46
- fast_agent/mcp/prompts/__main__.py +7 -0
- {mcp_agent → fast_agent}/mcp/prompts/prompt_helpers.py +31 -30
- {mcp_agent → fast_agent}/mcp/prompts/prompt_load.py +8 -8
- {mcp_agent → fast_agent}/mcp/prompts/prompt_server.py +11 -19
- {mcp_agent → fast_agent}/mcp/prompts/prompt_template.py +18 -18
- {mcp_agent → fast_agent}/mcp/resource_utils.py +1 -1
- {mcp_agent → fast_agent}/mcp/sampling.py +31 -26
- {mcp_agent/mcp_server → fast_agent/mcp/server}/__init__.py +1 -1
- {mcp_agent/mcp_server → fast_agent/mcp/server}/agent_server.py +5 -6
- fast_agent/mcp/ui_agent.py +48 -0
- fast_agent/mcp/ui_mixin.py +209 -0
- fast_agent/mcp_server_registry.py +90 -0
- {mcp_agent → fast_agent}/resources/examples/data-analysis/analysis-campaign.py +5 -4
- {mcp_agent → fast_agent}/resources/examples/data-analysis/analysis.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/forms_demo.py +3 -3
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/game_character.py +2 -2
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/game_character_handler.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/tool_call.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/mcp/state-transfer/agent_one.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/mcp/state-transfer/agent_two.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/researcher/researcher-eval.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/researcher/researcher-imp.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/researcher/researcher.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/tensorzero/agent.py +2 -2
- {mcp_agent → fast_agent}/resources/examples/tensorzero/image_demo.py +3 -3
- {mcp_agent → fast_agent}/resources/examples/tensorzero/simple_agent.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/workflows/chaining.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/workflows/evaluator.py +3 -3
- {mcp_agent → fast_agent}/resources/examples/workflows/human_input.py +5 -3
- {mcp_agent → fast_agent}/resources/examples/workflows/orchestrator.py +1 -1
- {mcp_agent → fast_agent}/resources/examples/workflows/parallel.py +2 -2
- {mcp_agent → fast_agent}/resources/examples/workflows/router.py +5 -2
- fast_agent/resources/setup/.gitignore +24 -0
- fast_agent/resources/setup/agent.py +18 -0
- fast_agent/resources/setup/fastagent.config.yaml +44 -0
- fast_agent/resources/setup/fastagent.secrets.yaml.example +38 -0
- fast_agent/resources/setup/pyproject.toml.tmpl +17 -0
- fast_agent/tools/elicitation.py +369 -0
- fast_agent/types/__init__.py +32 -0
- fast_agent/types/llm_stop_reason.py +77 -0
- fast_agent/ui/__init__.py +38 -0
- fast_agent/ui/console_display.py +1005 -0
- {mcp_agent/human_input → fast_agent/ui}/elicitation_form.py +17 -12
- mcp_agent/human_input/elicitation_forms.py → fast_agent/ui/elicitation_style.py +1 -1
- {mcp_agent/core → fast_agent/ui}/enhanced_prompt.py +96 -25
- {mcp_agent/core → fast_agent/ui}/interactive_prompt.py +330 -125
- fast_agent/ui/mcp_ui_utils.py +224 -0
- {mcp_agent → fast_agent/ui}/progress_display.py +2 -2
- {mcp_agent/logging → fast_agent/ui}/rich_progress.py +4 -4
- {mcp_agent/core → fast_agent/ui}/usage_display.py +3 -8
- {fast_agent_mcp-0.2.58.dist-info → fast_agent_mcp-0.3.1.dist-info}/METADATA +7 -7
- fast_agent_mcp-0.3.1.dist-info/RECORD +203 -0
- fast_agent_mcp-0.3.1.dist-info/entry_points.txt +5 -0
- fast_agent_mcp-0.2.58.dist-info/RECORD +0 -193
- fast_agent_mcp-0.2.58.dist-info/entry_points.txt +0 -6
- mcp_agent/__init__.py +0 -114
- mcp_agent/agents/agent.py +0 -92
- mcp_agent/agents/workflow/__init__.py +0 -1
- mcp_agent/agents/workflow/orchestrator_agent.py +0 -597
- mcp_agent/app.py +0 -175
- mcp_agent/core/__init__.py +0 -26
- mcp_agent/core/prompt.py +0 -191
- mcp_agent/event_progress.py +0 -134
- mcp_agent/human_input/handler.py +0 -81
- mcp_agent/llm/__init__.py +0 -2
- mcp_agent/llm/augmented_llm_passthrough.py +0 -232
- mcp_agent/llm/augmented_llm_slow.py +0 -53
- mcp_agent/llm/providers/__init__.py +0 -8
- mcp_agent/llm/providers/augmented_llm_anthropic.py +0 -718
- mcp_agent/llm/providers/augmented_llm_google_native.py +0 -496
- mcp_agent/llm/providers/sampling_converter_anthropic.py +0 -57
- mcp_agent/llm/providers/sampling_converter_openai.py +0 -26
- mcp_agent/llm/sampling_format_converter.py +0 -37
- mcp_agent/logging/__init__.py +0 -0
- mcp_agent/mcp/__init__.py +0 -50
- mcp_agent/mcp/helpers/__init__.py +0 -25
- mcp_agent/mcp/helpers/content_helpers.py +0 -187
- mcp_agent/mcp/interfaces.py +0 -266
- mcp_agent/mcp/prompts/__init__.py +0 -0
- mcp_agent/mcp/prompts/__main__.py +0 -10
- mcp_agent/mcp_server_registry.py +0 -343
- mcp_agent/tools/tool_definition.py +0 -14
- mcp_agent/ui/console_display.py +0 -790
- mcp_agent/ui/console_display_legacy.py +0 -401
- {mcp_agent → fast_agent}/agents/workflow/orchestrator_prompts.py +0 -0
- {mcp_agent/agents → fast_agent/cli}/__init__.py +0 -0
- {mcp_agent → fast_agent}/cli/constants.py +0 -0
- {mcp_agent → fast_agent}/core/error_handling.py +0 -0
- {mcp_agent → fast_agent}/core/exceptions.py +0 -0
- {mcp_agent/cli → fast_agent/core/executor}/__init__.py +0 -0
- {mcp_agent → fast_agent/core}/executor/task_registry.py +0 -0
- {mcp_agent → fast_agent/core}/executor/workflow_signal.py +0 -0
- {mcp_agent → fast_agent}/human_input/form_fields.py +0 -0
- {mcp_agent → fast_agent}/llm/prompt_utils.py +0 -0
- {mcp_agent/core → fast_agent/llm}/request_params.py +0 -0
- {mcp_agent → fast_agent}/mcp/common.py +0 -0
- {mcp_agent/executor → fast_agent/mcp/prompts}/__init__.py +0 -0
- {mcp_agent → fast_agent}/mcp/prompts/prompt_constants.py +0 -0
- {mcp_agent → fast_agent}/py.typed +0 -0
- {mcp_agent → fast_agent}/resources/examples/data-analysis/fastagent.config.yaml +0 -0
- {mcp_agent → fast_agent}/resources/examples/data-analysis/mount-point/WA_Fn-UseC_-HR-Employee-Attrition.csv +0 -0
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/elicitation_account_server.py +0 -0
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/elicitation_forms_server.py +0 -0
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/elicitation_game_server.py +0 -0
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/fastagent.config.yaml +0 -0
- {mcp_agent → fast_agent}/resources/examples/mcp/elicitations/fastagent.secrets.yaml.example +0 -0
- {mcp_agent → fast_agent}/resources/examples/mcp/state-transfer/fastagent.config.yaml +0 -0
- {mcp_agent → fast_agent}/resources/examples/mcp/state-transfer/fastagent.secrets.yaml.example +0 -0
- {mcp_agent → fast_agent}/resources/examples/researcher/fastagent.config.yaml +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/.env.sample +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/Makefile +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/README.md +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/demo_images/clam.jpg +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/demo_images/crab.png +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/demo_images/shrimp.png +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/docker-compose.yml +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/fastagent.config.yaml +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/mcp_server/Dockerfile +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/mcp_server/entrypoint.sh +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/mcp_server/mcp_server.py +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/mcp_server/pyproject.toml +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/tensorzero_config/system_schema.json +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/tensorzero_config/system_template.minijinja +0 -0
- {mcp_agent → fast_agent}/resources/examples/tensorzero/tensorzero_config/tensorzero.toml +0 -0
- {mcp_agent → fast_agent}/resources/examples/workflows/fastagent.config.yaml +0 -0
- {mcp_agent → fast_agent}/resources/examples/workflows/graded_report.md +0 -0
- {mcp_agent → fast_agent}/resources/examples/workflows/short_story.md +0 -0
- {mcp_agent → fast_agent}/resources/examples/workflows/short_story.txt +0 -0
- {mcp_agent → fast_agent/ui}/console.py +0 -0
- {mcp_agent/core → fast_agent/ui}/mermaid_utils.py +0 -0
- {fast_agent_mcp-0.2.58.dist-info → fast_agent_mcp-0.3.1.dist-info}/WHEEL +0 -0
- {fast_agent_mcp-0.2.58.dist-info → fast_agent_mcp-0.3.1.dist-info}/licenses/LICENSE +0 -0
mcp_agent/llm/providers/augmented_llm_deepseek.py → fast_agent/llm/provider/openai/llm_deepseek.py
RENAMED
|
@@ -6,17 +6,16 @@ from openai.types.chat import (
|
|
|
6
6
|
ChatCompletionMessage,
|
|
7
7
|
)
|
|
8
8
|
|
|
9
|
-
from
|
|
10
|
-
from
|
|
11
|
-
from
|
|
12
|
-
from
|
|
13
|
-
from mcp_agent.mcp.prompt_message_multipart import PromptMessageMultipart
|
|
9
|
+
from fast_agent.interfaces import ModelT
|
|
10
|
+
from fast_agent.llm.provider.openai.llm_openai import OpenAILLM
|
|
11
|
+
from fast_agent.llm.provider_types import Provider
|
|
12
|
+
from fast_agent.types import PromptMessageExtended, RequestParams
|
|
14
13
|
|
|
15
14
|
DEEPSEEK_BASE_URL = "https://api.deepseek.com"
|
|
16
15
|
DEFAULT_DEEPSEEK_MODEL = "deepseekchat" # current Deepseek only has two type models
|
|
17
16
|
|
|
18
17
|
|
|
19
|
-
class
|
|
18
|
+
class DeepSeekLLM(OpenAILLM):
|
|
20
19
|
def __init__(self, *args, **kwargs) -> None:
|
|
21
20
|
super().__init__(*args, provider=Provider.DEEPSEEK, **kwargs)
|
|
22
21
|
|
|
@@ -24,11 +23,11 @@ class DeepSeekAugmentedLLM(OpenAIAugmentedLLM):
|
|
|
24
23
|
"""Initialize Deepseek-specific default parameters"""
|
|
25
24
|
# Get base defaults from parent (includes ModelDatabase lookup)
|
|
26
25
|
base_params = super()._initialize_default_params(kwargs)
|
|
27
|
-
|
|
26
|
+
|
|
28
27
|
# Override with Deepseek-specific settings
|
|
29
28
|
chosen_model = kwargs.get("model", DEFAULT_DEEPSEEK_MODEL)
|
|
30
29
|
base_params.model = chosen_model
|
|
31
|
-
|
|
30
|
+
|
|
32
31
|
return base_params
|
|
33
32
|
|
|
34
33
|
def _base_url(self) -> str:
|
|
@@ -40,10 +39,10 @@ class DeepSeekAugmentedLLM(OpenAIAugmentedLLM):
|
|
|
40
39
|
|
|
41
40
|
async def _apply_prompt_provider_specific_structured(
|
|
42
41
|
self,
|
|
43
|
-
multipart_messages: List[
|
|
42
|
+
multipart_messages: List[PromptMessageExtended],
|
|
44
43
|
model: Type[ModelT],
|
|
45
44
|
request_params: RequestParams | None = None,
|
|
46
|
-
) -> Tuple[ModelT | None,
|
|
45
|
+
) -> Tuple[ModelT | None, PromptMessageExtended]: # noqa: F821
|
|
47
46
|
request_params = self.get_request_params(request_params)
|
|
48
47
|
|
|
49
48
|
request_params.response_format = {"type": "json_object"}
|
|
@@ -78,7 +77,7 @@ class DeepSeekAugmentedLLM(OpenAIAugmentedLLM):
|
|
|
78
77
|
- All required fields must be included"""
|
|
79
78
|
)
|
|
80
79
|
|
|
81
|
-
result:
|
|
80
|
+
result: PromptMessageExtended = await self._apply_prompt_provider_specific(
|
|
82
81
|
multipart_messages, request_params
|
|
83
82
|
)
|
|
84
83
|
return self._structured_from_multipart(result, model)
|
mcp_agent/llm/providers/augmented_llm_generic.py → fast_agent/llm/provider/openai/llm_generic.py
RENAMED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import os
|
|
2
2
|
|
|
3
|
-
from
|
|
4
|
-
from
|
|
5
|
-
from
|
|
3
|
+
from fast_agent.llm.provider.openai.llm_openai import OpenAILLM
|
|
4
|
+
from fast_agent.llm.provider_types import Provider
|
|
5
|
+
from fast_agent.types import RequestParams
|
|
6
6
|
|
|
7
7
|
DEFAULT_OLLAMA_BASE_URL = "http://localhost:11434/v1"
|
|
8
8
|
DEFAULT_OLLAMA_MODEL = "llama3.2:latest"
|
|
9
9
|
DEFAULT_OLLAMA_API_KEY = "ollama"
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
class
|
|
12
|
+
class GenericLLM(OpenAILLM):
|
|
13
13
|
def __init__(self, *args, **kwargs) -> None:
|
|
14
14
|
super().__init__(
|
|
15
15
|
*args, provider=Provider.GENERIC, **kwargs
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
from
|
|
2
|
-
from
|
|
3
|
-
from
|
|
1
|
+
from fast_agent.llm.provider.openai.llm_openai import OpenAILLM
|
|
2
|
+
from fast_agent.llm.provider_types import Provider
|
|
3
|
+
from fast_agent.types import RequestParams
|
|
4
4
|
|
|
5
5
|
GOOGLE_BASE_URL = "https://generativelanguage.googleapis.com/v1beta/openai"
|
|
6
6
|
DEFAULT_GOOGLE_MODEL = "gemini-2.0-flash"
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
class
|
|
9
|
+
class GoogleOaiLLM(OpenAILLM):
|
|
10
10
|
def __init__(self, *args, **kwargs) -> None:
|
|
11
11
|
super().__init__(*args, provider=Provider.GOOGLE_OAI, **kwargs)
|
|
12
12
|
|
|
@@ -2,14 +2,13 @@ from typing import List, Tuple, Type, cast
|
|
|
2
2
|
|
|
3
3
|
from pydantic_core import from_json
|
|
4
4
|
|
|
5
|
-
from
|
|
6
|
-
from
|
|
7
|
-
from
|
|
8
|
-
from
|
|
9
|
-
from
|
|
10
|
-
from
|
|
11
|
-
from
|
|
12
|
-
from mcp_agent.mcp.prompt_message_multipart import PromptMessageMultipart
|
|
5
|
+
from fast_agent.core.logging.logger import get_logger
|
|
6
|
+
from fast_agent.interfaces import ModelT
|
|
7
|
+
from fast_agent.llm.model_database import ModelDatabase
|
|
8
|
+
from fast_agent.llm.provider.openai.llm_openai import OpenAILLM
|
|
9
|
+
from fast_agent.llm.provider_types import Provider
|
|
10
|
+
from fast_agent.mcp.helpers.content_helpers import get_text, split_thinking_content
|
|
11
|
+
from fast_agent.types import PromptMessageExtended, RequestParams
|
|
13
12
|
|
|
14
13
|
GROQ_BASE_URL = "https://api.groq.com/openai/v1"
|
|
15
14
|
DEFAULT_GROQ_MODEL = "moonshotai/kimi-k2-instruct"
|
|
@@ -19,7 +18,7 @@ DEFAULT_GROQ_MODEL = "moonshotai/kimi-k2-instruct"
|
|
|
19
18
|
### - deduplicating between this and the deepseek llm
|
|
20
19
|
|
|
21
20
|
|
|
22
|
-
class
|
|
21
|
+
class GroqLLM(OpenAILLM):
|
|
23
22
|
def __init__(self, *args, **kwargs) -> None:
|
|
24
23
|
super().__init__(*args, provider=Provider.GROQ, **kwargs)
|
|
25
24
|
|
|
@@ -37,10 +36,10 @@ class GroqAugmentedLLM(OpenAIAugmentedLLM):
|
|
|
37
36
|
|
|
38
37
|
async def _apply_prompt_provider_specific_structured(
|
|
39
38
|
self,
|
|
40
|
-
multipart_messages: List[
|
|
39
|
+
multipart_messages: List[PromptMessageExtended],
|
|
41
40
|
model: Type[ModelT],
|
|
42
41
|
request_params: RequestParams | None = None,
|
|
43
|
-
) -> Tuple[ModelT | None,
|
|
42
|
+
) -> Tuple[ModelT | None, PromptMessageExtended]: # noqa: F821
|
|
44
43
|
request_params = self.get_request_params(request_params)
|
|
45
44
|
|
|
46
45
|
assert self.default_request_params
|
|
@@ -51,9 +50,7 @@ class GroqAugmentedLLM(OpenAIAugmentedLLM):
|
|
|
51
50
|
|
|
52
51
|
# Create a cleaner format description from full schema
|
|
53
52
|
full_schema = model.model_json_schema()
|
|
54
|
-
format_description = self._schema_to_json_object(
|
|
55
|
-
full_schema, full_schema.get("$defs")
|
|
56
|
-
)
|
|
53
|
+
format_description = self._schema_to_json_object(full_schema, full_schema.get("$defs"))
|
|
57
54
|
|
|
58
55
|
multipart_messages[-1].add_text(
|
|
59
56
|
f"""YOU MUST RESPOND WITH A JSON OBJECT IN EXACTLY THIS FORMAT:
|
|
@@ -64,9 +61,10 @@ IMPORTANT RULES:
|
|
|
64
61
|
- Do NOT include "properties" or "schema" wrappers
|
|
65
62
|
- Do NOT use code fences or markdown
|
|
66
63
|
- The response must be valid JSON that matches the format above
|
|
67
|
-
- All required fields must be included"""
|
|
64
|
+
- All required fields must be included"""
|
|
65
|
+
)
|
|
68
66
|
|
|
69
|
-
result:
|
|
67
|
+
result: PromptMessageExtended = await self._apply_prompt_provider_specific(
|
|
70
68
|
multipart_messages, request_params
|
|
71
69
|
)
|
|
72
70
|
reasoning_mode: str | None = ModelDatabase.get_reasoning(llm_model)
|
mcp_agent/llm/providers/augmented_llm_openai.py → fast_agent/llm/provider/openai/llm_openai.py
RENAMED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
from typing import Dict, List
|
|
1
|
+
from typing import Any, Dict, List
|
|
2
2
|
|
|
3
|
+
from mcp import Tool
|
|
3
4
|
from mcp.types import (
|
|
4
5
|
CallToolRequest,
|
|
5
6
|
CallToolRequestParams,
|
|
6
|
-
CallToolResult,
|
|
7
7
|
ContentBlock,
|
|
8
8
|
TextContent,
|
|
9
9
|
)
|
|
@@ -18,23 +18,20 @@ from openai.types.chat import (
|
|
|
18
18
|
ChatCompletionToolParam,
|
|
19
19
|
)
|
|
20
20
|
from pydantic_core import from_json
|
|
21
|
-
from rich.text import Text
|
|
22
21
|
|
|
23
|
-
from
|
|
24
|
-
from
|
|
25
|
-
from
|
|
26
|
-
from
|
|
27
|
-
|
|
22
|
+
from fast_agent.core.exceptions import ProviderKeyError
|
|
23
|
+
from fast_agent.core.logging.logger import get_logger
|
|
24
|
+
from fast_agent.core.prompt import Prompt
|
|
25
|
+
from fast_agent.event_progress import ProgressAction
|
|
26
|
+
from fast_agent.llm.fastagent_llm import (
|
|
27
|
+
FastAgentLLM,
|
|
28
28
|
RequestParams,
|
|
29
29
|
)
|
|
30
|
-
from
|
|
31
|
-
from
|
|
32
|
-
from
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
from mcp_agent.llm.usage_tracking import TurnUsage
|
|
36
|
-
from mcp_agent.logging.logger import get_logger
|
|
37
|
-
from mcp_agent.mcp.prompt_message_multipart import PromptMessageMultipart
|
|
30
|
+
from fast_agent.llm.provider.openai.multipart_converter_openai import OpenAIConverter, OpenAIMessage
|
|
31
|
+
from fast_agent.llm.provider_types import Provider
|
|
32
|
+
from fast_agent.llm.usage_tracking import TurnUsage
|
|
33
|
+
from fast_agent.types import PromptMessageExtended
|
|
34
|
+
from fast_agent.types.llm_stop_reason import LlmStopReason
|
|
38
35
|
|
|
39
36
|
_logger = get_logger(__name__)
|
|
40
37
|
|
|
@@ -42,31 +39,22 @@ DEFAULT_OPENAI_MODEL = "gpt-4.1-mini"
|
|
|
42
39
|
DEFAULT_REASONING_EFFORT = "medium"
|
|
43
40
|
|
|
44
41
|
|
|
45
|
-
class
|
|
46
|
-
"""
|
|
47
|
-
The basic building block of agentic systems is an LLM enhanced with augmentations
|
|
48
|
-
such as retrieval, tools, and memory provided from a collection of MCP servers.
|
|
49
|
-
This implementation uses OpenAI's ChatCompletion as the LLM.
|
|
50
|
-
"""
|
|
51
|
-
|
|
42
|
+
class OpenAILLM(FastAgentLLM[ChatCompletionMessageParam, ChatCompletionMessage]):
|
|
52
43
|
# OpenAI-specific parameter exclusions
|
|
53
44
|
OPENAI_EXCLUDE_FIELDS = {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
45
|
+
FastAgentLLM.PARAM_MESSAGES,
|
|
46
|
+
FastAgentLLM.PARAM_MODEL,
|
|
47
|
+
FastAgentLLM.PARAM_MAX_TOKENS,
|
|
48
|
+
FastAgentLLM.PARAM_SYSTEM_PROMPT,
|
|
49
|
+
FastAgentLLM.PARAM_PARALLEL_TOOL_CALLS,
|
|
50
|
+
FastAgentLLM.PARAM_USE_HISTORY,
|
|
51
|
+
FastAgentLLM.PARAM_MAX_ITERATIONS,
|
|
52
|
+
FastAgentLLM.PARAM_TEMPLATE_VARS,
|
|
53
|
+
FastAgentLLM.PARAM_MCP_METADATA,
|
|
54
|
+
FastAgentLLM.PARAM_STOP_SEQUENCES,
|
|
63
55
|
}
|
|
64
56
|
|
|
65
57
|
def __init__(self, provider: Provider = Provider.OPENAI, *args, **kwargs) -> None:
|
|
66
|
-
# Set type_converter before calling super().__init__
|
|
67
|
-
if "type_converter" not in kwargs:
|
|
68
|
-
kwargs["type_converter"] = OpenAISamplingConverter
|
|
69
|
-
|
|
70
58
|
super().__init__(*args, provider=provider, **kwargs)
|
|
71
59
|
|
|
72
60
|
# Initialize logger with name if available
|
|
@@ -144,8 +132,14 @@ class OpenAIAugmentedLLM(AugmentedLLM[ChatCompletionMessageParam, ChatCompletion
|
|
|
144
132
|
# Use base class method for token estimation and progress emission
|
|
145
133
|
estimated_tokens = self._update_streaming_progress(content, model, estimated_tokens)
|
|
146
134
|
|
|
147
|
-
#
|
|
148
|
-
|
|
135
|
+
# Check if we hit the length limit to avoid LengthFinishReasonError
|
|
136
|
+
current_snapshot = state.current_completion_snapshot
|
|
137
|
+
if current_snapshot.choices and current_snapshot.choices[0].finish_reason == "length":
|
|
138
|
+
# Return the current snapshot directly to avoid exception
|
|
139
|
+
final_completion = current_snapshot
|
|
140
|
+
else:
|
|
141
|
+
# Get the final completion with usage data (may include structured output parsing)
|
|
142
|
+
final_completion = state.get_final_completion()
|
|
149
143
|
|
|
150
144
|
# Log final usage information
|
|
151
145
|
if hasattr(final_completion, "usage") and final_completion.usage:
|
|
@@ -296,9 +290,10 @@ class OpenAIAugmentedLLM(AugmentedLLM[ChatCompletionMessageParam, ChatCompletion
|
|
|
296
290
|
|
|
297
291
|
async def _openai_completion(
|
|
298
292
|
self,
|
|
299
|
-
message: OpenAIMessage,
|
|
293
|
+
message: List[OpenAIMessage] | None,
|
|
300
294
|
request_params: RequestParams | None = None,
|
|
301
|
-
|
|
295
|
+
tools: List[Tool] | None = None,
|
|
296
|
+
) -> PromptMessageExtended:
|
|
302
297
|
"""
|
|
303
298
|
Process a query using an LLM and available tools.
|
|
304
299
|
The default implementation uses OpenAI's ChatCompletion as the LLM.
|
|
@@ -307,7 +302,7 @@ class OpenAIAugmentedLLM(AugmentedLLM[ChatCompletionMessageParam, ChatCompletion
|
|
|
307
302
|
|
|
308
303
|
request_params = self.get_request_params(request_params=request_params)
|
|
309
304
|
|
|
310
|
-
|
|
305
|
+
response_content_blocks: List[ContentBlock] = []
|
|
311
306
|
model_name = self.default_request_params.model or DEFAULT_OPENAI_MODEL
|
|
312
307
|
|
|
313
308
|
# TODO -- move this in to agent context management / agent group handling
|
|
@@ -317,9 +312,9 @@ class OpenAIAugmentedLLM(AugmentedLLM[ChatCompletionMessageParam, ChatCompletion
|
|
|
317
312
|
messages.append(ChatCompletionSystemMessageParam(role="system", content=system_prompt))
|
|
318
313
|
|
|
319
314
|
messages.extend(self.history.get(include_completion_history=request_params.use_history))
|
|
320
|
-
|
|
315
|
+
if message is not None:
|
|
316
|
+
messages.extend(message)
|
|
321
317
|
|
|
322
|
-
response = await self.aggregator.list_tools()
|
|
323
318
|
available_tools: List[ChatCompletionToolParam] | None = [
|
|
324
319
|
{
|
|
325
320
|
"type": "function",
|
|
@@ -329,7 +324,7 @@ class OpenAIAugmentedLLM(AugmentedLLM[ChatCompletionMessageParam, ChatCompletion
|
|
|
329
324
|
"parameters": self.adjust_schema(tool.inputSchema),
|
|
330
325
|
},
|
|
331
326
|
}
|
|
332
|
-
for tool in
|
|
327
|
+
for tool in tools or []
|
|
333
328
|
]
|
|
334
329
|
|
|
335
330
|
if not available_tools:
|
|
@@ -339,154 +334,91 @@ class OpenAIAugmentedLLM(AugmentedLLM[ChatCompletionMessageParam, ChatCompletion
|
|
|
339
334
|
available_tools = []
|
|
340
335
|
|
|
341
336
|
# we do NOT send "stop sequences" as this causes errors with mutlimodal processing
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
# Use basic streaming API
|
|
349
|
-
stream = await self._openai_client().chat.completions.create(**arguments)
|
|
350
|
-
# Process the stream
|
|
351
|
-
response = await self._process_stream(stream, self.default_request_params.model)
|
|
352
|
-
# Track usage if response is valid and has usage data
|
|
353
|
-
if (
|
|
354
|
-
hasattr(response, "usage")
|
|
355
|
-
and response.usage
|
|
356
|
-
and not isinstance(response, BaseException)
|
|
357
|
-
):
|
|
358
|
-
try:
|
|
359
|
-
model_name = self.default_request_params.model or DEFAULT_OPENAI_MODEL
|
|
360
|
-
turn_usage = TurnUsage.from_openai(response.usage, model_name)
|
|
361
|
-
self._finalize_turn_usage(turn_usage)
|
|
362
|
-
except Exception as e:
|
|
363
|
-
self.logger.warning(f"Failed to track usage: {e}")
|
|
364
|
-
|
|
365
|
-
self.logger.debug(
|
|
366
|
-
"OpenAI completion response:",
|
|
367
|
-
data=response,
|
|
368
|
-
)
|
|
369
|
-
|
|
370
|
-
if isinstance(response, AuthenticationError):
|
|
371
|
-
raise ProviderKeyError(
|
|
372
|
-
"Rejected OpenAI API key",
|
|
373
|
-
"The configured OpenAI API key was rejected.\n"
|
|
374
|
-
"Please check that your API key is valid and not expired.",
|
|
375
|
-
) from response
|
|
376
|
-
elif isinstance(response, BaseException):
|
|
377
|
-
self.logger.error(f"Error: {response}")
|
|
378
|
-
break
|
|
379
|
-
|
|
380
|
-
if not response.choices or len(response.choices) == 0:
|
|
381
|
-
# No response from the model, we're done
|
|
382
|
-
break
|
|
383
|
-
|
|
384
|
-
choice = response.choices[0]
|
|
385
|
-
message = choice.message
|
|
386
|
-
# prep for image/audio gen models
|
|
387
|
-
if message.content:
|
|
388
|
-
responses.append(TextContent(type="text", text=message.content))
|
|
389
|
-
|
|
390
|
-
# ParsedChatCompletionMessage is compatible with ChatCompletionMessage
|
|
391
|
-
# since it inherits from it, so we can use it directly
|
|
392
|
-
# Convert to dict and remove None values
|
|
393
|
-
message_dict = message.model_dump()
|
|
394
|
-
message_dict = {k: v for k, v in message_dict.items() if v is not None}
|
|
395
|
-
if model_name in (
|
|
396
|
-
"deepseek-r1-distill-llama-70b",
|
|
397
|
-
"openai/gpt-oss-120b",
|
|
398
|
-
"openai/gpt-oss-20b",
|
|
399
|
-
):
|
|
400
|
-
message_dict.pop("reasoning", None)
|
|
401
|
-
message_dict.pop("channel", None)
|
|
402
|
-
|
|
403
|
-
messages.append(message_dict)
|
|
404
|
-
|
|
405
|
-
message_text = message.content
|
|
406
|
-
if await self._is_tool_stop_reason(choice.finish_reason) and message.tool_calls:
|
|
407
|
-
if message_text:
|
|
408
|
-
await self.show_assistant_message(
|
|
409
|
-
message_text,
|
|
410
|
-
message.tool_calls[
|
|
411
|
-
0
|
|
412
|
-
].function.name, # TODO support displaying multiple tool calls
|
|
413
|
-
)
|
|
414
|
-
else:
|
|
415
|
-
await self.show_assistant_message(
|
|
416
|
-
Text(
|
|
417
|
-
"the assistant requested tool calls",
|
|
418
|
-
style="dim green italic",
|
|
419
|
-
),
|
|
420
|
-
message.tool_calls[0].function.name,
|
|
421
|
-
)
|
|
337
|
+
arguments: dict[str, Any] = self._prepare_api_request(
|
|
338
|
+
messages, available_tools, request_params
|
|
339
|
+
)
|
|
340
|
+
if not self._reasoning and request_params.stopSequences:
|
|
341
|
+
arguments["stop"] = request_params.stopSequences
|
|
422
342
|
|
|
423
|
-
|
|
343
|
+
self.logger.debug(f"OpenAI completion requested for: {arguments}")
|
|
424
344
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
available_tools,
|
|
428
|
-
tool_call.function.name,
|
|
429
|
-
tool_call.function.arguments,
|
|
430
|
-
)
|
|
431
|
-
tool_call_request = CallToolRequest(
|
|
432
|
-
method="tools/call",
|
|
433
|
-
params=CallToolRequestParams(
|
|
434
|
-
name=tool_call.function.name,
|
|
435
|
-
arguments={}
|
|
436
|
-
if not tool_call.function.arguments
|
|
437
|
-
or tool_call.function.arguments.strip() == ""
|
|
438
|
-
else from_json(tool_call.function.arguments, allow_partial=True),
|
|
439
|
-
),
|
|
440
|
-
)
|
|
441
|
-
|
|
442
|
-
try:
|
|
443
|
-
result = await self.call_tool(tool_call_request, tool_call.id)
|
|
444
|
-
self.show_tool_result(result)
|
|
445
|
-
tool_results.append((tool_call.id, result))
|
|
446
|
-
responses.extend(result.content)
|
|
447
|
-
except Exception as e:
|
|
448
|
-
self.logger.error(f"Tool call {tool_call.id} failed with error: {e}")
|
|
449
|
-
# Still add the tool_call_id with an error result to prevent missing responses
|
|
450
|
-
error_result = CallToolResult(
|
|
451
|
-
content=[TextContent(type="text", text=f"Tool call failed: {str(e)}")]
|
|
452
|
-
)
|
|
453
|
-
tool_results.append((tool_call.id, error_result))
|
|
454
|
-
|
|
455
|
-
converted_messages = OpenAIConverter.convert_function_results_to_openai(
|
|
456
|
-
tool_results
|
|
457
|
-
)
|
|
458
|
-
messages.extend(converted_messages)
|
|
345
|
+
self._log_chat_progress(self.chat_turn(), model=self.default_request_params.model)
|
|
346
|
+
model_name = self.default_request_params.model or DEFAULT_OPENAI_MODEL
|
|
459
347
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
348
|
+
# Use basic streaming API
|
|
349
|
+
stream = await self._openai_client().chat.completions.create(**arguments)
|
|
350
|
+
# Process the stream
|
|
351
|
+
response = await self._process_stream(stream, model_name)
|
|
352
|
+
# Track usage if response is valid and has usage data
|
|
353
|
+
if (
|
|
354
|
+
hasattr(response, "usage")
|
|
355
|
+
and response.usage
|
|
356
|
+
and not isinstance(response, BaseException)
|
|
357
|
+
):
|
|
358
|
+
try:
|
|
359
|
+
turn_usage = TurnUsage.from_openai(response.usage, model_name)
|
|
360
|
+
self._finalize_turn_usage(turn_usage)
|
|
361
|
+
except Exception as e:
|
|
362
|
+
self.logger.warning(f"Failed to track usage: {e}")
|
|
363
|
+
|
|
364
|
+
self.logger.debug(
|
|
365
|
+
"OpenAI completion response:",
|
|
366
|
+
data=response,
|
|
367
|
+
)
|
|
476
368
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
369
|
+
if isinstance(response, AuthenticationError):
|
|
370
|
+
raise ProviderKeyError(
|
|
371
|
+
"Rejected OpenAI API key",
|
|
372
|
+
"The configured OpenAI API key was rejected.\n"
|
|
373
|
+
"Please check that your API key is valid and not expired.",
|
|
374
|
+
) from response
|
|
375
|
+
elif isinstance(response, BaseException):
|
|
376
|
+
self.logger.error(f"Error: {response}")
|
|
377
|
+
|
|
378
|
+
choice = response.choices[0]
|
|
379
|
+
message = choice.message
|
|
380
|
+
# prep for image/audio gen models
|
|
381
|
+
if message.content:
|
|
382
|
+
response_content_blocks.append(TextContent(type="text", text=message.content))
|
|
383
|
+
|
|
384
|
+
# ParsedChatCompletionMessage is compatible with ChatCompletionMessage
|
|
385
|
+
# since it inherits from it, so we can use it directly
|
|
386
|
+
# Convert to dict and remove None values
|
|
387
|
+
message_dict = message.model_dump()
|
|
388
|
+
message_dict = {k: v for k, v in message_dict.items() if v is not None}
|
|
389
|
+
if model_name in (
|
|
390
|
+
"deepseek-r1-distill-llama-70b",
|
|
391
|
+
"openai/gpt-oss-120b",
|
|
392
|
+
"openai/gpt-oss-20b",
|
|
393
|
+
):
|
|
394
|
+
message_dict.pop("reasoning", None)
|
|
395
|
+
message_dict.pop("channel", None)
|
|
396
|
+
|
|
397
|
+
messages.append(message_dict)
|
|
398
|
+
stop_reason = LlmStopReason.END_TURN
|
|
399
|
+
requested_tool_calls: Dict[str, CallToolRequest] | None = None
|
|
400
|
+
if await self._is_tool_stop_reason(choice.finish_reason) and message.tool_calls:
|
|
401
|
+
requested_tool_calls = {}
|
|
402
|
+
stop_reason = LlmStopReason.TOOL_USE
|
|
403
|
+
for tool_call in message.tool_calls:
|
|
404
|
+
tool_call_request = CallToolRequest(
|
|
405
|
+
method="tools/call",
|
|
406
|
+
params=CallToolRequestParams(
|
|
407
|
+
name=tool_call.function.name,
|
|
408
|
+
arguments={}
|
|
409
|
+
if not tool_call.function.arguments
|
|
410
|
+
or tool_call.function.arguments.strip() == ""
|
|
411
|
+
else from_json(tool_call.function.arguments, allow_partial=True),
|
|
412
|
+
),
|
|
483
413
|
)
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
414
|
+
requested_tool_calls[tool_call.id] = tool_call_request
|
|
415
|
+
elif choice.finish_reason == "length":
|
|
416
|
+
stop_reason = LlmStopReason.MAX_TOKENS
|
|
417
|
+
# We have reached the max tokens limit
|
|
418
|
+
self.logger.debug(" Stopping because finish_reason is 'length'")
|
|
419
|
+
elif choice.finish_reason == "content_filter":
|
|
420
|
+
stop_reason = LlmStopReason.SAFETY
|
|
421
|
+
self.logger.debug(" Stopping because finish_reason is 'content_filter'")
|
|
490
422
|
|
|
491
423
|
if request_params.use_history:
|
|
492
424
|
# Get current prompt messages
|
|
@@ -502,19 +434,21 @@ class OpenAIAugmentedLLM(AugmentedLLM[ChatCompletionMessageParam, ChatCompletion
|
|
|
502
434
|
|
|
503
435
|
self._log_chat_finished(model=self.default_request_params.model)
|
|
504
436
|
|
|
505
|
-
return
|
|
437
|
+
return Prompt.assistant(
|
|
438
|
+
*response_content_blocks, stop_reason=stop_reason, tool_calls=requested_tool_calls
|
|
439
|
+
)
|
|
506
440
|
|
|
507
441
|
async def _is_tool_stop_reason(self, finish_reason: str) -> bool:
|
|
508
442
|
return True
|
|
509
443
|
|
|
510
444
|
async def _apply_prompt_provider_specific(
|
|
511
445
|
self,
|
|
512
|
-
multipart_messages: List["
|
|
446
|
+
multipart_messages: List["PromptMessageExtended"],
|
|
513
447
|
request_params: RequestParams | None = None,
|
|
448
|
+
tools: List[Tool] | None = None,
|
|
514
449
|
is_template: bool = False,
|
|
515
|
-
) ->
|
|
450
|
+
) -> PromptMessageExtended:
|
|
516
451
|
# Reset tool call counter for new turn
|
|
517
|
-
self._reset_turn_tool_calls()
|
|
518
452
|
|
|
519
453
|
last_message = multipart_messages[-1]
|
|
520
454
|
|
|
@@ -525,29 +459,21 @@ class OpenAIAugmentedLLM(AugmentedLLM[ChatCompletionMessageParam, ChatCompletion
|
|
|
525
459
|
)
|
|
526
460
|
converted = []
|
|
527
461
|
for msg in messages_to_add:
|
|
528
|
-
|
|
462
|
+
# convert_to_openai now returns a list of messages
|
|
463
|
+
converted.extend(OpenAIConverter.convert_to_openai(msg))
|
|
529
464
|
|
|
530
|
-
# TODO -- this looks like a defect from previous apply_prompt implementation.
|
|
531
465
|
self.history.extend(converted, is_prompt=is_template)
|
|
532
466
|
|
|
533
467
|
if "assistant" == last_message.role:
|
|
534
468
|
return last_message
|
|
535
469
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
request_params,
|
|
541
|
-
)
|
|
542
|
-
return Prompt.assistant(*responses)
|
|
543
|
-
|
|
544
|
-
async def pre_tool_call(self, tool_call_id: str | None, request: CallToolRequest):
|
|
545
|
-
return request
|
|
470
|
+
converted_messages = OpenAIConverter.convert_to_openai(last_message)
|
|
471
|
+
if not converted_messages:
|
|
472
|
+
# Fallback for empty conversion
|
|
473
|
+
converted_messages = [{"role": "user", "content": ""}]
|
|
546
474
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
):
|
|
550
|
-
return result
|
|
475
|
+
# Call completion without additional messages (all messages are now in history)
|
|
476
|
+
return await self._openai_completion(converted_messages, request_params, tools)
|
|
551
477
|
|
|
552
478
|
def _prepare_api_request(
|
|
553
479
|
self, messages, tools: List[ChatCompletionToolParam] | None, request_params: RequestParams
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import os
|
|
2
2
|
|
|
3
|
-
from
|
|
4
|
-
from
|
|
5
|
-
from
|
|
3
|
+
from fast_agent.llm.provider.openai.llm_openai import OpenAILLM
|
|
4
|
+
from fast_agent.llm.provider_types import Provider
|
|
5
|
+
from fast_agent.types import RequestParams
|
|
6
6
|
|
|
7
7
|
DEFAULT_OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1"
|
|
8
8
|
# No single default model for OpenRouter, users must specify full path
|
|
9
9
|
DEFAULT_OPENROUTER_MODEL = None
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
class
|
|
12
|
+
class OpenRouterLLM(OpenAILLM):
|
|
13
13
|
"""Augmented LLM provider for OpenRouter, using an OpenAI-compatible API."""
|
|
14
14
|
|
|
15
15
|
def __init__(self, *args, **kwargs) -> None:
|
|
@@ -19,7 +19,7 @@ class OpenRouterAugmentedLLM(OpenAIAugmentedLLM):
|
|
|
19
19
|
"""Initialize OpenRouter-specific default parameters."""
|
|
20
20
|
# Get base defaults from parent (includes ModelDatabase lookup)
|
|
21
21
|
base_params = super()._initialize_default_params(kwargs)
|
|
22
|
-
|
|
22
|
+
|
|
23
23
|
# Override with OpenRouter-specific settings
|
|
24
24
|
# OpenRouter model names include the provider, e.g., "google/gemini-flash-1.5"
|
|
25
25
|
# The model should be passed in the 'model' kwarg during factory creation.
|
|
@@ -28,7 +28,7 @@ class OpenRouterAugmentedLLM(OpenAIAugmentedLLM):
|
|
|
28
28
|
base_params.model = chosen_model
|
|
29
29
|
# If it's still None here, it indicates an issue upstream (factory or user input).
|
|
30
30
|
# However, the base class _get_model handles the error if model is None.
|
|
31
|
-
|
|
31
|
+
|
|
32
32
|
return base_params
|
|
33
33
|
|
|
34
34
|
def _base_url(self) -> str:
|