yaicli 0.6.2__py3-none-any.whl → 0.6.4__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.
pyproject.toml CHANGED
@@ -1,10 +1,10 @@
1
1
  [project]
2
2
  name = "yaicli"
3
- version = "0.6.2"
3
+ version = "0.6.4"
4
4
  description = "A simple CLI tool to interact with LLM"
5
5
  authors = [{ name = "belingud", email = "im.victor@qq.com" }]
6
6
  readme = "README.md"
7
- requires-python = ">=3.9"
7
+ requires-python = ">=3.10"
8
8
  license = { file = "LICENSE" }
9
9
  classifiers = [
10
10
  "Programming Language :: Python :: 3",
@@ -42,6 +42,15 @@ keywords = [
42
42
  "anthropic",
43
43
  "groq",
44
44
  "cohere",
45
+ "huggingface",
46
+ "chatglm",
47
+ "sambanova",
48
+ "siliconflow",
49
+ "xai",
50
+ "vertexai",
51
+ "deepseek",
52
+ "modelscope",
53
+ "ollama",
45
54
  ]
46
55
  dependencies = [
47
56
  "click>=8.1.8",
@@ -65,10 +74,20 @@ ai = "yaicli.entry:app"
65
74
  yaicli = "yaicli.entry:app"
66
75
 
67
76
  [project.optional-dependencies]
77
+ all = [
78
+ "volcengine-python-sdk>=3.0.15",
79
+ "ollama>=0.5.1",
80
+ "cohere>=5.15.0",
81
+ "google-genai>=1.20.0",
82
+ "huggingface-hub>=0.33.0",
83
+ ]
68
84
  doubao = ["volcengine-python-sdk>=3.0.15"]
69
85
  ollama = ["ollama>=0.5.1"]
70
86
  cohere = ["cohere>=5.15.0"]
71
- all = ["volcengine-python-sdk>=3.0.15", "ollama>=0.5.1", "cohere>=5.15.0"]
87
+ gemini = ["google-genai>=1.20.0"]
88
+ huggingface = [
89
+ "huggingface-hub>=0.33.0",
90
+ ]
72
91
 
73
92
  [tool.pytest.ini_options]
74
93
  testpaths = ["tests"]
@@ -91,6 +110,7 @@ dev = [
91
110
  "pytest>=8.3.5",
92
111
  "pytest-cov>=6.1.1",
93
112
  "ruff>=0.11.2",
113
+ "tox>=4.27.0",
94
114
  ]
95
115
 
96
116
  [tool.isort]
yaicli/cli.py CHANGED
@@ -267,7 +267,7 @@ class CLI:
267
267
  assistant_msg = self.chat.history[i + 1] if (i + 1) < len(self.chat.history) else None
268
268
  self.console.print(f"[dim]{i // 2 + 1}[/dim] [bold blue]User:[/bold blue] {user_msg.content}")
269
269
  if assistant_msg:
270
- md = Markdown(assistant_msg.content, code_theme=cfg["CODE_THEME"])
270
+ md = Markdown(assistant_msg.content or "", code_theme=cfg["CODE_THEME"])
271
271
  padded_md = Padding(md, (0, 0, 0, 4))
272
272
  self.console.print(" Assistant:", style="bold green")
273
273
  self.console.print(padded_md)
@@ -384,14 +384,7 @@ class CLI:
384
384
  self._check_history_len()
385
385
 
386
386
  if self.current_mode == EXEC_MODE:
387
- # We need to extract the executable command from the last assistant message
388
- # in case of tool use.
389
- final_content = ""
390
- if self.chat.history:
391
- last_message = self.chat.history[-1]
392
- if last_message.role == "assistant":
393
- final_content = last_message.content or ""
394
- self._confirm_and_execute(final_content)
387
+ self._confirm_and_execute(content or "")
395
388
  return True
396
389
 
397
390
  def _confirm_and_execute(self, raw_content: str) -> None:
yaicli/config.py CHANGED
@@ -142,7 +142,7 @@ class Config(dict):
142
142
  if target_type is bool:
143
143
  converted_value = str2bool(raw_value)
144
144
  elif target_type in (int, float, str):
145
- converted_value = target_type(raw_value)
145
+ converted_value = target_type(raw_value) if raw_value else raw_value
146
146
  elif target_type is dict and raw_value:
147
147
  converted_value = json.loads(raw_value)
148
148
  except (ValueError, TypeError, json.JSONDecodeError) as e:
yaicli/const.py CHANGED
@@ -1,5 +1,5 @@
1
1
  try:
2
- from enum import StrEnum
2
+ from enum import StrEnum # type: ignore
3
3
  except ImportError:
4
4
  from enum import Enum
5
5
 
yaicli/llms/__init__.py CHANGED
@@ -1,13 +1,4 @@
1
- from ..config import cfg
2
1
  from .client import LLMClient
3
2
  from .provider import Provider, ProviderFactory
4
3
 
5
4
  __all__ = ["LLMClient", "Provider", "ProviderFactory"]
6
-
7
-
8
- class BaseProvider:
9
- def __init__(self) -> None:
10
- self.api_key = cfg["API_KEY"]
11
- self.model = cfg["MODEL"]
12
- self.base_url = cfg["BASE_URL"]
13
- self.timeout = cfg["TIMEOUT"]
yaicli/llms/provider.py CHANGED
@@ -43,9 +43,13 @@ class ProviderFactory:
43
43
  "chatglm": (".providers.chatglm_provider", "ChatglmProvider"),
44
44
  "chutes": (".providers.chutes_provider", "ChutesProvider"),
45
45
  "cohere": (".providers.cohere_provider", "CohereProvider"),
46
+ "cohere-bedrock": (".providers.cohere_provider", "CohereBadrockProvider"),
47
+ "cohere-sagemaker": (".providers.cohere_provider", "CohereSagemakerProvider"),
46
48
  "deepseek": (".providers.deepseek_provider", "DeepSeekProvider"),
47
49
  "doubao": (".providers.doubao_provider", "DoubaoProvider"),
50
+ "gemini": (".providers.gemini_provider", "GeminiProvider"),
48
51
  "groq": (".providers.groq_provider", "GroqProvider"),
52
+ "huggingface": (".providers.huggingface_provider", "HuggingFaceProvider"),
49
53
  "infini-ai": (".providers.infiniai_provider", "InfiniAIProvider"),
50
54
  "minimax": (".providers.minimax_provider", "MinimaxProvider"),
51
55
  "modelscope": (".providers.modelscope_provider", "ModelScopeProvider"),
@@ -55,6 +59,7 @@ class ProviderFactory:
55
59
  "sambanova": (".providers.sambanova_provider", "SambanovaProvider"),
56
60
  "siliconflow": (".providers.siliconflow_provider", "SiliconFlowProvider"),
57
61
  "targon": (".providers.targon_provider", "TargonProvider"),
62
+ "vertexai": (".providers.vertexai_provider", "VertexAIProvider"),
58
63
  "xai": (".providers.xai_provider", "XaiProvider"),
59
64
  "yi": (".providers.yi_provider", "YiProvider"),
60
65
  }
@@ -1,5 +1,5 @@
1
1
  import json
2
- from typing import Any, Dict, Generator, Optional
2
+ from typing import Generator, Optional
3
3
 
4
4
  from openai._streaming import Stream
5
5
  from openai.types.chat.chat_completion import ChatCompletion, Choice
@@ -14,10 +14,14 @@ class ChatglmProvider(OpenAIProvider):
14
14
 
15
15
  DEFAULT_BASE_URL = "https://open.bigmodel.cn/api/paas/v4/"
16
16
 
17
- def get_completion_params(self) -> Dict[str, Any]:
18
- params = super().get_completion_params()
19
- params["max_tokens"] = params.pop("max_completion_tokens")
20
- return params
17
+ COMPLETION_PARAMS_KEYS = {
18
+ "model": "MODEL",
19
+ "temperature": "TEMPERATURE",
20
+ "top_p": "TOP_P",
21
+ "max_tokens": "MAX_TOKENS",
22
+ "do_sample": "DO_SAMPLE",
23
+ "extra_body": "EXTRA_BODY",
24
+ }
21
25
 
22
26
  def _handle_normal_response(self, response: ChatCompletion) -> Generator[LLMResponse, None, None]:
23
27
  """Handle normal (non-streaming) response
@@ -10,7 +10,8 @@ This module implements Cohere provider classes for different deployment options:
10
10
  from typing import Any, Dict, Generator, List, Optional
11
11
 
12
12
  from cohere import BedrockClientV2, ClientV2, SagemakerClientV2
13
- from cohere.types.tool_call_v2 import ToolCallV2, ToolCallV2Function
13
+ from cohere.types.tool_call_v2 import ToolCallV2
14
+ from cohere.types.tool_call_v2function import ToolCallV2Function
14
15
 
15
16
  from ...config import cfg
16
17
  from ...console import get_console
@@ -179,7 +180,9 @@ class CohereProvider(Provider):
179
180
  continue
180
181
  elif chunk.type == "tool-call-delta":
181
182
  # Tool call arguments being generated: cohere.types.chat_tool_call_delta_event_delta_message.ChatToolCallDeltaEventDeltaMessage
182
- tool_call.arguments += chunk.delta.message.tool_calls.function.arguments
183
+ if not tool_call:
184
+ continue
185
+ tool_call.arguments += chunk.delta.message.tool_calls.function.arguments or ""
183
186
  # Waiting for tool-call-end event
184
187
  continue
185
188
 
@@ -292,7 +295,7 @@ class CohereBadrockProvider(CohereProvider):
292
295
  return self.CLIENT_CLS(**self.client_params)
293
296
 
294
297
 
295
- class CohereSagemaker(CohereBadrockProvider):
298
+ class CohereSagemakerProvider(CohereBadrockProvider):
296
299
  """Cohere provider for AWS Sagemaker integration"""
297
300
 
298
301
  CLIENT_CLS = SagemakerClientV2
@@ -10,5 +10,6 @@ class DeepSeekProvider(OpenAIProvider):
10
10
 
11
11
  def get_completion_params(self) -> Dict[str, Any]:
12
12
  params = super().get_completion_params()
13
- params["max_tokens"] = params.pop("max_completion_tokens")
13
+ if "max_completion_tokens" in params:
14
+ params["max_tokens"] = params.pop("max_completion_tokens")
14
15
  return params
@@ -0,0 +1,193 @@
1
+ import json
2
+ from functools import wraps
3
+ from typing import Any, Callable, Dict, Generator, List
4
+
5
+ import google.genai as genai
6
+ from google.genai import types
7
+
8
+ from ...config import cfg
9
+ from ...console import get_console
10
+ from ...schemas import ChatMessage, LLMResponse
11
+ from ...tools import get_func_name_map
12
+ from ..provider import Provider
13
+
14
+
15
+ def wrap_function(func):
16
+ @wraps(func)
17
+ def wrapper(*args, **kwargs):
18
+ return func(*args, **kwargs)
19
+
20
+ return wrapper
21
+
22
+
23
+ class GeminiProvider(Provider):
24
+ """Gemini provider implementation based on google-genai library"""
25
+
26
+ DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com/v1beta"
27
+
28
+ def __init__(self, config: dict = cfg, verbose: bool = False, **kwargs):
29
+ self.config = config
30
+ self.enable_function = self.config["ENABLE_FUNCTIONS"]
31
+ self.verbose = verbose
32
+
33
+ # Initialize client
34
+ self.client_params = self.get_client_params()
35
+ self.client = genai.Client(**self.client_params)
36
+ self.console = get_console()
37
+
38
+ def get_client_params(self) -> Dict[str, Any]:
39
+ """Get the client parameters"""
40
+ # Initialize client params
41
+ return {
42
+ "api_key": self.config["API_KEY"],
43
+ }
44
+
45
+ def get_chat_config(self):
46
+ http_options_map = {
47
+ "timeout": self.config["TIMEOUT"] * 1000, # Timeout for the request in milliseconds.
48
+ "headers": {**self.config["EXTRA_HEADERS"], "X-Client": self.APP_NAME, "Referer": self.APP_REFERER},
49
+ }
50
+ if self.config.get("BASE_URL"):
51
+ http_options_map["base_url"] = self.config["BASE_URL"]
52
+ if self.config.get("API_VERSION"):
53
+ # Specifies the version of the API to use.
54
+ http_options_map["api_version"] = self.config["API_VERSION"]
55
+ http_options = types.HttpOptions(**http_options_map)
56
+ config_map = {
57
+ "max_output_tokens": self.config["MAX_TOKENS"],
58
+ "temperature": self.config["TEMPERATURE"],
59
+ "top_p": self.config["TOP_P"],
60
+ "http_options": http_options,
61
+ }
62
+ if self.config.get("TOP_K"):
63
+ config_map["top_k"] = self.config["TOP_K"]
64
+ if self.config.get("PRESENCE_PENALTY"):
65
+ config_map["presence_penalty"] = self.config["PRESENCE_PENALTY"]
66
+ if self.config.get("FREQUENCY_PENALTY"):
67
+ config_map["frequency_penalty"] = self.config["FREQUENCY_PENALTY"]
68
+ if self.config.get("SEED"):
69
+ config_map["seed"] = self.config["SEED"]
70
+ # Indicates whether to include thoughts in the response. If true, thoughts are returned only if the model supports thought and thoughts are available.
71
+ thinking_config_map = {"include_thoughts": self.config.get("INCLUDE_THOUGHTS", True)}
72
+ if self.config.get("THINKING_BUDGET"):
73
+ thinking_config_map["thinking_budget"] = int(self.config["THINKING_BUDGET"])
74
+ config_map["thinking_config"] = types.ThinkingConfig(**thinking_config_map)
75
+ config = types.GenerateContentConfig(**config_map)
76
+ if self.enable_function:
77
+ # TODO: support disable automatic function calling
78
+ # config.automatic_function_calling = types.AutomaticFunctionCallingConfig(disable=False)
79
+ config.tools = self.gen_gemini_functions()
80
+ return config
81
+
82
+ def _convert_messages(self, messages: List[ChatMessage]) -> List[types.Content]:
83
+ """Convert a list of ChatMessage objects to a list of Gemini Content objects."""
84
+ converted_messages = []
85
+ for msg in messages:
86
+ if msg.role == "system":
87
+ continue
88
+ content = types.Content(role=self._map_role(msg.role), parts=[types.Part(text=msg.content)])
89
+ if msg.role == "tool":
90
+ content.role = "user"
91
+ content.parts = [
92
+ types.Part.from_function_response(name=msg.name or "", response={"result": msg.content})
93
+ ]
94
+ converted_messages.append(content)
95
+ return converted_messages
96
+
97
+ def _map_role(self, role: str) -> str:
98
+ """Map OpenAI roles to Gemini roles"""
99
+ # Gemini uses "user", "model" instead of "user", "assistant"
100
+ if role == "assistant":
101
+ return "model"
102
+ return role
103
+
104
+ def gen_gemini_functions(self) -> List[Callable[..., Any]]:
105
+ """Wrap Gemini functions from OpenAI functions for automatic function calling"""
106
+ func_name_map = get_func_name_map()
107
+ if not func_name_map:
108
+ return []
109
+ funcs = []
110
+ for func_name, func in func_name_map.items():
111
+ wrapped_func = wrap_function(func.execute)
112
+ wrapped_func.__name__ = func_name
113
+ wrapped_func.__doc__ = func.__doc__
114
+ funcs.append(wrapped_func)
115
+ return funcs
116
+
117
+ def completion(
118
+ self,
119
+ messages: List[ChatMessage],
120
+ stream: bool = False,
121
+ ) -> Generator[LLMResponse, None, None]:
122
+ """
123
+ Send completion request to Gemini and return responses.
124
+
125
+ Args:
126
+ messages: List of chat messages to send
127
+ stream: Whether to stream the response
128
+
129
+ Yields:
130
+ LLMResponse: Response objects containing content, tool calls, etc.
131
+
132
+ Raises:
133
+ ValueError: If messages is empty or invalid
134
+ APIError: If API request fails
135
+ """
136
+ gemini_messages = self._convert_messages(messages)
137
+ if self.verbose:
138
+ self.console.print("Messages:")
139
+ self.console.print(gemini_messages)
140
+ chat_config = self.get_chat_config()
141
+ chat_config.system_instruction = messages[0].content
142
+ chat = self.client.chats.create(model=self.config["MODEL"], history=gemini_messages, config=chat_config) # type: ignore
143
+ message = messages[-1].content
144
+
145
+ if stream:
146
+ response = chat.send_message_stream(message=message) # type: ignore
147
+ yield from self._handle_stream_response(response)
148
+ else:
149
+ response = chat.send_message(message=message) # type: ignore
150
+ yield from self._handle_normal_response(response)
151
+
152
+ def _handle_normal_response(self, response) -> Generator[LLMResponse, None, None]:
153
+ """Handle normal (non-streaming) response"""
154
+ # TODO: support disable automatic function calling
155
+ if not response or not response.candidates:
156
+ yield LLMResponse(
157
+ content=json.dumps(response.to_json_dict()),
158
+ finish_reason="stop",
159
+ )
160
+ return
161
+ for part in response.candidates[0].content.parts:
162
+ if part.thought:
163
+ yield LLMResponse(reasoning=part.text, finish_reason="stop")
164
+ else:
165
+ yield LLMResponse(reasoning=None, content=part.text, finish_reason="stop")
166
+
167
+ def _handle_stream_response(self, response) -> Generator[LLMResponse, None, None]:
168
+ """Handle streaming response from Gemini API"""
169
+ # Initialize tool call object to accumulate tool call data across chunks
170
+ # TODO: support disable automatic function calling
171
+ tool_call = None
172
+ for chunk in response:
173
+ if not chunk.candidates:
174
+ continue
175
+ candidate = chunk.candidates[0]
176
+ finish_reason = candidate.finish_reason
177
+ for part in chunk.candidates[0].content.parts:
178
+ if part.thought:
179
+ reasoning = part.text
180
+ content = None
181
+ else:
182
+ content = part.text
183
+ reasoning = None
184
+ yield LLMResponse(
185
+ reasoning=reasoning,
186
+ content=content or "",
187
+ tool_call=tool_call if finish_reason == "tool_calls" else None,
188
+ finish_reason=finish_reason or None,
189
+ )
190
+
191
+ def detect_tool_role(self) -> str:
192
+ """Return the role that should be used for tool responses"""
193
+ return "user"
@@ -0,0 +1,40 @@
1
+ from typing import Any, Dict
2
+
3
+ from huggingface_hub import InferenceClient
4
+
5
+ from .chatglm_provider import ChatglmProvider
6
+
7
+
8
+ class HuggingFaceProvider(ChatglmProvider):
9
+ """
10
+ HuggingFaceProvider is a provider for the HuggingFace API.
11
+ """
12
+
13
+ CLIENT_CLS = InferenceClient
14
+ DEFAULT_PROVIDER = "hf-inference"
15
+
16
+ COMPLETION_PARAMS_KEYS = {
17
+ "model": "MODEL",
18
+ "temperature": "TEMPERATURE",
19
+ "top_p": "TOP_P",
20
+ "max_tokens": "MAX_TOKENS",
21
+ "extra_body": "EXTRA_BODY",
22
+ }
23
+
24
+ def get_client_params(self) -> Dict[str, Any]:
25
+ client_params = {
26
+ "api_key": self.config["API_KEY"],
27
+ "timeout": self.config["TIMEOUT"],
28
+ "provider": self.config.get("HF_PROVIDER") or self.DEFAULT_PROVIDER,
29
+ }
30
+ if self.config["BASE_URL"]:
31
+ client_params["base_url"] = self.config["BASE_URL"]
32
+ if self.config["EXTRA_HEADERS"]:
33
+ client_params["headers"] = {
34
+ **self.config["EXTRA_HEADERS"],
35
+ "X-Title": self.APP_NAME,
36
+ "HTTP-Referer": self.APP_REFERER,
37
+ }
38
+ if self.config.get("BILL_TO"):
39
+ client_params["bill_to"] = self.config["BILL_TO"]
40
+ return client_params
@@ -1,5 +1,6 @@
1
1
  from typing import Any, Dict
2
2
 
3
+ from ...config import cfg
3
4
  from .openai_provider import OpenAIProvider
4
5
 
5
6
 
@@ -8,7 +9,7 @@ class InfiniAIProvider(OpenAIProvider):
8
9
 
9
10
  DEFAULT_BASE_URL = "https://cloud.infini-ai.com/maas/v1"
10
11
 
11
- def __init__(self, config: dict = ..., **kwargs):
12
+ def __init__(self, config: dict = cfg, **kwargs):
12
13
  super().__init__(config, **kwargs)
13
14
  if self.enable_function:
14
15
  self.console.print("InfiniAI does not support functions, disabled", style="yellow")
@@ -16,5 +17,6 @@ class InfiniAIProvider(OpenAIProvider):
16
17
 
17
18
  def get_completion_params(self) -> Dict[str, Any]:
18
19
  params = super().get_completion_params()
19
- params["max_tokens"] = params.pop("max_completion_tokens")
20
+ if "max_completion_tokens" in params:
21
+ params["max_tokens"] = params.pop("max_completion_tokens")
20
22
  return params
@@ -10,5 +10,6 @@ class ModelScopeProvider(OpenAIProvider):
10
10
 
11
11
  def get_completion_params(self) -> Dict[str, Any]:
12
12
  params = super().get_completion_params()
13
- params["max_tokens"] = params.pop("max_completion_tokens")
13
+ if "max_completion_tokens" in params:
14
+ params["max_tokens"] = params.pop("max_completion_tokens")
14
15
  return params
@@ -19,7 +19,7 @@ class OpenAIProvider(Provider):
19
19
  DEFAULT_BASE_URL = "https://api.openai.com/v1"
20
20
  CLIENT_CLS = openai.OpenAI
21
21
  # Base mapping between config keys and API parameter names
22
- _BASE_COMPLETION_PARAMS_KEYS = {
22
+ COMPLETION_PARAMS_KEYS = {
23
23
  "model": "MODEL",
24
24
  "temperature": "TEMPERATURE",
25
25
  "top_p": "TOP_P",
@@ -69,7 +69,7 @@ class OpenAIProvider(Provider):
69
69
  Returns:
70
70
  Dict[str, str]: Mapping from API parameter names to config keys
71
71
  """
72
- return self._BASE_COMPLETION_PARAMS_KEYS.copy()
72
+ return self.COMPLETION_PARAMS_KEYS.copy()
73
73
 
74
74
  def get_completion_params(self) -> Dict[str, Any]:
75
75
  """
@@ -81,7 +81,7 @@ class OpenAIProvider(Provider):
81
81
  completion_params = {}
82
82
  params_keys = self.get_completion_params_keys()
83
83
  for api_key, config_key in params_keys.items():
84
- if self.config.get(config_key, None) is not None:
84
+ if self.config.get(config_key, None) is not None and self.config[config_key] != "":
85
85
  completion_params[api_key] = self.config[config_key]
86
86
  return completion_params
87
87
 
@@ -89,7 +89,7 @@ class OpenAIProvider(Provider):
89
89
  """Convert a list of ChatMessage objects to a list of OpenAI message dicts."""
90
90
  converted_messages = []
91
91
  for msg in messages:
92
- message = {"role": msg.role, "content": msg.content or ""}
92
+ message: Dict[str, Any] = {"role": msg.role, "content": msg.content or ""}
93
93
 
94
94
  if msg.name:
95
95
  message["name"] = msg.name
@@ -140,12 +140,19 @@ class OpenAIProvider(Provider):
140
140
  if tools:
141
141
  params["tools"] = tools
142
142
 
143
- if stream:
144
- response = self.client.chat.completions.create(**params)
145
- yield from self._handle_stream_response(response)
146
- else:
147
- response = self.client.chat.completions.create(**params)
148
- yield from self._handle_normal_response(response)
143
+ try:
144
+ if stream:
145
+ response = self.client.chat.completions.create(**params)
146
+ yield from self._handle_stream_response(response)
147
+ else:
148
+ response = self.client.chat.completions.create(**params)
149
+ yield from self._handle_normal_response(response)
150
+ except (openai.APIStatusError, openai.APIResponseValidationError) as e:
151
+ try:
152
+ body = e.response.json()
153
+ except Exception:
154
+ body = e.response.text
155
+ self.console.print(f"Error Response: {body}")
149
156
 
150
157
  def _handle_normal_response(self, response: ChatCompletion) -> Generator[LLMResponse, None, None]:
151
158
  """Handle normal (non-streaming) response"""
@@ -10,5 +10,6 @@ class SiliconFlowProvider(OpenAIProvider):
10
10
 
11
11
  def get_completion_params(self) -> Dict[str, Any]:
12
12
  params = super().get_completion_params()
13
- params["max_tokens"] = params.pop("max_completion_tokens")
13
+ if "max_completion_tokens" in params:
14
+ params["max_tokens"] = params.pop("max_completion_tokens")
14
15
  return params
@@ -0,0 +1,18 @@
1
+ from typing import Any, Dict
2
+
3
+ from .gemini_provider import GeminiProvider
4
+
5
+
6
+ class VertexAIProvider(GeminiProvider):
7
+ """Vertex AI provider implementation based on google-genai library"""
8
+
9
+ def get_client_params(self) -> Dict[str, Any]:
10
+ """Get the client parameters"""
11
+ # Initialize client params
12
+ if not self.config.get("PROJECT") or not self.config.get("LOCATION"):
13
+ raise ValueError("PROJECT and LOCATION are required for Vertex AI")
14
+ return {
15
+ "vertexai": True,
16
+ "project": self.config.get("PROJECT"),
17
+ "location": self.config.get("LOCATION"),
18
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yaicli
3
- Version: 0.6.2
3
+ Version: 0.6.4
4
4
  Summary: A simple CLI tool to interact with LLM
5
5
  Project-URL: Homepage, https://github.com/belingud/yaicli
6
6
  Project-URL: Repository, https://github.com/belingud/yaicli
@@ -208,11 +208,11 @@ License: Apache License
208
208
  See the License for the specific language governing permissions and
209
209
  limitations under the License.
210
210
  License-File: LICENSE
211
- Keywords: ai,ai-assistant,ai-chat,ai-interaction,anthropic,chatgpt,claude,cli,cohere,command-line,completion,console-application,conversation,gemini,gpt,groq,inference,interactive,language-model,llm,llms,mistral,nlp,openai,prompt,python-tool,shell-integration,terminal,terminal-interface,text-generation
211
+ Keywords: ai,ai-assistant,ai-chat,ai-interaction,anthropic,chatglm,chatgpt,claude,cli,cohere,command-line,completion,console-application,conversation,deepseek,gemini,gpt,groq,huggingface,inference,interactive,language-model,llm,llms,mistral,modelscope,nlp,ollama,openai,prompt,python-tool,sambanova,shell-integration,siliconflow,terminal,terminal-interface,text-generation,vertexai,xai
212
212
  Classifier: License :: OSI Approved :: Apache Software License
213
213
  Classifier: Operating System :: OS Independent
214
214
  Classifier: Programming Language :: Python :: 3
215
- Requires-Python: >=3.9
215
+ Requires-Python: >=3.10
216
216
  Requires-Dist: click>=8.1.8
217
217
  Requires-Dist: distro>=1.9.0
218
218
  Requires-Dist: httpx>=0.28.1
@@ -225,12 +225,18 @@ Requires-Dist: socksio>=1.0.0
225
225
  Requires-Dist: typer>=0.16.0
226
226
  Provides-Extra: all
227
227
  Requires-Dist: cohere>=5.15.0; extra == 'all'
228
+ Requires-Dist: google-genai>=1.20.0; extra == 'all'
229
+ Requires-Dist: huggingface-hub>=0.33.0; extra == 'all'
228
230
  Requires-Dist: ollama>=0.5.1; extra == 'all'
229
231
  Requires-Dist: volcengine-python-sdk>=3.0.15; extra == 'all'
230
232
  Provides-Extra: cohere
231
233
  Requires-Dist: cohere>=5.15.0; extra == 'cohere'
232
234
  Provides-Extra: doubao
233
235
  Requires-Dist: volcengine-python-sdk>=3.0.15; extra == 'doubao'
236
+ Provides-Extra: gemini
237
+ Requires-Dist: google-genai>=1.20.0; extra == 'gemini'
238
+ Provides-Extra: huggingface
239
+ Requires-Dist: huggingface-hub>=0.33.0; extra == 'huggingface'
234
240
  Provides-Extra: ollama
235
241
  Requires-Dist: ollama>=0.5.1; extra == 'ollama'
236
242
  Description-Content-Type: text/markdown
@@ -323,14 +329,8 @@ Yaicli has several optional dependencies group, you can copy below commands to i
323
329
  # install all denpendencies
324
330
  pip install 'yaicli[all]'
325
331
 
326
- # install with ollama support
327
- pip instsall 'yaicli[ollama]'
328
-
329
- # install with cohere support
330
- pip install 'yaicli[cohere]'
331
-
332
- # install with doubao support
333
- pip install 'yaicli[doubao]'
332
+ # install with specific provider support
333
+ pip instsall 'yaicli[ollama,cohere,doubao,huggingface,gemini]'
334
334
  ```
335
335
 
336
336
  Install by `uv`.
@@ -339,14 +339,8 @@ Install by `uv`.
339
339
  # install all denpendencies
340
340
  uv tool install 'yaicli[all]'
341
341
 
342
- # install with ollama support
343
- uv tool instsall 'yaicli[ollama]'
344
-
345
- # install with cohere support
346
- uv tool install 'yaicli[cohere]'
347
-
348
- # install with doubao support
349
- uv tool install 'yaicli[doubao]'
342
+ # install with specific provider support
343
+ uv tool instsall 'yaicli[ollama,cohere,doubao,huggingface,gemini]'
350
344
  ```
351
345
 
352
346
  ### Install from Source
@@ -357,6 +351,31 @@ cd yaicli
357
351
  pip install .
358
352
  ```
359
353
 
354
+ ## Buildin Supported Providers
355
+
356
+ - AI21
357
+ - Chatglm
358
+ - Chuts
359
+ - Cohere
360
+ - Cohere Badrock
361
+ - Cohere Sagemaker
362
+ - Deepseek
363
+ - Doubao
364
+ - Gemini
365
+ - Vertex AI
366
+ - Groq
367
+ - Huggingface
368
+ - Minimax
369
+ - ModelScope
370
+ - Ollama
371
+ - Openai
372
+ - Sambanova
373
+ - Siliconflow
374
+ - Targon
375
+ - X AI
376
+ - Yi
377
+ - Unlimited OpenAI-compatible providers
378
+
360
379
  ## ⚙️ Configuration
361
380
 
362
381
  YAICLI uses a simple configuration file to store your preferences and API keys.
@@ -494,6 +513,15 @@ API_KEY=
494
513
  MODEL=gpt-4o
495
514
  ```
496
515
 
516
+ Extra params:
517
+
518
+ ```ini
519
+ # REASONING_EFFORT: [high, midium, low]
520
+ REASONING_EFFORT=
521
+ ```
522
+
523
+ See official for more details: https://platform.openai.com/docs/guides/reasoning?api-mode=chat
524
+
497
525
  #### Deepseek
498
526
 
499
527
  ```ini
@@ -510,6 +538,48 @@ API_KEY=
510
538
  MODEL=deepseek/deepseek-chat-v3-0324
511
539
  ```
512
540
 
541
+ #### Gemini
542
+
543
+ Basic config:
544
+
545
+ ```ini
546
+ PROVIDER=gemini
547
+ API_KEY=
548
+ MODEL=gemini-2.5-flash
549
+ ```
550
+
551
+ Extra params:
552
+
553
+ ```ini
554
+ TOP_K=
555
+ PRESENCE_PENALTY=
556
+ FREQUENCY_PENALTY=
557
+ SEED=
558
+ THINKING_BUDGET=
559
+ API_VERSION=
560
+ BASE_URL=
561
+ ```
562
+
563
+ #### Vertex AI
564
+
565
+ ```ini
566
+ PROVIDER=vertexai
567
+ MODEL=gemini-2.5-flash
568
+ PROJECT=
569
+ LOCATION=
570
+ ```
571
+
572
+ #### Huggingface
573
+
574
+ ```ini
575
+ HF_PROVIDER=sambanova
576
+ PROVIDER=huggingface
577
+ API_KEY=
578
+ MODEL=deepseek-ai/DeepSeek-R1-0528
579
+ ```
580
+
581
+ See official docs for `HF_PROVIDER`: https://huggingface.co/docs/inference-providers/index
582
+
513
583
  #### Groq
514
584
 
515
585
  ```ini
@@ -534,6 +604,15 @@ API_KEY=
534
604
  MODEL=glm-4-plus
535
605
  ```
536
606
 
607
+ Extra params:
608
+
609
+ Check offcial docs: https://bigmodel.cn/dev/api/normal-model/glm-4
610
+
611
+ ```ini
612
+ # true or false
613
+ DO_SAMPLE=
614
+ ```
615
+
537
616
  #### Chutes
538
617
 
539
618
  ```ini
@@ -558,6 +637,16 @@ API_KEY=
558
637
  MODEL=DeepSeek-V3-0324
559
638
  ```
560
639
 
640
+ Only a few models support tool call as below:
641
+
642
+ - Meta-Llama-3.1-8B-Instruct
643
+ - Meta-Llama-3.1-405B-Instruct
644
+ - Meta-Llama-3.3-70B-Instruct
645
+ - Llama-4-Scout-17B-16E-Instruct
646
+ - DeepSeek-V3-0324
647
+
648
+ See official docs for more detail: https://docs.sambanova.ai/cloud/docs/capabilities/function-calling
649
+
561
650
  #### ModelScope
562
651
 
563
652
  ```ini
@@ -601,6 +690,45 @@ API_KEY=
601
690
  MODEL=command-a-03-2025
602
691
  ```
603
692
 
693
+ Check official docs: https://docs.cohere.com/docs/text-gen-quickstart
694
+
695
+ Support keys:
696
+
697
+ ```ini
698
+ ENVIRONMENT=
699
+ ```
700
+
701
+ For private deploy and Azure api, you need to set BASE_URL.
702
+
703
+ ```ini
704
+ PROVIDER=cohere
705
+ API_KEY=
706
+ MODEL=command-a-03-2025
707
+ BASE_URL=<YOUR_ENDPOINT>
708
+ ```
709
+
710
+ For Bedrock and Sagemaker cohere api, you have to set below keys:
711
+
712
+ See https://docs.cohere.com/docs/text-gen-quickstart.
713
+
714
+ ```ini
715
+ PROVIDER=cohere-bedrock
716
+ ; PROVIDER=cohere-sagemaker
717
+ API_KEY=
718
+ MODEL=command-a-03-2025
719
+
720
+ AWS_REGION=xx
721
+ AWS_ACCESS_KEY_ID=xx
722
+ AWS_SECRET_ACCESS_KEY=xx
723
+ AWS_SESSION_TOKEN=xx
724
+ ```
725
+
726
+ Note `MODEL` for Sagemaker should be endpoint name
727
+
728
+ ```ini
729
+ MODEL=<YOUR_ENDPOINT_NAME>
730
+ ```
731
+
604
732
  #### Doubao
605
733
 
606
734
  You have to install doubao dependencies, `pip install 'yaicli[doubao]'`
@@ -1,10 +1,10 @@
1
- pyproject.toml,sha256=xXPLxBBTEBHwusE6ko3ASXLhV3a5FV26eHNRAzkQzwc,2434
1
+ pyproject.toml,sha256=LQv7NHuPZjn7h03OWDzftK8V0G_OG0626EkpVEUh4IA,2756
2
2
  yaicli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  yaicli/chat.py,sha256=_emvZEdgMBth2nQGaNWPf0P45oW2k3bpuIwqsxFcM5A,13676
4
- yaicli/cli.py,sha256=YUKbtvRcNbe6iTmm0dx-38QDzzrAP4cOYaOChxpJ2wg,23673
5
- yaicli/config.py,sha256=HrWYcelLXE61XX719eVcuuo3292xxf1BNQznWdvjQFQ,6535
4
+ yaicli/cli.py,sha256=s8Bj4MSQmLblh4fHBPKS-DvJoAdMmp64KC7e7BwzmOs,23322
5
+ yaicli/config.py,sha256=_xLHgyW5dDg76bL1SyTcrQCnVs4dFpXafIS3sClshK0,6563
6
6
  yaicli/console.py,sha256=vARPJd-3lafutsQWrGntQVjLrYqaJD3qisN82pmuhjU,1973
7
- yaicli/const.py,sha256=jC01jLLuuYJ7K_QaekXkIN7j1bIKCoGwJeTpquJy55Q,8178
7
+ yaicli/const.py,sha256=G-EhMsSfOjKQLBhpOAc3pRtjvKmmWrwyyuyuGKHt7wk,8194
8
8
  yaicli/entry.py,sha256=Q1eqLE7tcHide7ooyPO7OCJpKE2YVuxR-NNFA2Pt2Hw,8693
9
9
  yaicli/exceptions.py,sha256=WBYg8OTJJzaj7lt6HE7ZyBoe5T6A3yZRNCRfWd4iN0c,372
10
10
  yaicli/history.py,sha256=s-57X9FMsaQHF7XySq1gGH_jpd_cHHTYafYu2ECuG6M,2472
@@ -16,29 +16,32 @@ yaicli/tools.py,sha256=xw8KEs_xlSf79A2Aq1rAsUWahS6A_e5QMLt7QDXL5bs,5086
16
16
  yaicli/utils.py,sha256=bpo3Xhozpxsaci3FtEIKZ32l4ZdyWMsrHjYGX0tB4J4,4541
17
17
  yaicli/functions/__init__.py,sha256=_FJooQ9GkijG8xLwuU0cr5GBrGnC9Nc6bnCeUjrsT0k,1271
18
18
  yaicli/functions/buildin/execute_shell_command.py,sha256=unl1-F8p6QZajeHdA0u5UpURMJM0WhdWMUWCCCHVRcI,1320
19
- yaicli/llms/__init__.py,sha256=cN54nu-YalZipXjLW0YAAe0rRv0tXAQ8lLi1ohuTpao,363
19
+ yaicli/llms/__init__.py,sha256=x78cJujrJkelXPnzHS6pzHkITZdgLYZqJMnrMHbptoc,134
20
20
  yaicli/llms/client.py,sha256=mkE9KHSuPcJfpNQXbzF2YXGkel3jrOW8KfQ3YYpaK4M,4453
21
- yaicli/llms/provider.py,sha256=ey8PCq5IS6E9YleErVw_j6GtudtOawhFVBt3X8Be51U,3017
21
+ yaicli/llms/provider.py,sha256=jF15kmY_tZVOjlw0fbHQkEvlmOX57-HBhILzG0KvXyo,3412
22
22
  yaicli/llms/providers/ai21_provider.py,sha256=SvgGj9_87KEqmxCMLbtsSkT8J3rUD7Mb21UF7pMWsks,3035
23
- yaicli/llms/providers/chatglm_provider.py,sha256=1xP4KVAi6SDKZ-lMi2wdzywtDydsTf6jDzh3jBBGMfA,6437
23
+ yaicli/llms/providers/chatglm_provider.py,sha256=QEzALvY5FBhuDCx6rHjLi7GSRTwTHNUwo8gg4FEdrxs,6466
24
24
  yaicli/llms/providers/chutes_provider.py,sha256=mtvWvRRfHPH3JFfzym87wXtPNiMpLnur3805N9acx7E,882
25
- yaicli/llms/providers/cohere_provider.py,sha256=hc6vQxbCHz9kM2tNKK-kGkuOf4-gkskXW9ctr9V4Cxk,10837
26
- yaicli/llms/providers/deepseek_provider.py,sha256=VjGes_jFin5WGYNFxYKMoHwgAQX_eYbYhQKfjeh-9eI,438
25
+ yaicli/llms/providers/cohere_provider.py,sha256=1UPzNqNOwM4_dsP4kvUaL9O6_bKjxm1lO6A0lM7hgS4,10959
26
+ yaicli/llms/providers/deepseek_provider.py,sha256=iIV97x2ZCcwhGkshc8wpRi-YAnAnmo0n-YRegPlaOwQ,488
27
27
  yaicli/llms/providers/doubao_provider.py,sha256=4eOdE91ITUn3uo3mvYAzdrHsuFIIBwZWib21mtZn8OY,1938
28
+ yaicli/llms/providers/gemini_provider.py,sha256=k_6JFmqiYPz5K8IioFic5tp8KAHgeeakjkPyqJVz8BI,8007
28
29
  yaicli/llms/providers/groq_provider.py,sha256=EiS1Yxw5jbAUBFCRYsJ57KYgZPk6oH-_gD72OfW8Oik,1358
29
- yaicli/llms/providers/infiniai_provider.py,sha256=1dseUIZiXsxYRATRtk_obFclyXMwi4glsP7l_tVtnv8,710
30
+ yaicli/llms/providers/huggingface_provider.py,sha256=vDJyyK_aOlvktNvs-cji6pDtmKEp61vuVJ783BZw4pc,1247
31
+ yaicli/llms/providers/infiniai_provider.py,sha256=8-nU6QE58PRoZL9b_HzbPp4yi6OGm7rXtfi9z7bJMOg,786
30
32
  yaicli/llms/providers/minimax_provider.py,sha256=W-j3dzrYMEv14bYt2pCPvPUxvxsUs-iMAcGB9yXakFs,744
31
- yaicli/llms/providers/modelscope_provider.py,sha256=BzBhYixiDEWB7gujQ0rcG__7nsv0psJRxdtYCYXBhdM,454
33
+ yaicli/llms/providers/modelscope_provider.py,sha256=qWM0T7r0Zf8k3pLzjj7_IFdnmnx7S3rJO0f9rRm8-_A,504
32
34
  yaicli/llms/providers/ollama_provider.py,sha256=pjpYjfnHWnExweZi1KGbT07JGkcxzKPhqICo8dD82D0,6967
33
- yaicli/llms/providers/openai_provider.py,sha256=qEvzi4UHCQzwK4FQp1m2Ioahenp2uCQdFftTjUa6PvI,9739
35
+ yaicli/llms/providers/openai_provider.py,sha256=ENn21QacP2iTcmbxuW7dgiw3_fUr8EGWhNSFR2yxjis,10079
34
36
  yaicli/llms/providers/openrouter_provider.py,sha256=R-7FrUrCAKPZ3gbnuo0M6rPlVw1mvSBjbLGs_FtZWM0,732
35
37
  yaicli/llms/providers/sambanova_provider.py,sha256=FFLrsvARt1UPAFWWgiuB6zvGzGKdtehKL58HdE1fo_M,2254
36
- yaicli/llms/providers/siliconflow_provider.py,sha256=7Ir73me9jGMO5TAZDjrAbX7tbb_QBmLjTGywY0yliqc,446
38
+ yaicli/llms/providers/siliconflow_provider.py,sha256=CW2VSt6evUyFy21vN84Nvmw1P0JpmHBLznsgiXMnHM0,496
37
39
  yaicli/llms/providers/targon_provider.py,sha256=RQ808eS9lvsyvlzyKaQYcN0NimbpoNWgjHUzY1gLNs4,717
40
+ yaicli/llms/providers/vertexai_provider.py,sha256=_ddrse1LfXRChTgkvxUlexyfJlfr0sVJH-Rmno3djSI,636
38
41
  yaicli/llms/providers/xai_provider.py,sha256=Q6iOvJZOXIAwRiiHMKEBgq8-W6SGVZ9QD1_532bNYfo,199
39
42
  yaicli/llms/providers/yi_provider.py,sha256=EnTm9qTxHPnzERsKqgGnzRIVhXFcAEdYqtOra65pGmY,719
40
- yaicli-0.6.2.dist-info/METADATA,sha256=rD4n886cYaYpdEyBugcrnJcKm6oiUIOMXLFczByLDiQ,53546
41
- yaicli-0.6.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
42
- yaicli-0.6.2.dist-info/entry_points.txt,sha256=iYVyQP0PJIm9tQnlQheqT435kK_xdGoi5j9aswGV9hA,66
43
- yaicli-0.6.2.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
44
- yaicli-0.6.2.dist-info/RECORD,,
43
+ yaicli-0.6.4.dist-info/METADATA,sha256=q1r4B-AADLIC9gAYEDd4BDHnAMnbjqDXrrOG3QNxbGc,55786
44
+ yaicli-0.6.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
45
+ yaicli-0.6.4.dist-info/entry_points.txt,sha256=iYVyQP0PJIm9tQnlQheqT435kK_xdGoi5j9aswGV9hA,66
46
+ yaicli-0.6.4.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
47
+ yaicli-0.6.4.dist-info/RECORD,,
File without changes