lite-agent 0.4.1__py3-none-any.whl → 0.5.0__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 lite-agent might be problematic. Click here for more details.

lite_agent/agent.py CHANGED
@@ -7,7 +7,7 @@ from funcall import Funcall
7
7
  from jinja2 import Environment, FileSystemLoader
8
8
  from litellm import CustomStreamWrapper
9
9
 
10
- from lite_agent.client import BaseLLMClient, LiteLLMClient
10
+ from lite_agent.client import BaseLLMClient, LiteLLMClient, ReasoningConfig
11
11
  from lite_agent.loggers import logger
12
12
  from lite_agent.stream_handlers import litellm_completion_stream_handler, litellm_response_stream_handler
13
13
  from lite_agent.types import AgentChunk, FunctionCallEvent, FunctionCallOutputEvent, RunnerMessages, ToolCall, message_to_llm_dict, system_message_to_llm_dict
@@ -32,15 +32,21 @@ class Agent:
32
32
  handoffs: list["Agent"] | None = None,
33
33
  message_transfer: Callable[[RunnerMessages], RunnerMessages] | None = None,
34
34
  completion_condition: str = "stop",
35
+ reasoning: ReasoningConfig = None,
35
36
  ) -> None:
36
37
  self.name = name
37
38
  self.instructions = instructions
39
+ self.reasoning = reasoning
40
+
38
41
  if isinstance(model, BaseLLMClient):
39
42
  # If model is a BaseLLMClient instance, use it directly
40
43
  self.client = model
41
44
  else:
42
45
  # Otherwise, create a LitellmClient instance
43
- self.client = LiteLLMClient(model=model)
46
+ self.client = LiteLLMClient(
47
+ model=model,
48
+ reasoning=reasoning,
49
+ )
44
50
  self.completion_condition = completion_condition
45
51
  self.handoffs = handoffs if handoffs else []
46
52
  self._parent: Agent | None = None
@@ -269,7 +275,12 @@ class Agent:
269
275
  res.append(message)
270
276
  return res
271
277
 
272
- async def completion(self, messages: RunnerMessages, record_to_file: Path | None = None) -> AsyncGenerator[AgentChunk, None]:
278
+ async def completion(
279
+ self,
280
+ messages: RunnerMessages,
281
+ record_to_file: Path | None = None,
282
+ reasoning: ReasoningConfig = None,
283
+ ) -> AsyncGenerator[AgentChunk, None]:
273
284
  # Apply message transfer callback if provided - always use legacy format for LLM compatibility
274
285
  processed_messages = messages
275
286
  if self.message_transfer:
@@ -284,6 +295,7 @@ class Agent:
284
295
  messages=self.message_histories,
285
296
  tools=tools,
286
297
  tool_choice="auto", # TODO: make this configurable
298
+ reasoning=reasoning,
287
299
  )
288
300
 
289
301
  # Ensure resp is a CustomStreamWrapper
@@ -292,7 +304,12 @@ class Agent:
292
304
  msg = "Response is not a CustomStreamWrapper, cannot stream chunks."
293
305
  raise TypeError(msg)
294
306
 
295
- async def responses(self, messages: RunnerMessages, record_to_file: Path | None = None) -> AsyncGenerator[AgentChunk, None]:
307
+ async def responses(
308
+ self,
309
+ messages: RunnerMessages,
310
+ record_to_file: Path | None = None,
311
+ reasoning: ReasoningConfig = None,
312
+ ) -> AsyncGenerator[AgentChunk, None]:
296
313
  # Apply message transfer callback if provided - always use legacy format for LLM compatibility
297
314
  processed_messages = messages
298
315
  if self.message_transfer:
@@ -306,6 +323,7 @@ class Agent:
306
323
  messages=self.message_histories,
307
324
  tools=tools,
308
325
  tool_choice="auto", # TODO: make this configurable
326
+ reasoning=reasoning,
309
327
  )
310
328
  return litellm_response_stream_handler(resp, record_to=record_to_file)
311
329
 
lite_agent/client.py CHANGED
@@ -1,25 +1,81 @@
1
1
  import abc
