lite-agent 0.5.0__py3-none-any.whl → 0.8.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
@@ -5,12 +5,23 @@ from typing import Any, Optional
5
5
 
6
6
  from funcall import Funcall
7
7
  from jinja2 import Environment, FileSystemLoader
8
- from litellm import CustomStreamWrapper
9
8
 
10
9
  from lite_agent.client import BaseLLMClient, LiteLLMClient, ReasoningConfig
10
+ from lite_agent.constants import CompletionMode, ToolName
11
11
  from lite_agent.loggers import logger
12
- from lite_agent.stream_handlers import litellm_completion_stream_handler, litellm_response_stream_handler
13
- from lite_agent.types import AgentChunk, FunctionCallEvent, FunctionCallOutputEvent, RunnerMessages, ToolCall, message_to_llm_dict, system_message_to_llm_dict
12
+ from lite_agent.response_handlers import CompletionResponseHandler, ResponsesAPIHandler
13
+ from lite_agent.types import (
14
+ AgentChunk,
15
+ AssistantTextContent,
16
+ AssistantToolCall,
17
+ AssistantToolCallResult,
18
+ FunctionCallEvent,
19
+ FunctionCallOutputEvent,
20
+ RunnerMessages,
21
+ ToolCall,
22
+ message_to_llm_dict,
23
+ system_message_to_llm_dict,
24
+ )
14
25
  from lite_agent.types.messages import NewAssistantMessage, NewSystemMessage, NewUserMessage
15
26
 
16
27
  TEMPLATES_DIR = Path(__file__).parent / "templates"
