| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248 |
- """增强版 HelloAgentsLLM - 支持流式工具调用"""
- from dataclasses import dataclass, field
- from enum import Enum
- from typing import Optional, List, Dict, Union, Any, AsyncIterator
- from hello_agents.core.llm import HelloAgentsLLM
- from hello_agents.core.exceptions import HelloAgentsException
- # ==================== 流式工具调用数据结构 ====================
- class StreamToolEventType(Enum):
- """流式工具调用事件类型"""
- CONTENT = "content" # 文本内容增量
- TOOL_CALL_START = "tool_call_start" # 工具调用开始(收到ID和名称)
- TOOL_CALL_DELTA = "tool_call_delta" # 工具调用参数增量
- FINISH = "finish" # 流结束
- @dataclass
- class StreamToolEvent:
- """流式工具调用事件
- 封装流式响应中的不同类型数据,统一处理文本内容和工具调用。
- """
- event_type: StreamToolEventType
- # 文本内容
- content: Optional[str] = None
- # 工具调用
- tool_call_index: Optional[int] = None # 工具调用索引(用于增量累积)
- tool_call_id: Optional[str] = None # 工具调用ID
- tool_name: Optional[str] = None # 工具名称
- tool_arguments_delta: Optional[str] = None # 参数增量
- # 结束信息
- finish_reason: Optional[str] = None
- @property
- def is_content(self) -> bool:
- """是否为文本内容事件"""
- return self.event_type == StreamToolEventType.CONTENT
- @property
- def is_tool_call(self) -> bool:
- """是否为工具调用事件"""
- return self.event_type in (
- StreamToolEventType.TOOL_CALL_START,
- StreamToolEventType.TOOL_CALL_DELTA
- )
- @property
- def is_finish(self) -> bool:
- """是否为结束事件"""
- return self.event_type == StreamToolEventType.FINISH
- @dataclass
- class StreamToolCallResult:
- """流式工具调用完成后的结果
- 包含累积的文本内容和工具调用列表。
- """
- content: str = ""
- tool_calls: List[Dict[str, Any]] = field(default_factory=list)
- finish_reason: Optional[str] = None
- def add_content(self, delta: str):
- """添加文本内容"""
- self.content += delta
- def add_tool_call_start(self, index: int, tool_id: str, tool_name: str):
- """添加工具调用开始"""
- # 确保列表足够长
- while len(self.tool_calls) <= index:
- self.tool_calls.append({"id": "", "name": "", "arguments": ""})
- self.tool_calls[index]["id"] = tool_id
- self.tool_calls[index]["name"] = tool_name
- def add_tool_call_delta(self, index: int, arguments_delta: str):
- """添加工具调用参数增量"""
- while len(self.tool_calls) <= index:
- self.tool_calls.append({"id": "", "name": "", "arguments": ""})
- self.tool_calls[index]["arguments"] += arguments_delta
- def get_complete_tool_calls(self) -> List[Dict[str, Any]]:
- """获取完整的工具调用列表(过滤不完整的)"""
- return [
- tc for tc in self.tool_calls
- if tc["id"] and tc["name"]
- ]
- def to_assistant_message(self) -> Dict[str, Any]:
- """转换为助手消息格式(用于追加到消息历史)"""
- message: Dict[str, Any] = {"role": "assistant", "content": self.content or None}
- if self.tool_calls:
- message["tool_calls"] = [
- {
- "id": tc["id"],
- "type": "function",
- "function": {
- "name": tc["name"],
- "arguments": tc["arguments"]
- }
- }
- for tc in self.get_complete_tool_calls()
- ]
- return message
- # ==================== 增强版 LLM 类 ====================
- class EnhancedHelloAgentsLLM(HelloAgentsLLM):
- """
- 增强版 HelloAgentsLLM - 添加流式工具调用支持
- 继承自 HelloAgentsLLM,新增以下方法:
- - astream_invoke_with_tools: 异步流式工具调用
- - get_last_stream_tool_result: 获取最后一次流式工具调用的累积结果
- """
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self._last_stream_tool_result: Optional[StreamToolCallResult] = None
- async def astream_invoke_with_tools(
- self,
- messages: List[Dict],
- tools: List[Dict],
- tool_choice: Union[str, Dict] = "auto",
- **kwargs
- ) -> AsyncIterator[StreamToolEvent]:
- """
- 异步流式调用 LLM 并支持工具调用(Function Calling)
- 这是最优雅的流式工具调用方法,封装了所有流式处理的复杂逻辑。
- Args:
- messages: 消息列表
- tools: 工具 schema 列表
- tool_choice: 工具选择策略
- **kwargs: 其他参数(temperature, max_tokens 等)
- Yields:
- StreamToolEvent: 流式事件,可能是文本内容或工具调用增量
- Example:
- async for event in llm.astream_invoke_with_tools(messages, tools):
- if event.is_content:
- print(event.content, end="")
- elif event.event_type == StreamToolEventType.TOOL_CALL_START:
- print(f"\\n调用工具: {event.tool_name}")
- # 获取累积结果
- result = llm.get_last_stream_tool_result()
- """
- from openai import AsyncOpenAI
- # 创建异步客户端
- client = AsyncOpenAI(
- api_key=self.api_key,
- base_url=self.base_url,
- timeout=self.timeout
- )
- # 构建请求参数
- request_params: Dict[str, Any] = {
- "model": self.model,
- "messages": messages,
- "tools": tools,
- "tool_choice": tool_choice,
- "stream": True,
- }
- if kwargs.get("temperature") is not None:
- request_params["temperature"] = kwargs["temperature"]
- if self.max_tokens:
- request_params["max_tokens"] = self.max_tokens
- # 初始化累积结果
- result = StreamToolCallResult()
- try:
- response = await client.chat.completions.create(**request_params)
- async for chunk in response:
- if not chunk.choices:
- continue
- choice = chunk.choices[0]
- delta = choice.delta
- # 处理文本内容
- if delta.content:
- result.add_content(delta.content)
- yield StreamToolEvent(
- event_type=StreamToolEventType.CONTENT,
- content=delta.content
- )
- # 处理工具调用增量
- if delta.tool_calls:
- for tc_delta in delta.tool_calls:
- idx = tc_delta.index
- # 工具调用开始(收到 ID 或名称)
- if tc_delta.id or (tc_delta.function and tc_delta.function.name):
- tool_id = tc_delta.id or ""
- tool_name = tc_delta.function.name if tc_delta.function else ""
- if tool_id or tool_name:
- result.add_tool_call_start(idx, tool_id, tool_name)
- yield StreamToolEvent(
- event_type=StreamToolEventType.TOOL_CALL_START,
- tool_call_index=idx,
- tool_call_id=tool_id,
- tool_name=tool_name
- )
- # 工具调用参数增量
- if tc_delta.function and tc_delta.function.arguments:
- args_delta = tc_delta.function.arguments
- result.add_tool_call_delta(idx, args_delta)
- yield StreamToolEvent(
- event_type=StreamToolEventType.TOOL_CALL_DELTA,
- tool_call_index=idx,
- tool_arguments_delta=args_delta
- )
- # 处理结束原因
- if choice.finish_reason:
- result.finish_reason = choice.finish_reason
- yield StreamToolEvent(
- event_type=StreamToolEventType.FINISH,
- finish_reason=choice.finish_reason
- )
- except Exception as e:
- raise HelloAgentsException(f"流式工具调用失败: {str(e)}")
- # 保存累积结果供后续使用
- self._last_stream_tool_result = result
- def get_last_stream_tool_result(self) -> Optional[StreamToolCallResult]:
- """
- 获取最后一次流式工具调用的累积结果
- Returns:
- StreamToolCallResult 或 None
- """
- return self._last_stream_tool_result
|