2
2
  import os
3
- from collections.abc import AsyncGenerator
4
3
  from typing import Any, Literal
5
4
 
6
5
  import litellm
7
- from litellm.types.llms.openai import ResponsesAPIStreamingResponse
8
6
  from openai.types.chat import ChatCompletionToolParam
9
7
  from openai.types.responses import FunctionToolParam
10
8
 
9
+ ReasoningEffort = Literal["minimal", "low", "medium", "high"]
10
+ ThinkingConfig = dict[str, Any] | None
11
+
12
+ # 统一的推理配置类型
13
+ ReasoningConfig = (
14
+ str
15
+ | dict[str, Any] # {"type": "enabled", "budget_tokens": 2048} 或其他配置
16
+ | bool # True/False 简单开关
17
+ | None # 不启用推理
18
+ )
19
+
20
+
21
+ def parse_reasoning_config(reasoning: ReasoningConfig) -> tuple[ReasoningEffort | None, ThinkingConfig]:
22
+ """
23
+ 解析统一的推理配置,返回 reasoning_effort 和 thinking_config。
24
+
25
+ Args:
26
+ reasoning: 统一的推理配置
27
+ - str: "minimal", "low", "medium", "high" -> reasoning_effort
28
+ - dict: {"type": "enabled", "budget_tokens": N} -> thinking_config
29
+ - bool: True -> "medium", False -> None
30
+ - None: 不启用推理
31
+
32
+ Returns:
33
+ tuple: (reasoning_effort, thinking_config)
34
+ """
35
+ if reasoning is None:
36
+ return None, None
37
+ if isinstance(reasoning, str):
38
+ # 字符串类型,使用 reasoning_effort
39
+ return reasoning, None
40
+ if isinstance(reasoning, dict):
41
+ # 字典类型,使用 thinking_config
42
+ return None, reasoning
43
+ if isinstance(reasoning, bool):
44
+ # 布尔类型,True 使用默认的 medium,False 不启用
45
+ return "medium" if reasoning else None, None
46
+ # 其他类型,默认不启用
47
+ return None, None
48
+
11
49
 
12
50
  class BaseLLMClient(abc.ABC):
13
51
  """Base class for LLM clients."""
14
52
 
15
- def __init__(self, *, model: str, api_key: str | None = None, api_base: str | None = None, api_version: str | None = None):
53
+ def __init__(
54
+ self,
55
+ *,
56
+ model: str,
57
+ api_key: str | None = None,
58
+ api_base: str | None = None,
59
+ api_version: str | None = None,
60
+ reasoning: ReasoningConfig = None,
61
+ ):
16
62
  self.model = model
17
63
  self.api_key = api_key
18
64
  self.api_base = api_base
19
65
  self.api_version = api_version
20
66
 
67
+ # 处理推理配置
68
+ self.reasoning_effort, self.thinking_config = parse_reasoning_config(reasoning)
69
+
21
70
  @abc.abstractmethod
22
- async def completion(self, messages: list[Any], tools: list[ChatCompletionToolParam] | None = None, tool_choice: str = "auto") -> Any: # noqa: ANN401
71
+ async def completion(
72
+ self,
73
+ messages: list[Any],
74
+ tools: list[ChatCompletionToolParam] | None = None,
75
+ tool_choice: str = "auto",
76
+ reasoning: ReasoningConfig = None,
77
+ **kwargs: Any, # noqa: ANN401
78
+ ) -> Any: # noqa: ANN401
23
79
  """Perform a completion request to the LLM."""
24
80
 
25
81
  @abc.abstractmethod
@@ -28,42 +84,95 @@ class BaseLLMClient(abc.ABC):
28
84
  messages: list[dict[str, Any]], # Changed from ResponseInputParam
29
85
  tools: list[FunctionToolParam] | None = None,
30
86
  tool_choice: Literal["none", "auto", "required"] = "auto",
31
- ) -> AsyncGenerator[ResponsesAPIStreamingResponse, None]:
87
+ reasoning: ReasoningConfig = None,
88
+ **kwargs: Any, # noqa: ANN401
89
+ ) -> Any: # noqa: ANN401
32
90
  """Perform a response request to the LLM."""