@@ -22,7 +33,7 @@ WAIT_FOR_USER_INSTRUCTIONS_TEMPLATE = jinja_env.get_template("wait_for_user_inst
22
33
 
23
34
 
24
35
  class Agent:
25
- def __init__( # noqa: PLR0913
36
+ def __init__(
26
37
  self,
27
38
  *,
28
39
  model: str | BaseLLMClient,
@@ -33,10 +44,24 @@ class Agent:
33
44
  message_transfer: Callable[[RunnerMessages], RunnerMessages] | None = None,
34
45
  completion_condition: str = "stop",
35
46
  reasoning: ReasoningConfig = None,
47
+ stop_before_tools: list[str] | list[Callable] | None = None,
36
48
  ) -> None:
37
49
  self.name = name
38
50
  self.instructions = instructions
39
51
  self.reasoning = reasoning
52
+ # Convert stop_before_functions to function names
53
+ if stop_before_tools:
54
+ self.stop_before_functions = set()
55
+ for func in stop_before_tools:
56
+ if isinstance(func, str):
57
+ self.stop_before_functions.add(func)
58
+ elif callable(func):
59
+ self.stop_before_functions.add(func.__name__)
60
+ else:
61
+ msg = f"stop_before_functions must contain strings or callables, got {type(func)}"
62
+ raise TypeError(msg)
63
+ else:
64
+ self.stop_before_functions = set()
40
65
 
41
66
  if isinstance(model, BaseLLMClient):
42
67
  # If model is a BaseLLMClient instance, use it directly
@@ -55,7 +80,7 @@ class Agent:
55
80
  self.fc = Funcall(tools)
56
81
 
57
82
  # Add wait_for_user tool if completion condition is "call"
58
- if completion_condition == "call":
83
+ if completion_condition == CompletionMode.CALL:
59
84
  self._add_wait_for_user_tool()
60
85
 
61
86
  # Set parent for handoff agents
@@ -100,7 +125,7 @@ class Agent:
100
125
 
101
126
  # Add single dynamic tool for all transfers
102
127
  self.fc.add_dynamic_tool(
103
- name="transfer_to_agent",
128
+ name=ToolName.TRANSFER_TO_AGENT,
104
129
  description="Transfer conversation to another agent.",
105
130
  parameters={
106
131
  "name": {
@@ -130,7 +155,7 @@ class Agent:
130
155
 
131
156
  # Add dynamic tool for parent transfer
132
157
  self.fc.add_dynamic_tool(
133
- name="transfer_to_parent",
158
+ name=ToolName.TRANSFER_TO_PARENT,
134
159
  description="Transfer conversation back to parent agent when current task is completed or cannot be solved by current agent",
135
160
  parameters={},
136
161
  required=[],
@@ -161,7 +186,7 @@ class Agent:
161
186
  try:
162
187
  # Try to remove the existing transfer tool
163
188
  if hasattr(self.fc, "remove_dynamic_tool"):
164
- self.fc.remove_dynamic_tool("transfer_to_agent")
189
+ self.fc.remove_dynamic_tool(ToolName.TRANSFER_TO_AGENT)
165
190
  except Exception as e:
166
191
  # If removal fails, log and continue anyway
167
192
  logger.debug(f"Failed to remove existing transfer tool: {e}")
@@ -206,31 +231,30 @@ class Agent:
206
231
  for message in messages:
207
232
  if isinstance(message, NewAssistantMessage):
208
233
  for item in message.content:
209
- match item.type:
210
- case "text":
211
- res.append(
212
- {
213
- "role": "assistant",
214
- "content": item.text,
215
- },
216
- )
217
- case "tool_call":
218
- res.append(
219
- {
220
- "type": "function_call",
221
- "call_id": item.call_id,
222
- "name": item.name,
223
- "arguments": item.arguments,
224
- },
225
- )
226
- case "tool_call_result":
227
- res.append(
228
- {
229
- "type": "function_call_output",
230
- "call_id": item.call_id,
231
- "output": item.output,
232
- },
233
- )
234
+ if isinstance(item, AssistantTextContent):
235
+ res.append(
236
+ {
237
+ "role": "assistant",
238
+ "content": item.text,
239
+ },
240
+ )
241
+ elif isinstance(item, AssistantToolCall):
242
+ res.append(
243
+ {
244
+ "type": "function_call",
245
+ "call_id": item.call_id,
246
+ "name": item.name,
247
+ "arguments": item.arguments,
248
+ },
249
+ )
250
+ elif isinstance(item, AssistantToolCallResult):
251
+ res.append(
252
+ {
253
+ "type": "function_call_output",
254
+ "call_id": item.call_id,
255
+ "output": item.output,
256
+ },
257
+ )
234
258
  elif isinstance(message, NewSystemMessage):
235
259
  res.append(
236
260
  {
@@ -270,9 +294,6 @@ class Agent:
270
294
  "content": contents,
271
295
  },
272
296
  )
273
- # Handle dict messages (legacy format)
274
- elif isinstance(message, dict):
275
- res.append(message)
276
297
  return res
277
298
 
278
299
  async def completion(
@@ -280,6 +301,8 @@ class Agent:
280
301
  messages: RunnerMessages,
281
302
  record_to_file: Path | None = None,
282
303
  reasoning: ReasoningConfig = None,
304
+ *,
305
+ streaming: bool = True,
283
306
  ) -> AsyncGenerator[AgentChunk, None]:
284
307
  # Apply message transfer callback if provided - always use legacy format for LLM compatibility
285
308
  processed_messages = messages
@@ -296,19 +319,20 @@ class Agent:
296
319
  tools=tools,
297
320
  tool_choice="auto", # TODO: make this configurable
298
321
  reasoning=reasoning,
322
+ streaming=streaming,
299
323
  )
300
324
 
301
- # Ensure resp is a CustomStreamWrapper
302
- if isinstance(resp, CustomStreamWrapper):
303
- return litellm_completion_stream_handler(resp, record_to=record_to_file)
304
- msg = "Response is not a CustomStreamWrapper, cannot stream chunks."
305
- raise TypeError(msg)
325
+ # Use response handler for unified processing
326
+ handler = CompletionResponseHandler()
327
+ return handler.handle(resp, streaming=streaming, record_to=record_to_file)
306
328
 
307
329
  async def responses(
308
330
  self,
309
331
  messages: RunnerMessages,
310
332
  record_to_file: Path | None = None,
311
333
  reasoning: ReasoningConfig = None,
334
+ *,
335
+ streaming: bool = True,
312
336
  ) -> AsyncGenerator[AgentChunk, None]:
313
337
  # Apply message transfer callback if provided - always use legacy format for LLM compatibility
314
338
  processed_messages = messages
@@ -324,21 +348,33 @@ class Agent:
324
348
  tools=tools,
325
349
  tool_choice="auto", # TODO: make this configurable
326
350
  reasoning=reasoning,
351
+ streaming=streaming,
327
352
  )
328
- return litellm_response_stream_handler(resp, record_to=record_to_file)
353
+ # Use response handler for unified processing
354
+ handler = ResponsesAPIHandler()
355
+ return handler.handle(resp, streaming=streaming, record_to=record_to_file)
329
356
 
330
357
  async def list_require_confirm_tools(self, tool_calls: Sequence[ToolCall] | None) -> Sequence[ToolCall]:
331
358
  if not tool_calls:
332
359
  return []
333
360
  results = []
334
361
  for tool_call in tool_calls:
335
- tool_func = self.fc.function_registry.get(tool_call.function.name)
362
+ function_name = tool_call.function.name
363
+
364
+ # Check if function is in dynamic stop_before_functions list
365
+ if function_name in self.stop_before_functions:
366
+ logger.debug('Tool call "%s" requires confirmation (stop_before_functions)', tool_call.id)
367
+ results.append(tool_call)
368
+ continue
369
+
370
+ # Check decorator-based require_confirmation
371
+ tool_func = self.fc.function_registry.get(function_name)
336
372
  if not tool_func:
337
- logger.warning("Tool function %s not found in registry", tool_call.function.name)
373
+ logger.warning("Tool function %s not found in registry", function_name)
338
374
  continue
339
- tool_meta = self.fc.get_tool_meta(tool_call.function.name)
375
+ tool_meta = self.fc.get_tool_meta(function_name)
340
376
  if tool_meta["require_confirm"]:
341
- logger.debug('Tool call "%s" requires confirmation', tool_call.id)
377
+ logger.debug('Tool call "%s" requires confirmation (decorator)', tool_call.id)
342
378
  results.append(tool_call)
343
379
  return results
344
380
 
@@ -393,10 +429,42 @@ class Agent:
393
429
  role = message_dict.get("role")
394
430
 
395
431
  if role == "assistant":
396
- # Look ahead for function_call messages
432
+ # Extract tool_calls from content if present
397
433
  tool_calls = []
434
+ content = message_dict.get("content", [])
435
+
436
+ # Handle both string and array content
437
+ if isinstance(content, list):
438
+ # Extract tool_calls from content array and filter out non-text content
439
+ filtered_content = []
440
+ for item in content:
441
+ if isinstance(item, dict):
442
+ if item.get("type") == "tool_call":
443
+ tool_call = {
444
+ "id": item.get("call_id", ""),
445
+ "type": "function",
446
+ "function": {
447
+ "name": item.get("name", ""),
448
+ "arguments": item.get("arguments", "{}"),
449
+ },
450
+ "index": len(tool_calls),
451
+ }
452
+ tool_calls.append(tool_call)
453
+ elif item.get("type") == "text":
454
+ filtered_content.append(item)
455
+ # Skip tool_call_result - they should be handled by separate function_call_output messages
456
+
457
+ # Update content to only include text items
458
+ if filtered_content:
459
+ message_dict = message_dict.copy()
460
+ message_dict["content"] = filtered_content
461
+ elif tool_calls:
462
+ # If we have tool_calls but no text content, set content to None per OpenAI API spec
463
+ message_dict = message_dict.copy()
464
+ message_dict["content"] = None
465
+
466
+ # Look ahead for function_call messages (legacy support)
398
467
  j = i + 1
399
-
400
468
  while j < len(messages):
401
469
  next_message = messages[j]
402
470
  next_dict = message_to_llm_dict(next_message) if isinstance(next_message, (NewUserMessage, NewSystemMessage, NewAssistantMessage)) else next_message
@@ -421,6 +489,13 @@ class Agent:
421
489
  if tool_calls:
422
490
  assistant_msg["tool_calls"] = tool_calls # type: ignore
423
491
 
492
+ # Convert content format for OpenAI API compatibility
493
+ content = assistant_msg.get("content", [])
494
+ if isinstance(content, list):
495
+ # Extract text content and convert to string using list comprehension
496
+ text_parts = [item.get("text", "") for item in content if isinstance(item, dict) and item.get("type") == "text"]
497
+ assistant_msg["content"] = " ".join(text_parts) if text_parts else None
498
+
424
499
  converted_messages.append(assistant_msg)
425
500
  i = j # Skip the function_call messages we've processed
426
501
 
@@ -533,9 +608,73 @@ class Agent:
533
608
 
534
609
  # Add dynamic tool for task completion
535
610
  self.fc.add_dynamic_tool(
536
- name="wait_for_user",
611
+ name=ToolName.WAIT_FOR_USER,
537
612
  description="Call this function when you have completed your assigned task or need more information from the user.",
538
613
  parameters={},
539
614
  required=[],
540
615
  handler=wait_for_user_handler,
541
616
  )
617
+
618
+ def set_stop_before_functions(self, functions: list[str] | list[Callable]) -> None:
619
+ """Set the list of functions that require confirmation before execution.
620
+
621
+ Args:
622
+ functions: List of function names (str) or callable objects
623
+ """
624
+ self.stop_before_functions = set()
625
+ for func in functions:
626
+ if isinstance(func, str):
627
+ self.stop_before_functions.add(func)
628
+ elif callable(func):
629
+ self.stop_before_functions.add(func.__name__)
630
+ else:
631
+ msg = f"stop_before_functions must contain strings or callables, got {type(func)}"
632
+ raise TypeError(msg)
633
+ logger.debug(f"Set stop_before_functions to: {self.stop_before_functions}")
634
+
635
+ def add_stop_before_function(self, function: str | Callable) -> None:
636
+ """Add a function to the stop_before_functions list.
637
+
638
+ Args:
639
+ function: Function name (str) or callable object to add
640
+ """
641
+ if isinstance(function, str):
642
+ function_name = function
643
+ elif callable(function):
644
+ function_name = function.__name__
645
+ else:
646
+ msg = f"function must be a string or callable, got {type(function)}"
647
+ raise TypeError(msg)
648
+
649
+ self.stop_before_functions.add(function_name)
650
+ logger.debug(f"Added '{function_name}' to stop_before_functions")
651
+
652
+ def remove_stop_before_function(self, function: str | Callable) -> None:
653
+ """Remove a function from the stop_before_functions list.
654
+
655
+ Args:
656
+ function: Function name (str) or callable object to remove
657
+ """
658
+ if isinstance(function, str):
659
+ function_name = function
660
+ elif callable(function):
661
+ function_name = function.__name__
662
+ else:
663
+ msg = f"function must be a string or callable, got {type(function)}"
664
+ raise TypeError(msg)
665
+
666
+ self.stop_before_functions.discard(function_name)
667
+ logger.debug(f"Removed '{function_name}' from stop_before_functions")
668
+
669
+ def clear_stop_before_functions(self) -> None:
670
+ """Clear all function names from the stop_before_functions list."""
671
+ self.stop_before_functions.clear()
672
+ logger.debug("Cleared all stop_before_functions")
673
+
674
+ def get_stop_before_functions(self) -> set[str]:
675
+ """Get the current set of function names that require confirmation.
676
+
677
+ Returns:
678
+ Set of function names
679
+ """
680
+ return self.stop_before_functions.copy()
@@ -26,6 +26,8 @@ from lite_agent.types import (
26
26
  AgentSystemMessage,
27
27
  AgentUserMessage,
28
28
  AssistantMessageMeta,
29
+ AssistantToolCall,
30
+ AssistantToolCallResult,
29
31
  BasicMessageMeta,
30
32
  FlexibleRunnerMessage,
31
33
  LLMResponseMeta,
@@ -228,9 +230,9 @@ def _update_message_counts(message: FlexibleRunnerMessage, counts: dict[str, int
228
230
  counts["Assistant"] += 1
229
231
  # Count tool calls and outputs within the assistant message
230
232
  for content_item in message.content:
231
- if content_item.type == "tool_call":
233
+ if isinstance(content_item, AssistantToolCall):
232
234
  counts["Function Call"] += 1
233
- elif content_item.type == "tool_call_result":
235
+ elif isinstance(content_item, AssistantToolCallResult):
234
236
  counts["Function Output"] += 1
235
237
  elif isinstance(message, NewSystemMessage):
236
238
  counts["System"] += 1
@@ -295,10 +297,18 @@ def _process_object_meta(meta: BasicMessageMeta | LLMResponseMeta | AssistantMes
295
297
  """处理对象类型的 meta 数据。"""
296
298
  # LLMResponseMeta 和 AssistantMessageMeta 都有这些字段
297
299
  if isinstance(meta, (LLMResponseMeta, AssistantMessageMeta)):
298
- if hasattr(meta, "input_tokens") and meta.input_tokens is not None:
299
- total_input += int(meta.input_tokens)
300
- if hasattr(meta, "output_tokens") and meta.output_tokens is not None:
301
- total_output += int(meta.output_tokens)
300
+ # For AssistantMessageMeta, use the structured usage field
301
+ if isinstance(meta, AssistantMessageMeta) and meta.usage is not None:
302
+ if meta.usage.input_tokens is not None:
303
+ total_input += int(meta.usage.input_tokens)
304
+ if meta.usage.output_tokens is not None:
305
+ total_output += int(meta.usage.output_tokens)
306
+ # For LLMResponseMeta, use the flat fields
307
+ elif isinstance(meta, LLMResponseMeta):
308
+ if hasattr(meta, "input_tokens") and meta.input_tokens is not None:
309
+ total_input += int(meta.input_tokens)
310
+ if hasattr(meta, "output_tokens") and meta.output_tokens is not None:
311
+ total_output += int(meta.output_tokens)
302
312
  if hasattr(meta, "latency_ms") and meta.latency_ms is not None:
303
313
  total_latency += int(meta.latency_ms)
304
314
  if hasattr(meta, "output_time_ms") and meta.output_time_ms is not None:
@@ -363,11 +373,9 @@ def display_chat_summary(messages: RunnerMessages, *, console: Console | None =
363
373
  messages: 要汇总的消息列表
364
374
  console: Rich Console 实例,如果为 None 则创建新的
365
375
  """
366
- if console is None:
367
- console = Console()
368
-
376
+ active_console = console or Console()
369
377
  summary_table = build_chat_summary_table(messages)
370
- console.print(summary_table)
378
+ active_console.print(summary_table)
371
379
 
372
380
 
373
381
  def display_messages(
@@ -437,7 +445,7 @@ def display_messages(
437
445
  )
438
446
 
439
447
 
440
- def _display_single_message_compact( # noqa: PLR0913
448
+ def _display_single_message_compact(
441
449
  message: FlexibleRunnerMessage,
442
450
  *,
443
451
  index: int | None = None,
@@ -577,9 +585,9 @@ def _display_assistant_message_compact_v2(message: AgentAssistantMessage, contex
577
585
  meta_parts.append(f"Latency:{message.meta.latency_ms}ms")
578
586
  if message.meta.output_time_ms is not None:
579
587
  meta_parts.append(f"Output:{message.meta.output_time_ms}ms")
580
- if message.meta.input_tokens is not None and message.meta.output_tokens is not None:
581
- total_tokens = message.meta.input_tokens + message.meta.output_tokens
582
- meta_parts.append(f"Tokens:↑{message.meta.input_tokens}↓{message.meta.output_tokens}={total_tokens}")
588
+ if message.meta.usage and message.meta.usage.input_tokens is not None and message.meta.usage.output_tokens is not None:
589
+ total_tokens = message.meta.usage.input_tokens + message.meta.usage.output_tokens
590
+ meta_parts.append(f"Tokens:↑{message.meta.usage.input_tokens}↓{message.meta.usage.output_tokens}={total_tokens}")
583
591
 
584
592
  if meta_parts:
585
593
  meta_info = f" [dim]({' | '.join(meta_parts)})[/dim]"
lite_agent/client.py CHANGED
@@ -5,6 +5,7 @@ from typing import Any, Literal
5
5
  import litellm
6
6
  from openai.types.chat import ChatCompletionToolParam
7
7
  from openai.types.responses import FunctionToolParam
8
+ from pydantic import BaseModel
8
9
 
9
10
  ReasoningEffort = Literal["minimal", "low", "medium", "high"]
10
11
  ThinkingConfig = dict[str, Any] | None
@@ -18,6 +19,17 @@ ReasoningConfig = (
18
19
  )
19
20
 
20
21
 
22
+ class LLMConfig(BaseModel):
23
+ """LLM generation parameters configuration."""
24
+
25
+ temperature: float | None = None
26
+ max_tokens: int | None = None
27
+ top_p: float | None = None
28
+ frequency_penalty: float | None = None
29
+ presence_penalty: float | None = None
30
+ stop: list[str] | str | None = None
31
+
32
+
21
33
  def parse_reasoning_config(reasoning: ReasoningConfig) -> tuple[ReasoningEffort | None, ThinkingConfig]:
22
34
  """
23
35
  解析统一的推理配置,返回 reasoning_effort 和 thinking_config。
@@ -36,7 +48,10 @@ def parse_reasoning_config(reasoning: ReasoningConfig) -> tuple[ReasoningEffort
36
48
  return None, None
37
49
  if isinstance(reasoning, str):
38
50
  # 字符串类型,使用 reasoning_effort
39
- return reasoning, None
51
+ # 确保字符串是有效的 ReasoningEffort 值
52
+ if reasoning in ("minimal", "low", "medium", "high"):
53
+ return reasoning, None # type: ignore[return-value]
54
+ return None, None
40
55
  if isinstance(reasoning, dict):
41
56
  # 字典类型,使用 thinking_config
42
57
  return None, reasoning
@@ -58,13 +73,24 @@ class BaseLLMClient(abc.ABC):
58
73
  api_base: str | None = None,
59
74
  api_version: str | None = None,
60
75
  reasoning: ReasoningConfig = None,
76
+ llm_config: LLMConfig | None = None,
77
+ **llm_params: Any, # noqa: ANN401
61
78
  ):
62
79
  self.model = model
63
80
  self.api_key = api_key
64
81
  self.api_base = api_base
65
82
  self.api_version = api_version
66
83
 
84
+ # 处理 LLM 生成参数
85
+ if llm_config is not None:
86
+ self.llm_config = llm_config
87
+ else:
88
+ # 从 **llm_params 创建配置
89
+ self.llm_config = LLMConfig(**llm_params)
90
+
67
91
  # 处理推理配置
92
+ self.reasoning_effort: ReasoningEffort | None
93
+ self.thinking_config: ThinkingConfig
68
94
  self.reasoning_effort, self.thinking_config = parse_reasoning_config(reasoning)
69
95
 
70
96
  @abc.abstractmethod
@@ -74,6 +100,8 @@ class BaseLLMClient(abc.ABC):
74
100
  tools: list[ChatCompletionToolParam] | None = None,
75
101
  tool_choice: str = "auto",
76
102
  reasoning: ReasoningConfig = None,
103
+ *,
104
+ streaming: bool = True,
77
105
  **kwargs: Any, # noqa: ANN401
78
106
  ) -> Any: # noqa: ANN401
79
107
  """Perform a completion request to the LLM."""
@@ -85,6 +113,8 @@ class BaseLLMClient(abc.ABC):
85
113
  tools: list[FunctionToolParam] | None = None,
86
114
  tool_choice: Literal["none", "auto", "required"] = "auto",
87
115
  reasoning: ReasoningConfig = None,
116
+ *,
117
+ streaming: bool = True,
88
118
  **kwargs: Any, # noqa: ANN401
89
119
  ) -> Any: # noqa: ANN401
90
120
  """Perform a response request to the LLM."""
@@ -108,6 +138,8 @@ class LiteLLMClient(BaseLLMClient):
108
138
  tools: list[ChatCompletionToolParam] | None = None,
109
139
  tool_choice: str = "auto",
110
140
  reasoning: ReasoningConfig = None,
141
+ *,
142
+ streaming: bool = True,
111
143
  **kwargs: Any, # noqa: ANN401
112
144
  ) -> Any: # noqa: ANN401
113
145
  """Perform a completion request to the Litellm API."""
@@ -126,10 +158,24 @@ class LiteLLMClient(BaseLLMClient):
126
158
  "api_version": self.api_version,
127
159
  "api_key": self.api_key,
128
160
  "api_base": self.api_base,
129
- "stream": True,
161
+ "stream": streaming,
130
162
  **kwargs,
131
163
  }
132
164
 
165
+ # Add LLM generation parameters if specified
166
+ if self.llm_config.temperature is not None:
167
+ completion_params["temperature"] = self.llm_config.temperature
168
+ if self.llm_config.max_tokens is not None:
169
+ completion_params["max_tokens"] = self.llm_config.max_tokens
170
+ if self.llm_config.top_p is not None:
171
+ completion_params["top_p"] = self.llm_config.top_p
172
+ if self.llm_config.frequency_penalty is not None:
173
+ completion_params["frequency_penalty"] = self.llm_config.frequency_penalty
174
+ if self.llm_config.presence_penalty is not None:
175
+ completion_params["presence_penalty"] = self.llm_config.presence_penalty
176
+ if self.llm_config.stop is not None:
177
+ completion_params["stop"] = self.llm_config.stop
178
+
133
179
  # Add reasoning parameters if specified
134
180
  if final_reasoning_effort is not None:
135
181
  completion_params["reasoning_effort"] = final_reasoning_effort
@@ -144,6 +190,8 @@ class LiteLLMClient(BaseLLMClient):
144
190
  tools: list[FunctionToolParam] | None = None,
145
191
  tool_choice: Literal["none", "auto", "required"] = "auto",
146
192
  reasoning: ReasoningConfig = None,
193
+ *,
194
+ streaming: bool = True,
147
195
  **kwargs: Any, # noqa: ANN401
148
196
  ) -> Any: # type: ignore[return] # noqa: ANN401
149
197
  """Perform a response request to the Litellm API."""
@@ -164,11 +212,25 @@ class LiteLLMClient(BaseLLMClient):
164
212
  "api_version": self.api_version,
165
213
  "api_key": self.api_key,
166
214
  "api_base": self.api_base,
167
- "stream": True,
215
+ "stream": streaming,
168
216
  "store": False,
169
217
  **kwargs,
170
218
  }
171
219
 
220
+ # Add LLM generation parameters if specified
221
+ if self.llm_config.temperature is not None:
222
+ response_params["temperature"] = self.llm_config.temperature
223
+ if self.llm_config.max_tokens is not None:
224
+ response_params["max_tokens"] = self.llm_config.max_tokens
225
+ if self.llm_config.top_p is not None:
226
+ response_params["top_p"] = self.llm_config.top_p
227
+ if self.llm_config.frequency_penalty is not None:
228
+ response_params["frequency_penalty"] = self.llm_config.frequency_penalty
229
+ if self.llm_config.presence_penalty is not None:
230
+ response_params["presence_penalty"] = self.llm_config.presence_penalty
231
+ if self.llm_config.stop is not None:
232
+ response_params["stop"] = self.llm_config.stop
233
+
172
234
  # Add reasoning parameters if specified
173
235
  if final_reasoning_effort is not None:
174
236
  response_params["reasoning_effort"] = final_reasoning_effort
@@ -0,0 +1,30 @@
1
+ from typing import Literal
2
+
3
+
4
+ class CompletionMode:
5
+ """Agent completion modes."""
6
+
7
+ STOP: Literal["stop"] = "stop" # Traditional completion until model decides to stop
8
+ CALL: Literal["call"] = "call" # Completion until specific tool is called
9
+
10
+
11
+ class ToolName:
12
+ """System tool names."""
13
+
14
+ TRANSFER_TO_AGENT = "transfer_to_agent"
15
+ TRANSFER_TO_PARENT = "transfer_to_parent"
16
+ WAIT_FOR_USER = "wait_for_user"
17
+
18
+
19
+ class StreamIncludes:
20
+ """Default stream includes configuration."""
21
+
22
+ DEFAULT_INCLUDES = (
23
+ "completion_raw",
24
+ "usage",
25
+ "function_call",
26
+ "function_call_output",
27
+ "content_delta",
28
+ "function_call_delta",
29
+ "assistant_message",
30
+ )
@@ -5,7 +5,7 @@ This module provides common message transfer functions that can be used
5
5
  with agents to preprocess messages before sending them to the API.
6
6
  """
7
7
 
8
- from lite_agent.types import RunnerMessages
8
+ from lite_agent.types import NewUserMessage, RunnerMessages, UserTextContent
9
9
 
10
10
 
11
11
  def consolidate_history_transfer(messages: RunnerMessages) -> RunnerMessages:
@@ -43,8 +43,8 @@ def consolidate_history_transfer(messages: RunnerMessages) -> RunnerMessages:
43
43
  # Create the consolidated message
44
44
  consolidated_content = "以下是目前发生的所有交互:\n\n" + "\n".join(xml_content) + "\n\n接下来该做什么?"
45
45
 
46
- # Return a single user message
47
- return [{"role": "user", "content": consolidated_content}]
46
+ # Return a single user message using NewMessage format
47
+ return [NewUserMessage(content=[UserTextContent(text=consolidated_content)])]
48
48
 
49
49
 
50
50
  def _process_message_to_xml(message: dict | object) -> list[str]: