增加环绕侦察场景适配

This commit is contained in:
2026-01-08 15:44:38 +08:00
parent 3eba1f962b
commit 10c5bb5a8a
5441 changed files with 40219 additions and 379695 deletions

View File

@@ -135,7 +135,7 @@ class InferenceClient:
Note: for better compatibility with OpenAI's client, `model` has been aliased as `base_url`. Those 2
arguments are mutually exclusive. If a URL is passed as `model` or `base_url` for chat completion, the `(/v1)/chat/completions` suffix path will be appended to the URL.
provider (`str`, *optional*):
Name of the provider to use for inference. Can be `"black-forest-labs"`, `"cerebras"`, `"clarifai"`, `"cohere"`, `"fal-ai"`, `"featherless-ai"`, `"fireworks-ai"`, `"groq"`, `"hf-inference"`, `"hyperbolic"`, `"nebius"`, `"novita"`, `"nscale"`, `"openai"`, `"publicai"`, `"replicate"`, `"sambanova"`, `"scaleway"`, `"together"`, `"wavespeed"` or `"zai-org"`.
Name of the provider to use for inference. Can be `"black-forest-labs"`, `"cerebras"`, `"clarifai"`, `"cohere"`, `"fal-ai"`, `"featherless-ai"`, `"fireworks-ai"`, `"groq"`, `"hf-inference"`, `"hyperbolic"`, `"nebius"`, `"novita"`, `"nscale"`, `"openai"`, `"ovhcloud"`, `"publicai"`, `"replicate"`, `"sambanova"`, `"scaleway"`, `"together"`, `"wavespeed"` or `"zai-org"`.
Defaults to "auto" i.e. the first of the providers available for the model, sorted by the user's order in https://hf.co/settings/inference-providers.
If model is a URL or `base_url` is passed, then `provider` is not used.
token (`str`, *optional*):
@@ -452,6 +452,7 @@ class InferenceClient:
api_key=self.token,
)
response = self._inner_post(request_parameters)
response = provider_helper.get_response(response, request_params=request_parameters)
return AutomaticSpeechRecognitionOutput.parse_obj_as_instance(response)
@overload
@@ -1028,7 +1029,7 @@ class InferenceClient:
normalize: Optional[bool] = None,
prompt_name: Optional[str] = None,
truncate: Optional[bool] = None,
truncation_direction: Optional[Literal["Left", "Right"]] = None,
truncation_direction: Optional[Literal["left", "right"]] = None,
model: Optional[str] = None,
) -> "np.ndarray":
"""
@@ -1053,7 +1054,7 @@ class InferenceClient:
truncate (`bool`, *optional*):
Whether to truncate the embeddings or not.
Only available on server powered by Text-Embedding-Inference.
truncation_direction (`Literal["Left", "Right"]`, *optional*):
truncation_direction (`Literal["left", "right"]`, *optional*):
Which side of the input should be truncated when `truncate=True` is passed.
Returns:
@@ -3195,10 +3196,7 @@ class InferenceClient:
)
response = self._inner_post(request_parameters)
output = _bytes_to_dict(response)
return [
ZeroShotClassificationOutputElement.parse_obj_as_instance({"label": label, "score": score})
for label, score in zip(output["labels"], output["scores"])
]
return ZeroShotClassificationOutputElement.parse_obj_as_list(output)
def zero_shot_image_classification(
self,

View File

@@ -144,7 +144,7 @@ def _open_as_mime_bytes(content: Optional[ContentT]) -> Optional[MimeBytes]:
if hasattr(content, "read"): # duck-typing instead of isinstance(content, BinaryIO)
logger.debug("Reading content from BinaryIO")
data = content.read()
mime_type = mimetypes.guess_type(content.name)[0] if hasattr(content, "name") else None
mime_type = mimetypes.guess_type(str(content.name))[0] if hasattr(content, "name") else None
if isinstance(data, str):
raise TypeError("Expected binary stream (bytes), but got text stream")
return MimeBytes(data, mime_type=mime_type)

View File

@@ -126,7 +126,7 @@ class AsyncInferenceClient:
Note: for better compatibility with OpenAI's client, `model` has been aliased as `base_url`. Those 2
arguments are mutually exclusive. If a URL is passed as `model` or `base_url` for chat completion, the `(/v1)/chat/completions` suffix path will be appended to the URL.
provider (`str`, *optional*):
Name of the provider to use for inference. Can be `"black-forest-labs"`, `"cerebras"`, `"clarifai"`, `"cohere"`, `"fal-ai"`, `"featherless-ai"`, `"fireworks-ai"`, `"groq"`, `"hf-inference"`, `"hyperbolic"`, `"nebius"`, `"novita"`, `"nscale"`, `"openai"`, `"publicai"`, `"replicate"`, `"sambanova"`, `"scaleway"`, `"together"`, `"wavespeed"` or `"zai-org"`.
Name of the provider to use for inference. Can be `"black-forest-labs"`, `"cerebras"`, `"clarifai"`, `"cohere"`, `"fal-ai"`, `"featherless-ai"`, `"fireworks-ai"`, `"groq"`, `"hf-inference"`, `"hyperbolic"`, `"nebius"`, `"novita"`, `"nscale"`, `"openai"`, `"ovhcloud"`, `"publicai"`, `"replicate"`, `"sambanova"`, `"scaleway"`, `"together"`, `"wavespeed"` or `"zai-org"`.
Defaults to "auto" i.e. the first of the providers available for the model, sorted by the user's order in https://hf.co/settings/inference-providers.
If model is a URL or `base_url` is passed, then `provider` is not used.
token (`str`, *optional*):
@@ -472,6 +472,7 @@ class AsyncInferenceClient:
api_key=self.token,
)
response = await self._inner_post(request_parameters)
response = provider_helper.get_response(response, request_params=request_parameters)
return AutomaticSpeechRecognitionOutput.parse_obj_as_instance(response)
@overload
@@ -1055,7 +1056,7 @@ class AsyncInferenceClient:
normalize: Optional[bool] = None,
prompt_name: Optional[str] = None,
truncate: Optional[bool] = None,
truncation_direction: Optional[Literal["Left", "Right"]] = None,
truncation_direction: Optional[Literal["left", "right"]] = None,
model: Optional[str] = None,
) -> "np.ndarray":
"""
@@ -1080,7 +1081,7 @@ class AsyncInferenceClient:
truncate (`bool`, *optional*):
Whether to truncate the embeddings or not.
Only available on server powered by Text-Embedding-Inference.
truncation_direction (`Literal["Left", "Right"]`, *optional*):
truncation_direction (`Literal["left", "right"]`, *optional*):
Which side of the input should be truncated when `truncate=True` is passed.
Returns:
@@ -3245,10 +3246,7 @@ class AsyncInferenceClient:
)
response = await self._inner_post(request_parameters)
output = _bytes_to_dict(response)
return [
ZeroShotClassificationOutputElement.parse_obj_as_instance({"label": label, "score": score})
for label, score in zip(output["labels"], output["scores"])
]
return ZeroShotClassificationOutputElement.parse_obj_as_list(output)
async def zero_shot_image_classification(
self,

View File

@@ -19,6 +19,8 @@ import types
from dataclasses import asdict, dataclass
from typing import Any, TypeVar, Union, get_args
from typing_extensions import dataclass_transform
T = TypeVar("T", bound="BaseInferenceType")
@@ -29,6 +31,7 @@ def _repr_with_extra(self):
return f"{self.__class__.__name__}({', '.join(f'{k}={self.__dict__[k]!r}' for k in fields + other_fields)})"
@dataclass_transform()
def dataclass_with_extra(cls: type[T]) -> type[T]:
"""Decorator to add a custom __repr__ method to a dataclass, showing all fields, including extra ones.

View File

@@ -8,7 +8,7 @@ from typing import Literal, Optional, Union
from .base import BaseInferenceType, dataclass_with_extra
FeatureExtractionInputTruncationDirection = Literal["Left", "Right"]
FeatureExtractionInputTruncationDirection = Literal["left", "right"]
@dataclass_with_extra

View File

@@ -5,8 +5,8 @@ import traceback
from typing import Optional
import typer
from rich import print
from ...utils import ANSI
from ._cli_hacks import _async_prompt, _patch_anyio_open_process
from .agent import Agent
from .utils import _load_agent_config
@@ -55,10 +55,10 @@ async def run_agent(
if first_sigint:
first_sigint = False
abort_event.set()
print("\n[red]Interrupted. Press Ctrl+C again to quit.[/red]", flush=True)
print(ANSI.red("\nInterrupted. Press Ctrl+C again to quit."), flush=True)
return
print("\n[red]Exiting...[/red]", flush=True)
print(ANSI.red("\nExiting..."), flush=True)
exit_event.set()
try:
@@ -75,8 +75,12 @@ async def run_agent(
if len(inputs) > 0:
print(
"[bold blue]Some initial inputs are required by the agent. "
"Please provide a value or leave empty to load from env.[/bold blue]"
ANSI.bold(
ANSI.blue(
"Some initial inputs are required by the agent. "
"Please provide a value or leave empty to load from env."
)
)
)
for input_item in inputs:
input_id = input_item["id"]
@@ -98,15 +102,17 @@ async def run_agent(
if not input_usages:
print(
f"[yellow]Input '{input_id}' defined in config but not used by any server or as an API key."
" Skipping.[/yellow]"
ANSI.yellow(
f"Input '{input_id}' defined in config but not used by any server or as an API key."
" Skipping."
)
)
continue
# Prompt user for input
env_variable_key = input_id.replace("-", "_").upper()
print(
f"[blue]{input_id}[/blue]: {description}. (default: load from {env_variable_key}).",
ANSI.blue(f"{input_id}") + f": {description}. (default: load from {env_variable_key}).",
end=" ",
)
user_input = (await _async_prompt(exit_event=exit_event)).strip()
@@ -118,10 +124,12 @@ async def run_agent(
if not final_value:
final_value = os.getenv(env_variable_key, "")
if final_value:
print(f"[green]Value successfully loaded from '{env_variable_key}'[/green]")
print(ANSI.green(f"Value successfully loaded from '{env_variable_key}'"))
else:
print(
f"[yellow]No value found for '{env_variable_key}' in environment variables. Continuing.[/yellow]"
ANSI.yellow(
f"No value found for '{env_variable_key}' in environment variables. Continuing."
)
)
resolved_inputs[input_id] = final_value
@@ -150,9 +158,9 @@ async def run_agent(
prompt=prompt,
) as agent:
await agent.load_tools()
print(f"[bold blue]Agent loaded with {len(agent.available_tools)} tools:[/bold blue]")
print(ANSI.bold(ANSI.blue("Agent loaded with {} tools:".format(len(agent.available_tools)))))
for t in agent.available_tools:
print(f"[blue]{t.function.name}[/blue]")
print(ANSI.blue(f"{t.function.name}"))
while True:
abort_event.clear()
@@ -165,13 +173,13 @@ async def run_agent(
user_input = await _async_prompt(exit_event=exit_event)
first_sigint = True
except EOFError:
print("\n[red]EOF received, exiting.[/red]", flush=True)
print(ANSI.red("\nEOF received, exiting."), flush=True)
break
except KeyboardInterrupt:
if not first_sigint and abort_event.is_set():
continue
else:
print("\n[red]Keyboard interrupt during input processing.[/red]", flush=True)
print(ANSI.red("\nKeyboard interrupt during input processing."), flush=True)
break
try:
@@ -195,7 +203,7 @@ async def run_agent(
print(f"{call.function.arguments}", end="")
else:
print(
f"\n\n[green]Tool[{chunk.name}] {chunk.tool_call_id}\n{chunk.content}[/green]\n",
ANSI.green(f"\n\nTool[{chunk.name}] {chunk.tool_call_id}\n{chunk.content}\n"),
flush=True,
)
@@ -203,12 +211,12 @@ async def run_agent(
except Exception as e:
tb_str = traceback.format_exc()
print(f"\n[bold red]Error during agent run: {e}\n{tb_str}[/bold red]", flush=True)
print(ANSI.red(f"\nError during agent run: {e}\n{tb_str}"), flush=True)
first_sigint = True # Allow graceful interrupt for the next command
except Exception as e:
tb_str = traceback.format_exc()
print(f"\n[bold red]An unexpected error occurred: {e}\n{tb_str}[/bold red]", flush=True)
print(ANSI.red(f"\nAn unexpected error occurred: {e}\n{tb_str}"), flush=True)
raise e
finally:
@@ -236,10 +244,10 @@ def run(
try:
asyncio.run(run_agent(path))
except KeyboardInterrupt:
print("\n[red]Application terminated by KeyboardInterrupt.[/red]", flush=True)
print(ANSI.red("\nApplication terminated by KeyboardInterrupt."), flush=True)
raise typer.Exit(code=130)
except Exception as e:
print(f"\n[bold red]An unexpected error occurred: {e}[/bold red]", flush=True)
print(ANSI.red(f"\nAn unexpected error occurred: {e}"), flush=True)
raise e

View File

@@ -57,10 +57,10 @@ def format_result(result: "mcp_types.CallToolResult") -> str:
elif item.type == "resource":
resource = item.resource
if hasattr(resource, "text"):
if hasattr(resource, "text") and isinstance(resource.text, str):
formatted_parts.append(resource.text)
elif hasattr(resource, "blob"):
elif hasattr(resource, "blob") and isinstance(resource.blob, str):
formatted_parts.append(
f"[Binary Content ({resource.uri}): {resource.mimeType}, {_get_base64_size(resource.blob)} bytes]\n"
f"The task is complete and the content accessible to the User"

View File

@@ -38,8 +38,15 @@ from .nebius import (
from .novita import NovitaConversationalTask, NovitaTextGenerationTask, NovitaTextToVideoTask
from .nscale import NscaleConversationalTask, NscaleTextToImageTask
from .openai import OpenAIConversationalTask
from .ovhcloud import OVHcloudConversationalTask
from .publicai import PublicAIConversationalTask
from .replicate import ReplicateImageToImageTask, ReplicateTask, ReplicateTextToImageTask, ReplicateTextToSpeechTask
from .replicate import (
ReplicateAutomaticSpeechRecognitionTask,
ReplicateImageToImageTask,
ReplicateTask,
ReplicateTextToImageTask,
ReplicateTextToSpeechTask,
)
from .sambanova import SambanovaConversationalTask, SambanovaFeatureExtractionTask
from .scaleway import ScalewayConversationalTask, ScalewayFeatureExtractionTask
from .together import TogetherConversationalTask, TogetherTextGenerationTask, TogetherTextToImageTask
@@ -70,6 +77,7 @@ PROVIDER_T = Literal[
"novita",
"nscale",
"openai",
"ovhcloud",
"publicai",
"replicate",
"sambanova",
@@ -166,10 +174,14 @@ PROVIDERS: dict[PROVIDER_T, dict[str, TaskProviderHelper]] = {
"openai": {
"conversational": OpenAIConversationalTask(),
},
"ovhcloud": {
"conversational": OVHcloudConversationalTask(),
},
"publicai": {
"conversational": PublicAIConversationalTask(),
},
"replicate": {
"automatic-speech-recognition": ReplicateAutomaticSpeechRecognitionTask(),
"image-to-image": ReplicateImageToImageTask(),
"text-to-image": ReplicateTextToImageTask(),
"text-to-speech": ReplicateTextToSpeechTask(),

View File

@@ -32,6 +32,7 @@ HARDCODED_MODEL_INFERENCE_MAPPING: dict[str, dict[str, InferenceProviderMapping]
"hyperbolic": {},
"nebius": {},
"nscale": {},
"ovhcloud": {},
"replicate": {},
"sambanova": {},
"scaleway": {},

View File

@@ -112,7 +112,7 @@ class FalAIAutomaticSpeechRecognitionTask(FalAITask):
text = _as_dict(response)["text"]
if not isinstance(text, str):
raise ValueError(f"Unexpected output format from FalAI API. Expected string, got {type(text)}.")
return text
return {"text": text}
class FalAITextToImageTask(FalAITask):

View File

@@ -72,6 +72,67 @@ class ReplicateTextToSpeechTask(ReplicateTask):
return payload
class ReplicateAutomaticSpeechRecognitionTask(ReplicateTask):
def __init__(self) -> None:
super().__init__("automatic-speech-recognition")
def _prepare_payload_as_dict(
self,
inputs: Any,
parameters: dict,
provider_mapping_info: InferenceProviderMapping,
) -> Optional[dict]:
mapped_model = provider_mapping_info.provider_id
audio_url = _as_url(inputs, default_mime_type="audio/wav")
payload: dict[str, Any] = {
"input": {
**{"audio": audio_url},
**filter_none(parameters),
}
}
if ":" in mapped_model:
payload["version"] = mapped_model.split(":", 1)[1]
return payload
def get_response(self, response: Union[bytes, dict], request_params: Optional[RequestParameters] = None) -> Any:
response_dict = _as_dict(response)
output = response_dict.get("output")
if isinstance(output, str):
return {"text": output}
if isinstance(output, list) and output:
first_item = output[0]
if isinstance(first_item, str):
return {"text": first_item}
if isinstance(first_item, dict):
output = first_item
text: Optional[str] = None
if isinstance(output, dict):
transcription = output.get("transcription")
if isinstance(transcription, str):
text = transcription
translation = output.get("translation")
if isinstance(translation, str):
text = translation
txt_file = output.get("txt_file")
if isinstance(txt_file, str):
text_response = get_session().get(txt_file)
text_response.raise_for_status()
text = text_response.text
if text is not None:
return {"text": text}
raise ValueError("Received malformed response from Replicate automatic-speech-recognition API")
class ReplicateImageToImageTask(ReplicateTask):
def __init__(self):
super().__init__("image-to-image")