33
91
 
34
92
 
35
93
  class LiteLLMClient(BaseLLMClient):
36
- async def completion(self, messages: list[Any], tools: list[ChatCompletionToolParam] | None = None, tool_choice: str = "auto") -> Any: # noqa: ANN401
94
+ def _resolve_reasoning_params(
95
+ self,
96
+ reasoning: ReasoningConfig,
97
+ ) -> tuple[ReasoningEffort | None, ThinkingConfig]:
98
+ """解析推理配置参数。"""
99
+ if reasoning is not None:
100
+ return parse_reasoning_config(reasoning)
101
+
102
+ # 使用实例默认值
103
+ return self.reasoning_effort, self.thinking_config
104
+
105
+ async def completion(
106
+ self,
107
+ messages: list[Any],
108
+ tools: list[ChatCompletionToolParam] | None = None,
109
+ tool_choice: str = "auto",
110
+ reasoning: ReasoningConfig = None,
111
+ **kwargs: Any, # noqa: ANN401
112
+ ) -> Any: # noqa: ANN401
37
113
  """Perform a completion request to the Litellm API."""
38
- return await litellm.acompletion(
39
- model=self.model,
40
- messages=messages,
41
- tools=tools,
42
- tool_choice=tool_choice,
43
- api_version=self.api_version,
44
- api_key=self.api_key,
45
- api_base=self.api_base,
46
- stream=True,
114
+
115
+ # 处理推理配置参数
116
+ final_reasoning_effort, final_thinking_config = self._resolve_reasoning_params(
117
+ reasoning,
47
118
  )
48
119
 
120
+ # Prepare completion parameters
121
+ completion_params = {
122
+ "model": self.model,
123
+ "messages": messages,
124
+ "tools": tools,
125
+ "tool_choice": tool_choice,
126
+ "api_version": self.api_version,
127
+ "api_key": self.api_key,
128
+ "api_base": self.api_base,
129
+ "stream": True,
130
+ **kwargs,
131
+ }
132
+
133
+ # Add reasoning parameters if specified
134
+ if final_reasoning_effort is not None:
135
+ completion_params["reasoning_effort"] = final_reasoning_effort
136
+ if final_thinking_config is not None:
137
+ completion_params["thinking"] = final_thinking_config
138
+
139
+ return await litellm.acompletion(**completion_params)
140
+
49
141
  async def responses(
50
142
  self,
51
143
  messages: list[dict[str, Any]], # Changed from ResponseInputParam
52
144
  tools: list[FunctionToolParam] | None = None,
53
145
  tool_choice: Literal["none", "auto", "required"] = "auto",
54
- ) -> AsyncGenerator[ResponsesAPIStreamingResponse, None]:
146
+ reasoning: ReasoningConfig = None,
147
+ **kwargs: Any, # noqa: ANN401
148
+ ) -> Any: # type: ignore[return] # noqa: ANN401
55
149
  """Perform a response request to the Litellm API."""
56
150
 
57
151
  os.environ["DISABLE_AIOHTTP_TRANSPORT"] = "True"
58
152
 
