fast-agent-mcp 0.4.7__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.
- fast_agent/__init__.py +183 -0
- fast_agent/acp/__init__.py +19 -0
- fast_agent/acp/acp_aware_mixin.py +304 -0
- fast_agent/acp/acp_context.py +437 -0
- fast_agent/acp/content_conversion.py +136 -0
- fast_agent/acp/filesystem_runtime.py +427 -0
- fast_agent/acp/permission_store.py +269 -0
- fast_agent/acp/server/__init__.py +5 -0
- fast_agent/acp/server/agent_acp_server.py +1472 -0
- fast_agent/acp/slash_commands.py +1050 -0
- fast_agent/acp/terminal_runtime.py +408 -0
- fast_agent/acp/tool_permission_adapter.py +125 -0
- fast_agent/acp/tool_permissions.py +474 -0
- fast_agent/acp/tool_progress.py +814 -0
- fast_agent/agents/__init__.py +85 -0
- fast_agent/agents/agent_types.py +64 -0
- fast_agent/agents/llm_agent.py +350 -0
- fast_agent/agents/llm_decorator.py +1139 -0
- fast_agent/agents/mcp_agent.py +1337 -0
- fast_agent/agents/tool_agent.py +271 -0
- fast_agent/agents/workflow/agents_as_tools_agent.py +849 -0
- fast_agent/agents/workflow/chain_agent.py +212 -0
- fast_agent/agents/workflow/evaluator_optimizer.py +380 -0
- fast_agent/agents/workflow/iterative_planner.py +652 -0
- fast_agent/agents/workflow/maker_agent.py +379 -0
- fast_agent/agents/workflow/orchestrator_models.py +218 -0
- fast_agent/agents/workflow/orchestrator_prompts.py +248 -0
- fast_agent/agents/workflow/parallel_agent.py +250 -0
- fast_agent/agents/workflow/router_agent.py +353 -0
- fast_agent/cli/__init__.py +0 -0
- fast_agent/cli/__main__.py +73 -0
- fast_agent/cli/commands/acp.py +159 -0
- fast_agent/cli/commands/auth.py +404 -0
- fast_agent/cli/commands/check_config.py +783 -0
- fast_agent/cli/commands/go.py +514 -0
- fast_agent/cli/commands/quickstart.py +557 -0
- fast_agent/cli/commands/serve.py +143 -0
- fast_agent/cli/commands/server_helpers.py +114 -0
- fast_agent/cli/commands/setup.py +174 -0
- fast_agent/cli/commands/url_parser.py +190 -0
- fast_agent/cli/constants.py +40 -0
- fast_agent/cli/main.py +115 -0
- fast_agent/cli/terminal.py +24 -0
- fast_agent/config.py +798 -0
- fast_agent/constants.py +41 -0
- fast_agent/context.py +279 -0
- fast_agent/context_dependent.py +50 -0
- fast_agent/core/__init__.py +92 -0
- fast_agent/core/agent_app.py +448 -0
- fast_agent/core/core_app.py +137 -0
- fast_agent/core/direct_decorators.py +784 -0
- fast_agent/core/direct_factory.py +620 -0
- fast_agent/core/error_handling.py +27 -0
- fast_agent/core/exceptions.py +90 -0
- fast_agent/core/executor/__init__.py +0 -0
- fast_agent/core/executor/executor.py +280 -0
- fast_agent/core/executor/task_registry.py +32 -0
- fast_agent/core/executor/workflow_signal.py +324 -0
- fast_agent/core/fastagent.py +1186 -0
- fast_agent/core/logging/__init__.py +5 -0
- fast_agent/core/logging/events.py +138 -0
- fast_agent/core/logging/json_serializer.py +164 -0
- fast_agent/core/logging/listeners.py +309 -0
- fast_agent/core/logging/logger.py +278 -0
- fast_agent/core/logging/transport.py +481 -0
- fast_agent/core/prompt.py +9 -0
- fast_agent/core/prompt_templates.py +183 -0
- fast_agent/core/validation.py +326 -0
- fast_agent/event_progress.py +62 -0
- fast_agent/history/history_exporter.py +49 -0
- fast_agent/human_input/__init__.py +47 -0
- fast_agent/human_input/elicitation_handler.py +123 -0
- fast_agent/human_input/elicitation_state.py +33 -0
- fast_agent/human_input/form_elements.py +59 -0
- fast_agent/human_input/form_fields.py +256 -0
- fast_agent/human_input/simple_form.py +113 -0
- fast_agent/human_input/types.py +40 -0
- fast_agent/interfaces.py +310 -0
- fast_agent/llm/__init__.py +9 -0
- fast_agent/llm/cancellation.py +22 -0
- fast_agent/llm/fastagent_llm.py +931 -0
- fast_agent/llm/internal/passthrough.py +161 -0
- fast_agent/llm/internal/playback.py +129 -0
- fast_agent/llm/internal/silent.py +41 -0
- fast_agent/llm/internal/slow.py +38 -0
- fast_agent/llm/memory.py +275 -0
- fast_agent/llm/model_database.py +490 -0
- fast_agent/llm/model_factory.py +388 -0
- fast_agent/llm/model_info.py +102 -0
- fast_agent/llm/prompt_utils.py +155 -0
- fast_agent/llm/provider/anthropic/anthropic_utils.py +84 -0
- fast_agent/llm/provider/anthropic/cache_planner.py +56 -0
- fast_agent/llm/provider/anthropic/llm_anthropic.py +796 -0
- fast_agent/llm/provider/anthropic/multipart_converter_anthropic.py +462 -0
- fast_agent/llm/provider/bedrock/bedrock_utils.py +218 -0
- fast_agent/llm/provider/bedrock/llm_bedrock.py +2207 -0
- fast_agent/llm/provider/bedrock/multipart_converter_bedrock.py +84 -0
- fast_agent/llm/provider/google/google_converter.py +466 -0
- fast_agent/llm/provider/google/llm_google_native.py +681 -0
- fast_agent/llm/provider/openai/llm_aliyun.py +31 -0
- fast_agent/llm/provider/openai/llm_azure.py +143 -0
- fast_agent/llm/provider/openai/llm_deepseek.py +76 -0
- fast_agent/llm/provider/openai/llm_generic.py +35 -0
- fast_agent/llm/provider/openai/llm_google_oai.py +32 -0
- fast_agent/llm/provider/openai/llm_groq.py +42 -0
- fast_agent/llm/provider/openai/llm_huggingface.py +85 -0
- fast_agent/llm/provider/openai/llm_openai.py +1195 -0
- fast_agent/llm/provider/openai/llm_openai_compatible.py +138 -0
- fast_agent/llm/provider/openai/llm_openrouter.py +45 -0
- fast_agent/llm/provider/openai/llm_tensorzero_openai.py +128 -0
- fast_agent/llm/provider/openai/llm_xai.py +38 -0
- fast_agent/llm/provider/openai/multipart_converter_openai.py +561 -0
- fast_agent/llm/provider/openai/openai_multipart.py +169 -0
- fast_agent/llm/provider/openai/openai_utils.py +67 -0
- fast_agent/llm/provider/openai/responses.py +133 -0
- fast_agent/llm/provider_key_manager.py +139 -0
- fast_agent/llm/provider_types.py +34 -0
- fast_agent/llm/request_params.py +61 -0
- fast_agent/llm/sampling_converter.py +98 -0
- fast_agent/llm/stream_types.py +9 -0
- fast_agent/llm/usage_tracking.py +445 -0
- fast_agent/mcp/__init__.py +56 -0
- fast_agent/mcp/common.py +26 -0
- fast_agent/mcp/elicitation_factory.py +84 -0
- fast_agent/mcp/elicitation_handlers.py +164 -0
- fast_agent/mcp/gen_client.py +83 -0
- fast_agent/mcp/helpers/__init__.py +36 -0
- fast_agent/mcp/helpers/content_helpers.py +352 -0
- fast_agent/mcp/helpers/server_config_helpers.py +25 -0
- fast_agent/mcp/hf_auth.py +147 -0
- fast_agent/mcp/interfaces.py +92 -0
- fast_agent/mcp/logger_textio.py +108 -0
- fast_agent/mcp/mcp_agent_client_session.py +411 -0
- fast_agent/mcp/mcp_aggregator.py +2175 -0
- fast_agent/mcp/mcp_connection_manager.py +723 -0
- fast_agent/mcp/mcp_content.py +262 -0
- fast_agent/mcp/mime_utils.py +108 -0
- fast_agent/mcp/oauth_client.py +509 -0
- fast_agent/mcp/prompt.py +159 -0
- fast_agent/mcp/prompt_message_extended.py +155 -0
- fast_agent/mcp/prompt_render.py +84 -0
- fast_agent/mcp/prompt_serialization.py +580 -0
- fast_agent/mcp/prompts/__init__.py +0 -0
- fast_agent/mcp/prompts/__main__.py +7 -0
- fast_agent/mcp/prompts/prompt_constants.py +18 -0
- fast_agent/mcp/prompts/prompt_helpers.py +238 -0
- fast_agent/mcp/prompts/prompt_load.py +186 -0
- fast_agent/mcp/prompts/prompt_server.py +552 -0
- fast_agent/mcp/prompts/prompt_template.py +438 -0
- fast_agent/mcp/resource_utils.py +215 -0
- fast_agent/mcp/sampling.py +200 -0
- fast_agent/mcp/server/__init__.py +4 -0
- fast_agent/mcp/server/agent_server.py +613 -0
- fast_agent/mcp/skybridge.py +44 -0
- fast_agent/mcp/sse_tracking.py +287 -0
- fast_agent/mcp/stdio_tracking_simple.py +59 -0
- fast_agent/mcp/streamable_http_tracking.py +309 -0
- fast_agent/mcp/tool_execution_handler.py +137 -0
- fast_agent/mcp/tool_permission_handler.py +88 -0
- fast_agent/mcp/transport_tracking.py +634 -0
- fast_agent/mcp/types.py +24 -0
- fast_agent/mcp/ui_agent.py +48 -0
- fast_agent/mcp/ui_mixin.py +209 -0
- fast_agent/mcp_server_registry.py +89 -0
- fast_agent/py.typed +0 -0
- fast_agent/resources/examples/data-analysis/analysis-campaign.py +189 -0
- fast_agent/resources/examples/data-analysis/analysis.py +68 -0
- fast_agent/resources/examples/data-analysis/fastagent.config.yaml +41 -0
- fast_agent/resources/examples/data-analysis/mount-point/WA_Fn-UseC_-HR-Employee-Attrition.csv +1471 -0
- fast_agent/resources/examples/mcp/elicitations/elicitation_account_server.py +88 -0
- fast_agent/resources/examples/mcp/elicitations/elicitation_forms_server.py +297 -0
- fast_agent/resources/examples/mcp/elicitations/elicitation_game_server.py +164 -0
- fast_agent/resources/examples/mcp/elicitations/fastagent.config.yaml +35 -0
- fast_agent/resources/examples/mcp/elicitations/fastagent.secrets.yaml.example +17 -0
- fast_agent/resources/examples/mcp/elicitations/forms_demo.py +107 -0
- fast_agent/resources/examples/mcp/elicitations/game_character.py +65 -0
- fast_agent/resources/examples/mcp/elicitations/game_character_handler.py +256 -0
- fast_agent/resources/examples/mcp/elicitations/tool_call.py +21 -0
- fast_agent/resources/examples/mcp/state-transfer/agent_one.py +18 -0
- fast_agent/resources/examples/mcp/state-transfer/agent_two.py +18 -0
- fast_agent/resources/examples/mcp/state-transfer/fastagent.config.yaml +27 -0
- fast_agent/resources/examples/mcp/state-transfer/fastagent.secrets.yaml.example +15 -0
- fast_agent/resources/examples/researcher/fastagent.config.yaml +61 -0
- fast_agent/resources/examples/researcher/researcher-eval.py +53 -0
- fast_agent/resources/examples/researcher/researcher-imp.py +189 -0
- fast_agent/resources/examples/researcher/researcher.py +36 -0
- fast_agent/resources/examples/tensorzero/.env.sample +2 -0
- fast_agent/resources/examples/tensorzero/Makefile +31 -0
- fast_agent/resources/examples/tensorzero/README.md +56 -0
- fast_agent/resources/examples/tensorzero/agent.py +35 -0
- fast_agent/resources/examples/tensorzero/demo_images/clam.jpg +0 -0
- fast_agent/resources/examples/tensorzero/demo_images/crab.png +0 -0
- fast_agent/resources/examples/tensorzero/demo_images/shrimp.png +0 -0
- fast_agent/resources/examples/tensorzero/docker-compose.yml +105 -0
- fast_agent/resources/examples/tensorzero/fastagent.config.yaml +19 -0
- fast_agent/resources/examples/tensorzero/image_demo.py +67 -0
- fast_agent/resources/examples/tensorzero/mcp_server/Dockerfile +25 -0
- fast_agent/resources/examples/tensorzero/mcp_server/entrypoint.sh +35 -0
- fast_agent/resources/examples/tensorzero/mcp_server/mcp_server.py +31 -0
- fast_agent/resources/examples/tensorzero/mcp_server/pyproject.toml +11 -0
- fast_agent/resources/examples/tensorzero/simple_agent.py +25 -0
- fast_agent/resources/examples/tensorzero/tensorzero_config/system_schema.json +29 -0
- fast_agent/resources/examples/tensorzero/tensorzero_config/system_template.minijinja +11 -0
- fast_agent/resources/examples/tensorzero/tensorzero_config/tensorzero.toml +35 -0
- fast_agent/resources/examples/workflows/agents_as_tools_extended.py +73 -0
- fast_agent/resources/examples/workflows/agents_as_tools_simple.py +50 -0
- fast_agent/resources/examples/workflows/chaining.py +37 -0
- fast_agent/resources/examples/workflows/evaluator.py +77 -0
- fast_agent/resources/examples/workflows/fastagent.config.yaml +26 -0
- fast_agent/resources/examples/workflows/graded_report.md +89 -0
- fast_agent/resources/examples/workflows/human_input.py +28 -0
- fast_agent/resources/examples/workflows/maker.py +156 -0
- fast_agent/resources/examples/workflows/orchestrator.py +70 -0
- fast_agent/resources/examples/workflows/parallel.py +56 -0
- fast_agent/resources/examples/workflows/router.py +69 -0
- fast_agent/resources/examples/workflows/short_story.md +13 -0
- fast_agent/resources/examples/workflows/short_story.txt +19 -0
- fast_agent/resources/setup/.gitignore +30 -0
- fast_agent/resources/setup/agent.py +28 -0
- fast_agent/resources/setup/fastagent.config.yaml +65 -0
- fast_agent/resources/setup/fastagent.secrets.yaml.example +38 -0
- fast_agent/resources/setup/pyproject.toml.tmpl +23 -0
- fast_agent/skills/__init__.py +9 -0
- fast_agent/skills/registry.py +235 -0
- fast_agent/tools/elicitation.py +369 -0
- fast_agent/tools/shell_runtime.py +402 -0
- fast_agent/types/__init__.py +59 -0
- fast_agent/types/conversation_summary.py +294 -0
- fast_agent/types/llm_stop_reason.py +78 -0
- fast_agent/types/message_search.py +249 -0
- fast_agent/ui/__init__.py +38 -0
- fast_agent/ui/console.py +59 -0
- fast_agent/ui/console_display.py +1080 -0
- fast_agent/ui/elicitation_form.py +946 -0
- fast_agent/ui/elicitation_style.py +59 -0
- fast_agent/ui/enhanced_prompt.py +1400 -0
- fast_agent/ui/history_display.py +734 -0
- fast_agent/ui/interactive_prompt.py +1199 -0
- fast_agent/ui/markdown_helpers.py +104 -0
- fast_agent/ui/markdown_truncator.py +1004 -0
- fast_agent/ui/mcp_display.py +857 -0
- fast_agent/ui/mcp_ui_utils.py +235 -0
- fast_agent/ui/mermaid_utils.py +169 -0
- fast_agent/ui/message_primitives.py +50 -0
- fast_agent/ui/notification_tracker.py +205 -0
- fast_agent/ui/plain_text_truncator.py +68 -0
- fast_agent/ui/progress_display.py +10 -0
- fast_agent/ui/rich_progress.py +195 -0
- fast_agent/ui/streaming.py +774 -0
- fast_agent/ui/streaming_buffer.py +449 -0
- fast_agent/ui/tool_display.py +422 -0
- fast_agent/ui/usage_display.py +204 -0
- fast_agent/utils/__init__.py +5 -0
- fast_agent/utils/reasoning_stream_parser.py +77 -0
- fast_agent/utils/time.py +22 -0
- fast_agent/workflow_telemetry.py +261 -0
- fast_agent_mcp-0.4.7.dist-info/METADATA +788 -0
- fast_agent_mcp-0.4.7.dist-info/RECORD +261 -0
- fast_agent_mcp-0.4.7.dist-info/WHEEL +4 -0
- fast_agent_mcp-0.4.7.dist-info/entry_points.txt +7 -0
- fast_agent_mcp-0.4.7.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
from typing import Type
|
|
2
|
+
|
|
3
|
+
from fast_agent.interfaces import ModelT
|
|
4
|
+
from fast_agent.llm.model_database import ModelDatabase
|
|
5
|
+
from fast_agent.llm.provider.openai.llm_openai import OpenAILLM
|
|
6
|
+
from fast_agent.mcp.helpers.content_helpers import split_thinking_content
|
|
7
|
+
from fast_agent.types import PromptMessageExtended, RequestParams
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class OpenAICompatibleLLM(OpenAILLM):
|
|
11
|
+
"""Shared helpers for OpenAI-compatible providers that need structured output prompting."""
|
|
12
|
+
|
|
13
|
+
STRUCTURED_PROMPT_TEMPLATE = """YOU MUST RESPOND WITH A JSON OBJECT IN EXACTLY THIS FORMAT:
|
|
14
|
+
{format_description}
|
|
15
|
+
|
|
16
|
+
IMPORTANT RULES:
|
|
17
|
+
- Respond ONLY with the JSON object, no other text
|
|
18
|
+
- Do NOT include "properties" or "schema" wrappers
|
|
19
|
+
- Do NOT use code fences or markdown
|
|
20
|
+
- The response must be valid JSON that matches the format above
|
|
21
|
+
- All required fields must be included"""
|
|
22
|
+
|
|
23
|
+
async def _apply_prompt_provider_specific_structured(
|
|
24
|
+
self,
|
|
25
|
+
multipart_messages: list[PromptMessageExtended],
|
|
26
|
+
model: Type[ModelT],
|
|
27
|
+
request_params: RequestParams | None = None,
|
|
28
|
+
) -> tuple[ModelT | None, PromptMessageExtended]:
|
|
29
|
+
if not self._supports_structured_prompt():
|
|
30
|
+
return await super()._apply_prompt_provider_specific_structured(
|
|
31
|
+
multipart_messages, model, request_params
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
request_params = self.get_request_params(request_params)
|
|
35
|
+
|
|
36
|
+
prompt_format = self._structured_prompt_format()
|
|
37
|
+
if prompt_format == "json_object" and not request_params.response_format:
|
|
38
|
+
request_params.response_format = {"type": "json_object"}
|
|
39
|
+
|
|
40
|
+
instructions = self._build_structured_prompt_instruction(model)
|
|
41
|
+
if instructions:
|
|
42
|
+
multipart_messages[-1].add_text(instructions)
|
|
43
|
+
|
|
44
|
+
return await super()._apply_prompt_provider_specific_structured(
|
|
45
|
+
multipart_messages, model, request_params
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
def _supports_structured_prompt(self) -> bool:
|
|
49
|
+
"""Allow subclasses to opt-out of shared structured prompting."""
|
|
50
|
+
return True
|
|
51
|
+
|
|
52
|
+
def _structured_prompt_format(self) -> str | None:
|
|
53
|
+
"""Return the response_format type this provider expects."""
|
|
54
|
+
return "json_object"
|
|
55
|
+
|
|
56
|
+
def _build_structured_prompt_instruction(self, model: Type[ModelT]) -> str | None:
|
|
57
|
+
template = self._structured_prompt_template()
|
|
58
|
+
if not template:
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
schema = model.model_json_schema()
|
|
62
|
+
format_description = self._schema_to_json_object(schema, schema.get("$defs"))
|
|
63
|
+
return template.format(format_description=format_description)
|
|
64
|
+
|
|
65
|
+
def _structured_prompt_template(self) -> str | None:
|
|
66
|
+
return self.STRUCTURED_PROMPT_TEMPLATE
|
|
67
|
+
|
|
68
|
+
def _prepare_structured_text(self, text: str) -> str:
|
|
69
|
+
reasoning_mode = self._structured_reasoning_mode()
|
|
70
|
+
if reasoning_mode == "tags":
|
|
71
|
+
thinking, trimmed = split_thinking_content(text)
|
|
72
|
+
if thinking is None:
|
|
73
|
+
closing_tag = "</think>"
|
|
74
|
+
closing_index = text.find(closing_tag)
|
|
75
|
+
if closing_index != -1:
|
|
76
|
+
trimmed = text[closing_index + len(closing_tag) :].lstrip()
|
|
77
|
+
else:
|
|
78
|
+
trimmed = text
|
|
79
|
+
return trimmed
|
|
80
|
+
|
|
81
|
+
if "</think>" in text:
|
|
82
|
+
logger = getattr(self, "logger", None)
|
|
83
|
+
if logger:
|
|
84
|
+
logger.warning(
|
|
85
|
+
"Model emitted reasoning tags without 'tags' reasoning mode",
|
|
86
|
+
data={
|
|
87
|
+
"model": getattr(self.default_request_params, "model", None),
|
|
88
|
+
"text_preview": text[:200],
|
|
89
|
+
},
|
|
90
|
+
)
|
|
91
|
+
return text
|
|
92
|
+
|
|
93
|
+
def _structured_reasoning_mode(self) -> str | None:
|
|
94
|
+
model_name = self.default_request_params.model if self.default_request_params else None
|
|
95
|
+
return ModelDatabase.get_reasoning(model_name) if model_name else None
|
|
96
|
+
|
|
97
|
+
def _schema_to_json_object(
|
|
98
|
+
self, schema: dict, defs: dict | None = None, visited: set | None = None
|
|
99
|
+
) -> str:
|
|
100
|
+
"""Render a compact, human-friendly shape of the JSON schema."""
|
|
101
|
+
visited = visited or set()
|
|
102
|
+
|
|
103
|
+
if id(schema) in visited:
|
|
104
|
+
return '"<recursive>"'
|
|
105
|
+
visited.add(id(schema))
|
|
106
|
+
|
|
107
|
+
if "$ref" in schema:
|
|
108
|
+
ref = schema.get("$ref", "")
|
|
109
|
+
if ref.startswith("#/$defs/"):
|
|
110
|
+
target = ref.split("/")[-1]
|
|
111
|
+
if defs and target in defs:
|
|
112
|
+
return self._schema_to_json_object(defs[target], defs, visited)
|
|
113
|
+
return f'"<ref:{ref}>"'
|
|
114
|
+
|
|
115
|
+
schema_type = schema.get("type")
|
|
116
|
+
description = schema.get("description", "")
|
|
117
|
+
required = schema.get("required", [])
|
|
118
|
+
|
|
119
|
+
if schema_type == "object":
|
|
120
|
+
props = schema.get("properties", {})
|
|
121
|
+
result = "{\n"
|
|
122
|
+
for prop_name, prop_schema in props.items():
|
|
123
|
+
is_required = prop_name in required
|
|
124
|
+
prop_str = self._schema_to_json_object(prop_schema, defs, visited)
|
|
125
|
+
if is_required:
|
|
126
|
+
prop_str += " // REQUIRED"
|
|
127
|
+
result += f' "{prop_name}": {prop_str},\n'
|
|
128
|
+
result += "}"
|
|
129
|
+
return result
|
|
130
|
+
elif schema_type == "array":
|
|
131
|
+
items = schema.get("items", {})
|
|
132
|
+
items_str = self._schema_to_json_object(items, defs, visited)
|
|
133
|
+
return f"[{items_str}]"
|
|
134
|
+
elif schema_type:
|
|
135
|
+
comment = f" // {description}" if description else ""
|
|
136
|
+
return f'"{schema_type}"' + comment
|
|
137
|
+
|
|
138
|
+
return '"<unknown>"'
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
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
|
+
|
|
7
|
+
DEFAULT_OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1"
|
|
8
|
+
# No single default model for OpenRouter, users must specify full path
|
|
9
|
+
DEFAULT_OPENROUTER_MODEL = None
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class OpenRouterLLM(OpenAILLM):
|
|
13
|
+
"""Augmented LLM provider for OpenRouter, using an OpenAI-compatible API."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
16
|
+
super().__init__(*args, provider=Provider.OPENROUTER, **kwargs)
|
|
17
|
+
|
|
18
|
+
def _initialize_default_params(self, kwargs: dict) -> RequestParams:
|
|
19
|
+
"""Initialize OpenRouter-specific default parameters."""
|
|
20
|
+
# Get base defaults from parent (includes ModelDatabase lookup)
|
|
21
|
+
base_params = super()._initialize_default_params(kwargs)
|
|
22
|
+
|
|
23
|
+
# Override with OpenRouter-specific settings
|
|
24
|
+
# OpenRouter model names include the provider, e.g., "google/gemini-flash-1.5"
|
|
25
|
+
# The model should be passed in the 'model' kwarg during factory creation.
|
|
26
|
+
chosen_model = kwargs.get("model", DEFAULT_OPENROUTER_MODEL)
|
|
27
|
+
if chosen_model:
|
|
28
|
+
base_params.model = chosen_model
|
|
29
|
+
# If it's still None here, it indicates an issue upstream (factory or user input).
|
|
30
|
+
# However, the base class _get_model handles the error if model is None.
|
|
31
|
+
|
|
32
|
+
return base_params
|
|
33
|
+
|
|
34
|
+
def _base_url(self) -> str:
|
|
35
|
+
"""Retrieve the OpenRouter base URL from config or use the default."""
|
|
36
|
+
base_url = os.getenv("OPENROUTER_BASE_URL", DEFAULT_OPENROUTER_BASE_URL) # Default
|
|
37
|
+
config = self.context.config
|
|
38
|
+
|
|
39
|
+
# Check config file for override
|
|
40
|
+
if config and hasattr(config, "openrouter") and config.openrouter:
|
|
41
|
+
config_base_url = getattr(config.openrouter, "base_url", None)
|
|
42
|
+
if config_base_url:
|
|
43
|
+
base_url = config_base_url
|
|
44
|
+
|
|
45
|
+
return base_url
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from openai.types.chat import ChatCompletionMessageParam, ChatCompletionSystemMessageParam
|
|
4
|
+
|
|
5
|
+
from fast_agent.llm.provider.openai.llm_openai import OpenAILLM
|
|
6
|
+
from fast_agent.llm.provider_types import Provider
|
|
7
|
+
from fast_agent.types import RequestParams
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TensorZeroOpenAILLM(OpenAILLM):
|
|
11
|
+
"""
|
|
12
|
+
An LLM augmentation that interacts with TensorZero's OpenAI-compatible inference endpoint.
|
|
13
|
+
This class extends the base OpenAIAugmentedLLM to handle TensorZero-specific
|
|
14
|
+
features, such as system template variables and custom parameters.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
18
|
+
"""
|
|
19
|
+
Initializes the TensorZeroOpenAIAugmentedLLM.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
*args: Variable length argument list.
|
|
23
|
+
**kwargs: Arbitrary keyword arguments.
|
|
24
|
+
"""
|
|
25
|
+
self._t0_episode_id = kwargs.pop("episode_id", None)
|
|
26
|
+
self._t0_function_name = kwargs.get("model", "")
|
|
27
|
+
|
|
28
|
+
super().__init__(*args, provider=Provider.TENSORZERO, **kwargs)
|
|
29
|
+
self.logger.info("TensorZeroOpenAILLM initialized.")
|
|
30
|
+
|
|
31
|
+
def _initialize_default_params(self, kwargs: dict) -> RequestParams:
|
|
32
|
+
"""
|
|
33
|
+
Initializes TensorZero-specific default parameters. Ensures the model name
|
|
34
|
+
is correctly prefixed for the TensorZero API.
|
|
35
|
+
"""
|
|
36
|
+
model = kwargs.get("model", "")
|
|
37
|
+
if not model.startswith("tensorzero::"):
|
|
38
|
+
model = f"tensorzero::function_name::{model}"
|
|
39
|
+
|
|
40
|
+
self.logger.debug(f"Initializing with TensorZero model: {model}")
|
|
41
|
+
|
|
42
|
+
return RequestParams(
|
|
43
|
+
model=model,
|
|
44
|
+
systemPrompt=self.instruction,
|
|
45
|
+
parallel_tool_calls=True,
|
|
46
|
+
max_iterations=10,
|
|
47
|
+
use_history=True,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
def _base_url(self) -> str:
|
|
51
|
+
"""
|
|
52
|
+
Constructs the TensorZero OpenAI-compatible endpoint URL.
|
|
53
|
+
"""
|
|
54
|
+
default_url = "http://localhost:3000/openai/v1"
|
|
55
|
+
if self.context and self.context.config and hasattr(self.context.config, "tensorzero"):
|
|
56
|
+
base_url = getattr(self.context.config.tensorzero, "base_url", default_url)
|
|
57
|
+
# Ensure the path is correctly appended
|
|
58
|
+
if not base_url.endswith("/openai/v1"):
|
|
59
|
+
base_url = f"{base_url.rstrip('/')}/openai/v1"
|
|
60
|
+
self.logger.debug(f"Using TensorZero base URL from config: {base_url}")
|
|
61
|
+
return base_url
|
|
62
|
+
self.logger.debug(f"Using default TensorZero base URL: {default_url}")
|
|
63
|
+
return default_url
|
|
64
|
+
|
|
65
|
+
def _prepare_api_request(
|
|
66
|
+
self,
|
|
67
|
+
messages: list[ChatCompletionMessageParam],
|
|
68
|
+
tools: list[Any] | None,
|
|
69
|
+
request_params: RequestParams,
|
|
70
|
+
) -> dict[str, Any]:
|
|
71
|
+
"""
|
|
72
|
+
Prepares the API request for the TensorZero OpenAI-compatible endpoint.
|
|
73
|
+
This method injects system template variables and other TensorZero-specific
|
|
74
|
+
parameters into the request. It also handles multimodal inputs.
|
|
75
|
+
"""
|
|
76
|
+
self.logger.debug("Preparing API request for TensorZero OpenAI endpoint.")
|
|
77
|
+
|
|
78
|
+
# Start with the base arguments from the parent class
|
|
79
|
+
arguments = super()._prepare_api_request(messages, tools, request_params)
|
|
80
|
+
|
|
81
|
+
# Handle system template variables
|
|
82
|
+
if request_params.template_vars:
|
|
83
|
+
self.logger.debug(f"Injecting template variables: {request_params.template_vars}")
|
|
84
|
+
system_message_found = False
|
|
85
|
+
for i, msg in enumerate(messages):
|
|
86
|
+
if msg.get("role") == "system":
|
|
87
|
+
# If content is a string, convert it to the TensorZero format
|
|
88
|
+
if isinstance(msg.get("content"), str):
|
|
89
|
+
messages[i] = ChatCompletionSystemMessageParam(
|
|
90
|
+
role="system", content=[request_params.template_vars]
|
|
91
|
+
)
|
|
92
|
+
elif isinstance(msg.get("content"), list):
|
|
93
|
+
# If content is already a list, merge the template vars
|
|
94
|
+
msg["content"][0].update(request_params.template_vars)
|
|
95
|
+
system_message_found = True
|
|
96
|
+
break
|
|
97
|
+
|
|
98
|
+
if not system_message_found:
|
|
99
|
+
# If no system message exists, create one
|
|
100
|
+
messages.insert(
|
|
101
|
+
0,
|
|
102
|
+
ChatCompletionSystemMessageParam(
|
|
103
|
+
role="system", content=[request_params.template_vars]
|
|
104
|
+
),
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# Add TensorZero-specific extra body parameters
|
|
108
|
+
extra_body = arguments.get("extra_body", {})
|
|
109
|
+
|
|
110
|
+
if self._t0_episode_id:
|
|
111
|
+
extra_body["tensorzero::episode_id"] = str(self._t0_episode_id)
|
|
112
|
+
self.logger.debug(f"Added tensorzero::episode_id: {self._t0_episode_id}")
|
|
113
|
+
|
|
114
|
+
# Merge metadata arguments
|
|
115
|
+
if request_params.metadata and isinstance(request_params.metadata, dict):
|
|
116
|
+
t0_args = request_params.metadata.get("tensorzero_arguments")
|
|
117
|
+
if t0_args:
|
|
118
|
+
self.logger.debug(f"Merging tensorzero_arguments from metadata: {t0_args}")
|
|
119
|
+
for msg in messages:
|
|
120
|
+
if msg.get("role") == "system" and isinstance(msg.get("content"), list):
|
|
121
|
+
msg["content"][0].update(t0_args)
|
|
122
|
+
break
|
|
123
|
+
|
|
124
|
+
if extra_body:
|
|
125
|
+
arguments["extra_body"] = extra_body
|
|
126
|
+
|
|
127
|
+
self.logger.debug(f"Final API request arguments: {arguments}")
|
|
128
|
+
return arguments
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
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
|
+
|
|
7
|
+
XAI_BASE_URL = "https://api.x.ai/v1"
|
|
8
|
+
DEFAULT_XAI_MODEL = "grok-3"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class XAILLM(OpenAILLM):
|
|
12
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
13
|
+
super().__init__(
|
|
14
|
+
*args, provider=Provider.XAI, **kwargs
|
|
15
|
+
) # Properly pass args and kwargs to parent
|
|
16
|
+
|
|
17
|
+
def _initialize_default_params(self, kwargs: dict) -> RequestParams:
|
|
18
|
+
"""Initialize xAI parameters"""
|
|
19
|
+
# Get base defaults from parent (includes ModelDatabase lookup)
|
|
20
|
+
base_params = super()._initialize_default_params(kwargs)
|
|
21
|
+
|
|
22
|
+
# Override with xAI-specific settings
|
|
23
|
+
chosen_model = kwargs.get("model", DEFAULT_XAI_MODEL)
|
|
24
|
+
base_params.model = chosen_model
|
|
25
|
+
base_params.parallel_tool_calls = False
|
|
26
|
+
|
|
27
|
+
return base_params
|
|
28
|
+
|
|
29
|
+
def _base_url(self) -> str:
|
|
30
|
+
base_url = os.getenv("XAI_BASE_URL", XAI_BASE_URL)
|
|
31
|
+
if self.context.config and self.context.config.xai:
|
|
32
|
+
base_url = self.context.config.xai.base_url
|
|
33
|
+
|
|
34
|
+
return base_url
|
|
35
|
+
|
|
36
|
+
async def _is_tool_stop_reason(self, finish_reason: str) -> bool:
|
|
37
|
+
# grok uses Null as the finish reason for tool calls?
|
|
38
|
+
return await super()._is_tool_stop_reason(finish_reason) or finish_reason is None
|