59
- return await litellm.aresponses(
60
- model=self.model,
61
- input=messages, # type: ignore[arg-type]
62
- tools=tools,
63
- tool_choice=tool_choice,
64
- api_version=self.api_version,
65
- api_key=self.api_key,
66
- api_base=self.api_base,
67
- stream=True,
68
- store=False,
153
+ # 处理推理配置参数
154
+ final_reasoning_effort, final_thinking_config = self._resolve_reasoning_params(
155
+ reasoning,
69
156
  )
157
+
158
+ # Prepare response parameters
159
+ response_params = {
160
+ "model": self.model,
161
+ "input": messages, # type: ignore[arg-type]
162
+ "tools": tools,
163
+ "tool_choice": tool_choice,
164
+ "api_version": self.api_version,
165
+ "api_key": self.api_key,
166
+ "api_base": self.api_base,
167
+ "stream": True,
168
+ "store": False,
169
+ **kwargs,
170
+ }
171
+
172
+ # Add reasoning parameters if specified
173
+ if final_reasoning_effort is not None:
174
+ response_params["reasoning_effort"] = final_reasoning_effort
175
+ if final_thinking_config is not None:
176
+ response_params["thinking"] = final_thinking_config
177
+
178
+ return await litellm.aresponses(**response_params) # type: ignore[return-value]
lite_agent/runner.py CHANGED
@@ -168,13 +168,14 @@ class Runner:
168
168
  """Collect all chunks from an async generator into a list."""
169
169
  return [chunk async for chunk in stream]
170
170
 
171
- def run(
171
+ def run( # noqa: PLR0913
172
172
  self,
173
173
  user_input: UserInput,
174
174
  max_steps: int = 20,
175
175
  includes: Sequence[AgentChunkType] | None = None,
176
176
  context: "Any | None" = None, # noqa: ANN401
177
177
  record_to: PathLike | str | None = None,
178
+ agent_kwargs: dict[str, Any] | None = None,
178
179
  ) -> AsyncGenerator[AgentChunk, None]:
179
180
  """Run the agent and return a RunResponse object that can be asynchronously iterated for each chunk."""
180
181
  includes = self._normalize_includes(includes)
@@ -188,9 +189,16 @@ class Runner:
188
189
  case _:
189
190
  # Handle single message (BaseModel, TypedDict, or dict)
190
191
  self.append_message(user_input) # type: ignore[arg-type]
191
- return self._run(max_steps, includes, self._normalize_record_path(record_to), context=context)
192
+ return self._run(max_steps, includes, self._normalize_record_path(record_to), context=context, agent_kwargs=agent_kwargs)
192
193
 
193
- async def _run(self, max_steps: int, includes: Sequence[AgentChunkType], record_to: Path | None = None, context: Any | None = None) -> AsyncGenerator[AgentChunk, None]: # noqa: ANN401
194
+ async def _run(
195
+ self,
196
+ max_steps: int,
197
+ includes: Sequence[AgentChunkType],
198
+ record_to: Path | None = None,
199
+ context: Any | None = None, # noqa: ANN401
200
+ agent_kwargs: dict[str, Any] | None = None,
201
+ ) -> AsyncGenerator[AgentChunk, None]:
194
202
  """Run the agent and return a RunResponse object that can be asynchronously iterated for each chunk."""
195
203
  logger.debug(f"Running agent with messages: {self.messages}")
196
204
  steps = 0
@@ -213,11 +221,24 @@ class Runner:
213
221
  logger.debug(f"Step {steps}: finish_reason={finish_reason}, is_finish()={is_finish()}")
214
222
  # Convert to legacy format only when needed for LLM communication
215
223
  # This allows us to keep the new format internally but ensures compatibility
224
+ # Extract agent kwargs for reasoning configuration
225
+ reasoning = None
226
+ if agent_kwargs:
227
+ reasoning = agent_kwargs.get("reasoning")
228
+
216
229
  match self.api:
217
230
  case "completion":
218
- resp = await self.agent.completion(self.messages, record_to_file=record_to)
231
+ resp = await self.agent.completion(
232
+ self.messages,
233
+ record_to_file=record_to,
234
+ reasoning=reasoning,
235
+ )
219
236
  case "responses":
220
- resp = await self.agent.responses(self.messages, record_to_file=record_to)
237
+ resp = await self.agent.responses(
238
+ self.messages,
239
+ record_to_file=record_to,
240
+ reasoning=reasoning,
241
+ )
221
242
  case _:
222
243
  msg = f"Unknown API type: {self.api}"
223
244
  raise ValueError(msg)
@@ -367,11 +388,6 @@ class Runner:
367
388
  msg = "Cannot continue running without a valid last message from the assistant."
368
389
  raise ValueError(msg)
369
390
 
370
- last_message = self.messages[-1]
371
- if not (isinstance(last_message, NewAssistantMessage) or (hasattr(last_message, "role") and getattr(last_message, "role", None) == "assistant")):
372
- msg = "Cannot continue running without a valid last message from the assistant."
373
- raise ValueError(msg)
374
-
375
391
  resp = self._run(max_steps=max_steps, includes=includes, record_to=self._normalize_record_path(record_to), context=context)
376
392
  async for chunk in resp:
377
393
  yield chunk
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lite-agent
3
- Version: 0.4.1
3
+ Version: 0.5.0
4
4
  Summary: A lightweight, extensible framework for building AI agent.
5
5
  Author-email: Jianqi Pan <jannchie@gmail.com>
6
6
  License: MIT
@@ -1,11 +1,11 @@
1
1
  lite_agent/__init__.py,sha256=Swuefee0etSiaDnn30K2hBNV9UI3hIValW3A-pRE7e0,338
2
- lite_agent/agent.py,sha256=t4AYlw3aF2DCPXf2W3s7aow0ql1ON5O2Q8VVuyoN6UI,22936
2
+ lite_agent/agent.py,sha256=M0U59KpMy6OGFje6yZuQCYVGr4oBboRwbtImPF59o2w,23314
3
3
  lite_agent/chat_display.py,sha256=b0sUH3fkutc4e_KAKH7AtPu2msyLloNIAiWqCNavdds,30533
4
- lite_agent/client.py,sha256=m2jfBPIsleMZ1QCczjyHND-PIF17kQh4RTuf5FaipGM,2571
4
+ lite_agent/client.py,sha256=HG-NbTIUSFAUAPjRow3TFYJxvTc6Y4bdT2oJWIJNEEk,5963
5
5
  lite_agent/loggers.py,sha256=XkNkdqwD_nQGfhQJ-bBWT7koci_mMkNw3aBpyMhOICw,57
6
6
  lite_agent/message_transfers.py,sha256=9qucjc-uSIXvVfhcmVRC_0lp0Q8sWp99dV4ReCh6ZlI,4428
7
7
  lite_agent/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- lite_agent/runner.py,sha256=ACZuFJ2dNpdg4Tzeg-bl4Th1X14uhHJdELcBWe5E_Us,40155
8
+ lite_agent/runner.py,sha256=U7eVNAJ_VLwgbPPpn-vggSgvBmFl8wMMFWn3mWCsDow,40423
9
9
  lite_agent/processors/__init__.py,sha256=ybpAzpMBIE9v5I24wIBZRXeaOaPNTmoKH13aofgNI6Q,234
10
10
  lite_agent/processors/completion_event_processor.py,sha256=8fQYRofgBd8t0V3oUakTOmZdv5Q9tCuzADGCGvVgy0k,13442
11
11
  lite_agent/processors/response_event_processor.py,sha256=CElJMUzLs8mklVqJtoLiVu-NTq0Dz2NNd9YdAKpjgE0,8088
@@ -18,6 +18,6 @@ lite_agent/types/__init__.py,sha256=QKuhjFWRcpAlsBK9JYgoCABpoQExwhuyGudJoiiqQfs,
18
18
  lite_agent/types/events.py,sha256=mFMqV55WWJbPDyb_P61nd3qMLpEnwZgVY6NTKFkINkg,2389
19
19
  lite_agent/types/messages.py,sha256=c7nTIWqXNo562het_vaWcZvsoy-adkARwAYn4JNqm0c,9897
20
20
  lite_agent/types/tool_calls.py,sha256=Xnut8-2-Ld9vgA2GKJY6BbFlBaAv_n4W7vo7Jx21A-E,260
21
- lite_agent-0.4.1.dist-info/METADATA,sha256=iQIr1OAdiVK5Ad6Uho65OpqS1u4YC9sOaoxKZ1FssOs,3456
22
- lite_agent-0.4.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
23
- lite_agent-0.4.1.dist-info/RECORD,,
21
+ lite_agent-0.5.0.dist-info/METADATA,sha256=20K2Xirnyawl1uN_I8TLcuGlgRjNhs04hz2BtDDRnbM,3456
22
+ lite_agent-0.5.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
23
+ lite_agent-0.5.0.dist-info/RECORD,,