flowllm 0.1.1__py3-none-any.whl → 0.1.2__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.
- flowllm/__init__.py +15 -6
- flowllm/app.py +4 -14
- flowllm/client/__init__.py +25 -0
- flowllm/client/async_http_client.py +81 -0
- flowllm/client/http_client.py +81 -0
- flowllm/client/mcp_client.py +133 -0
- flowllm/client/sync_mcp_client.py +116 -0
- flowllm/config/__init__.py +1 -0
- flowllm/config/{default_config.yaml → default.yaml} +3 -8
- flowllm/config/empty.yaml +37 -0
- flowllm/config/pydantic_config_parser.py +17 -17
- flowllm/context/base_context.py +27 -7
- flowllm/context/flow_context.py +6 -18
- flowllm/context/registry.py +5 -1
- flowllm/context/service_context.py +81 -37
- flowllm/embedding_model/__init__.py +1 -1
- flowllm/embedding_model/base_embedding_model.py +91 -0
- flowllm/embedding_model/openai_compatible_embedding_model.py +63 -5
- flowllm/flow/__init__.py +1 -0
- flowllm/flow/base_flow.py +72 -0
- flowllm/flow/base_tool_flow.py +15 -0
- flowllm/flow/gallery/__init__.py +8 -0
- flowllm/flow/gallery/cmd_flow.py +11 -0
- flowllm/flow/gallery/code_tool_flow.py +30 -0
- flowllm/flow/gallery/dashscope_search_tool_flow.py +34 -0
- flowllm/flow/gallery/deepsearch_tool_flow.py +39 -0
- flowllm/flow/gallery/expression_tool_flow.py +18 -0
- flowllm/flow/gallery/mock_tool_flow.py +67 -0
- flowllm/flow/gallery/tavily_search_tool_flow.py +30 -0
- flowllm/flow/gallery/terminate_tool_flow.py +30 -0
- flowllm/flow/parser/__init__.py +0 -0
- flowllm/{flow_engine/simple_flow_engine.py → flow/parser/expression_parser.py} +25 -67
- flowllm/llm/__init__.py +2 -1
- flowllm/llm/base_llm.py +94 -4
- flowllm/llm/litellm_llm.py +455 -0
- flowllm/llm/openai_compatible_llm.py +205 -5
- flowllm/op/__init__.py +11 -3
- flowllm/op/agent/__init__.py +0 -0
- flowllm/op/agent/react_op.py +83 -0
- flowllm/op/agent/react_prompt.yaml +28 -0
- flowllm/op/akshare/__init__.py +3 -0
- flowllm/op/akshare/get_ak_a_code_op.py +14 -22
- flowllm/op/akshare/get_ak_a_info_op.py +17 -20
- flowllm/op/{llm_base_op.py → base_llm_op.py} +6 -5
- flowllm/op/base_op.py +14 -35
- flowllm/op/base_ray_op.py +313 -0
- flowllm/op/code/__init__.py +1 -0
- flowllm/op/code/execute_code_op.py +42 -0
- flowllm/op/gallery/__init__.py +2 -0
- flowllm/op/{mock_op.py → gallery/mock_op.py} +4 -4
- flowllm/op/gallery/terminate_op.py +29 -0
- flowllm/op/parallel_op.py +2 -9
- flowllm/op/search/__init__.py +3 -0
- flowllm/op/search/dashscope_deep_research_op.py +260 -0
- flowllm/op/search/dashscope_search_op.py +179 -0
- flowllm/op/search/dashscope_search_prompt.yaml +13 -0
- flowllm/op/search/tavily_search_op.py +102 -0
- flowllm/op/sequential_op.py +1 -9
- flowllm/schema/flow_request.py +12 -0
- flowllm/schema/service_config.py +12 -16
- flowllm/schema/tool_call.py +13 -5
- flowllm/schema/vector_node.py +1 -0
- flowllm/service/__init__.py +3 -2
- flowllm/service/base_service.py +50 -41
- flowllm/service/cmd_service.py +15 -0
- flowllm/service/http_service.py +34 -42
- flowllm/service/mcp_service.py +13 -11
- flowllm/storage/cache/__init__.py +1 -0
- flowllm/storage/cache/cache_data_handler.py +104 -0
- flowllm/{utils/dataframe_cache.py → storage/cache/data_cache.py} +136 -92
- flowllm/storage/vector_store/__init__.py +3 -3
- flowllm/storage/vector_store/es_vector_store.py +1 -2
- flowllm/storage/vector_store/local_vector_store.py +0 -1
- flowllm/utils/common_utils.py +9 -21
- flowllm/utils/fetch_url.py +16 -12
- flowllm/utils/llm_utils.py +28 -0
- flowllm/utils/ridge_v2.py +54 -0
- {flowllm-0.1.1.dist-info → flowllm-0.1.2.dist-info}/METADATA +43 -390
- flowllm-0.1.2.dist-info/RECORD +99 -0
- flowllm-0.1.2.dist-info/entry_points.txt +2 -0
- flowllm/flow_engine/__init__.py +0 -1
- flowllm/flow_engine/base_flow_engine.py +0 -34
- flowllm-0.1.1.dist-info/RECORD +0 -62
- flowllm-0.1.1.dist-info/entry_points.txt +0 -4
- {flowllm-0.1.1.dist-info → flowllm-0.1.2.dist-info}/WHEEL +0 -0
- {flowllm-0.1.1.dist-info → flowllm-0.1.2.dist-info}/licenses/LICENSE +0 -0
- {flowllm-0.1.1.dist-info → flowllm-0.1.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,455 @@
|
|
1
|
+
import asyncio
|
2
|
+
import os
|
3
|
+
from typing import List, Dict
|
4
|
+
|
5
|
+
from litellm import completion, acompletion
|
6
|
+
from loguru import logger
|
7
|
+
from pydantic import Field, PrivateAttr, model_validator
|
8
|
+
|
9
|
+
from flowllm.context.service_context import C
|
10
|
+
from flowllm.enumeration.chunk_enum import ChunkEnum
|
11
|
+
from flowllm.enumeration.role import Role
|
12
|
+
from flowllm.llm.base_llm import BaseLLM
|
13
|
+
from flowllm.schema.message import Message
|
14
|
+
from flowllm.schema.tool_call import ToolCall
|
15
|
+
|
16
|
+
|
17
|
+
@C.register_llm("litellm")
|
18
|
+
class LiteLLMBaseLLM(BaseLLM):
|
19
|
+
"""
|
20
|
+
LiteLLM-compatible LLM implementation supporting multiple LLM providers through unified interface.
|
21
|
+
|
22
|
+
This class implements the BaseLLM interface using LiteLLM, which provides:
|
23
|
+
- Support for 100+ LLM providers (OpenAI, Anthropic, Cohere, Azure, etc.)
|
24
|
+
- Streaming responses with different chunk types (content, tools, usage)
|
25
|
+
- Tool calling with parallel execution support
|
26
|
+
- Unified API across different providers
|
27
|
+
- Robust error handling and retries
|
28
|
+
|
29
|
+
LiteLLM automatically handles provider-specific authentication and request formatting.
|
30
|
+
"""
|
31
|
+
|
32
|
+
# API configuration - LiteLLM handles provider-specific settings
|
33
|
+
api_key: str = Field(default_factory=lambda: os.getenv("FLOW_LLM_API_KEY"),
|
34
|
+
description="API key for authentication")
|
35
|
+
base_url: str = Field(default_factory=lambda: os.getenv("FLOW_LLM_BASE_URL"),
|
36
|
+
description="Base URL for custom endpoints")
|
37
|
+
|
38
|
+
# LiteLLM specific configuration
|
39
|
+
custom_llm_provider: str = Field(default="openai", description="Custom LLM provider name for LiteLLM routing")
|
40
|
+
|
41
|
+
# Additional LiteLLM parameters
|
42
|
+
timeout: float = Field(default=600, description="Request timeout in seconds")
|
43
|
+
max_tokens: int = Field(default=None, description="Maximum tokens to generate")
|
44
|
+
|
45
|
+
# Private attributes for LiteLLM configuration
|
46
|
+
_litellm_params: dict = PrivateAttr(default_factory=dict)
|
47
|
+
|
48
|
+
@model_validator(mode="after")
|
49
|
+
def init_litellm_config(self):
|
50
|
+
"""
|
51
|
+
Initialize LiteLLM configuration after model validation.
|
52
|
+
|
53
|
+
This validator sets up LiteLLM-specific parameters and environment variables
|
54
|
+
required for different providers. It configures authentication and routing
|
55
|
+
based on the model name and provider settings.
|
56
|
+
|
57
|
+
Returns:
|
58
|
+
Self for method chaining
|
59
|
+
"""
|
60
|
+
|
61
|
+
# Configure LiteLLM parameters
|
62
|
+
self._litellm_params = {
|
63
|
+
"api_key": self.api_key,
|
64
|
+
"base_url": self.base_url, #.replace("/v1", "")
|
65
|
+
"model": self.model_name,
|
66
|
+
"temperature": self.temperature,
|
67
|
+
"seed": self.seed,
|
68
|
+
"timeout": self.timeout,
|
69
|
+
}
|
70
|
+
|
71
|
+
# Add optional parameters
|
72
|
+
if self.top_p is not None:
|
73
|
+
self._litellm_params["top_p"] = self.top_p
|
74
|
+
if self.max_tokens is not None:
|
75
|
+
self._litellm_params["max_tokens"] = self.max_tokens
|
76
|
+
if self.presence_penalty is not None:
|
77
|
+
self._litellm_params["presence_penalty"] = self.presence_penalty
|
78
|
+
if self.custom_llm_provider:
|
79
|
+
self._litellm_params["custom_llm_provider"] = self.custom_llm_provider
|
80
|
+
|
81
|
+
return self
|
82
|
+
|
83
|
+
def stream_chat(self, messages: List[Message], tools: List[ToolCall] = None, **kwargs):
|
84
|
+
"""
|
85
|
+
Stream chat completions from LiteLLM with support for multiple providers.
|
86
|
+
|
87
|
+
This method handles streaming responses and categorizes chunks into different types:
|
88
|
+
- ANSWER: Regular response content from the model
|
89
|
+
- TOOL: Tool calls that need to be executed
|
90
|
+
- USAGE: Token usage statistics (when available)
|
91
|
+
- ERROR: Error information from failed requests
|
92
|
+
|
93
|
+
Args:
|
94
|
+
messages: List of conversation messages
|
95
|
+
tools: Optional list of tools available to the model
|
96
|
+
**kwargs: Additional parameters passed to LiteLLM
|
97
|
+
|
98
|
+
Yields:
|
99
|
+
Tuple of (chunk_content, ChunkEnum) for each streaming piece
|
100
|
+
"""
|
101
|
+
for i in range(self.max_retries):
|
102
|
+
try:
|
103
|
+
# Prepare parameters for LiteLLM
|
104
|
+
params = self._litellm_params.copy()
|
105
|
+
params.update(kwargs)
|
106
|
+
params.update({
|
107
|
+
"messages": [x.simple_dump() for x in messages],
|
108
|
+
"stream": True,
|
109
|
+
})
|
110
|
+
|
111
|
+
# Add tools if provided
|
112
|
+
if tools:
|
113
|
+
params["tools"] = [x.simple_input_dump() for x in tools]
|
114
|
+
params["tool_choice"] = self.tool_choice if self.tool_choice else "auto"
|
115
|
+
|
116
|
+
# Create streaming completion using LiteLLM
|
117
|
+
completion_response = completion(**params)
|
118
|
+
|
119
|
+
# Initialize tool call tracking
|
120
|
+
ret_tools: List[ToolCall] = [] # Accumulate tool calls across chunks
|
121
|
+
|
122
|
+
# Process each chunk in the streaming response
|
123
|
+
for chunk in completion_response:
|
124
|
+
try:
|
125
|
+
# Handle chunks without choices (usually usage/metadata)
|
126
|
+
if not hasattr(chunk, 'choices') or not chunk.choices:
|
127
|
+
# Check for usage information
|
128
|
+
if hasattr(chunk, 'usage') and chunk.usage:
|
129
|
+
yield chunk.usage, ChunkEnum.USAGE
|
130
|
+
continue
|
131
|
+
|
132
|
+
delta = chunk.choices[0].delta
|
133
|
+
|
134
|
+
# Handle regular response content
|
135
|
+
if hasattr(delta, 'content') and delta.content is not None:
|
136
|
+
yield delta.content, ChunkEnum.ANSWER
|
137
|
+
|
138
|
+
# Handle tool calls (function calling)
|
139
|
+
if hasattr(delta, 'tool_calls') and delta.tool_calls is not None:
|
140
|
+
for tool_call in delta.tool_calls:
|
141
|
+
index = getattr(tool_call, 'index', 0)
|
142
|
+
|
143
|
+
# Ensure we have enough tool call slots
|
144
|
+
while len(ret_tools) <= index:
|
145
|
+
ret_tools.append(ToolCall(index=index))
|
146
|
+
|
147
|
+
# Accumulate tool call information across chunks
|
148
|
+
if hasattr(tool_call, 'id') and tool_call.id:
|
149
|
+
ret_tools[index].id += tool_call.id
|
150
|
+
|
151
|
+
if (hasattr(tool_call, 'function') and tool_call.function and
|
152
|
+
hasattr(tool_call.function, 'name') and tool_call.function.name):
|
153
|
+
ret_tools[index].name += tool_call.function.name
|
154
|
+
|
155
|
+
if (hasattr(tool_call, 'function') and tool_call.function and
|
156
|
+
hasattr(tool_call.function, 'arguments') and tool_call.function.arguments):
|
157
|
+
ret_tools[index].arguments += tool_call.function.arguments
|
158
|
+
|
159
|
+
except Exception as chunk_error:
|
160
|
+
logger.warning(f"Error processing chunk: {chunk_error}")
|
161
|
+
continue
|
162
|
+
|
163
|
+
# Yield completed tool calls after streaming finishes
|
164
|
+
if ret_tools:
|
165
|
+
tool_dict: Dict[str, ToolCall] = {x.name: x for x in tools} if tools else {}
|
166
|
+
for tool in ret_tools:
|
167
|
+
# Only yield tool calls that correspond to available tools
|
168
|
+
if tools and tool.name not in tool_dict:
|
169
|
+
continue
|
170
|
+
|
171
|
+
yield tool, ChunkEnum.TOOL
|
172
|
+
|
173
|
+
return
|
174
|
+
|
175
|
+
except Exception as e:
|
176
|
+
logger.exception(f"stream chat with LiteLLM model={self.model_name} encounter error: {e}")
|
177
|
+
|
178
|
+
# Handle retry logic
|
179
|
+
if i == self.max_retries - 1 and self.raise_exception:
|
180
|
+
raise e
|
181
|
+
else:
|
182
|
+
error_msg = str(e.args) if hasattr(e, 'args') else str(e)
|
183
|
+
yield error_msg, ChunkEnum.ERROR
|
184
|
+
|
185
|
+
async def astream_chat(self, messages: List[Message], tools: List[ToolCall] = None, **kwargs):
|
186
|
+
"""
|
187
|
+
Async stream chat completions from LiteLLM with support for multiple providers.
|
188
|
+
|
189
|
+
This method handles async streaming responses and categorizes chunks into different types:
|
190
|
+
- ANSWER: Regular response content from the model
|
191
|
+
- TOOL: Tool calls that need to be executed
|
192
|
+
- USAGE: Token usage statistics (when available)
|
193
|
+
- ERROR: Error information from failed requests
|
194
|
+
|
195
|
+
Args:
|
196
|
+
messages: List of conversation messages
|
197
|
+
tools: Optional list of tools available to the model
|
198
|
+
**kwargs: Additional parameters passed to LiteLLM
|
199
|
+
|
200
|
+
Yields:
|
201
|
+
Tuple of (chunk_content, ChunkEnum) for each streaming piece
|
202
|
+
"""
|
203
|
+
for i in range(self.max_retries):
|
204
|
+
try:
|
205
|
+
# Prepare parameters for LiteLLM
|
206
|
+
params = self._litellm_params.copy()
|
207
|
+
params.update(kwargs)
|
208
|
+
params.update({
|
209
|
+
"messages": [x.simple_dump() for x in messages],
|
210
|
+
"stream": True,
|
211
|
+
})
|
212
|
+
|
213
|
+
# Add tools if provided
|
214
|
+
if tools:
|
215
|
+
params["tools"] = [x.simple_input_dump() for x in tools]
|
216
|
+
params["tool_choice"] = self.tool_choice if self.tool_choice else "auto"
|
217
|
+
|
218
|
+
# Create async streaming completion using LiteLLM
|
219
|
+
completion_response = await acompletion(**params)
|
220
|
+
|
221
|
+
# Initialize tool call tracking
|
222
|
+
ret_tools: List[ToolCall] = [] # Accumulate tool calls across chunks
|
223
|
+
|
224
|
+
# Process each chunk in the async streaming response
|
225
|
+
async for chunk in completion_response:
|
226
|
+
try:
|
227
|
+
# Handle chunks without choices (usually usage/metadata)
|
228
|
+
if not hasattr(chunk, 'choices') or not chunk.choices:
|
229
|
+
# Check for usage information
|
230
|
+
if hasattr(chunk, 'usage') and chunk.usage:
|
231
|
+
yield chunk.usage, ChunkEnum.USAGE
|
232
|
+
continue
|
233
|
+
|
234
|
+
delta = chunk.choices[0].delta
|
235
|
+
|
236
|
+
# Handle regular response content
|
237
|
+
if hasattr(delta, 'content') and delta.content is not None:
|
238
|
+
yield delta.content, ChunkEnum.ANSWER
|
239
|
+
|
240
|
+
# Handle tool calls (function calling)
|
241
|
+
if hasattr(delta, 'tool_calls') and delta.tool_calls is not None:
|
242
|
+
for tool_call in delta.tool_calls:
|
243
|
+
index = getattr(tool_call, 'index', 0)
|
244
|
+
|
245
|
+
# Ensure we have enough tool call slots
|
246
|
+
while len(ret_tools) <= index:
|
247
|
+
ret_tools.append(ToolCall(index=index))
|
248
|
+
|
249
|
+
# Accumulate tool call information across chunks
|
250
|
+
if hasattr(tool_call, 'id') and tool_call.id:
|
251
|
+
ret_tools[index].id += tool_call.id
|
252
|
+
|
253
|
+
if (hasattr(tool_call, 'function') and tool_call.function and
|
254
|
+
hasattr(tool_call.function, 'name') and tool_call.function.name):
|
255
|
+
ret_tools[index].name += tool_call.function.name
|
256
|
+
|
257
|
+
if (hasattr(tool_call, 'function') and tool_call.function and
|
258
|
+
hasattr(tool_call.function, 'arguments') and tool_call.function.arguments):
|
259
|
+
ret_tools[index].arguments += tool_call.function.arguments
|
260
|
+
|
261
|
+
except Exception as chunk_error:
|
262
|
+
logger.warning(f"Error processing async chunk: {chunk_error}")
|
263
|
+
continue
|
264
|
+
|
265
|
+
# Yield completed tool calls after streaming finishes
|
266
|
+
if ret_tools:
|
267
|
+
tool_dict: Dict[str, ToolCall] = {x.name: x for x in tools} if tools else {}
|
268
|
+
for tool in ret_tools:
|
269
|
+
# Only yield tool calls that correspond to available tools
|
270
|
+
if tools and tool.name not in tool_dict:
|
271
|
+
continue
|
272
|
+
|
273
|
+
yield tool, ChunkEnum.TOOL
|
274
|
+
|
275
|
+
return
|
276
|
+
|
277
|
+
except Exception as e:
|
278
|
+
logger.exception(f"async stream chat with LiteLLM model={self.model_name} encounter error: {e}")
|
279
|
+
|
280
|
+
# Handle retry logic with async sleep
|
281
|
+
await asyncio.sleep(1 + i)
|
282
|
+
|
283
|
+
if i == self.max_retries - 1 and self.raise_exception:
|
284
|
+
raise e
|
285
|
+
else:
|
286
|
+
error_msg = str(e.args) if hasattr(e, 'args') else str(e)
|
287
|
+
yield error_msg, ChunkEnum.ERROR
|
288
|
+
|
289
|
+
def _chat(self, messages: List[Message], tools: List[ToolCall] = None, enable_stream_print: bool = False,
|
290
|
+
**kwargs) -> Message:
|
291
|
+
"""
|
292
|
+
Perform a complete chat completion by aggregating streaming chunks from LiteLLM.
|
293
|
+
|
294
|
+
This method consumes the entire streaming response and combines all
|
295
|
+
chunks into a single Message object. It separates regular answer content
|
296
|
+
and tool calls, providing a complete response.
|
297
|
+
|
298
|
+
Args:
|
299
|
+
messages: List of conversation messages
|
300
|
+
tools: Optional list of tools available to the model
|
301
|
+
enable_stream_print: Whether to print streaming response to console
|
302
|
+
**kwargs: Additional parameters passed to LiteLLM
|
303
|
+
|
304
|
+
Returns:
|
305
|
+
Complete Message with all content aggregated
|
306
|
+
"""
|
307
|
+
answer_content = "" # Final response content
|
308
|
+
tool_calls = [] # List of tool calls to execute
|
309
|
+
|
310
|
+
# Consume streaming response and aggregate chunks by type
|
311
|
+
for chunk, chunk_enum in self.stream_chat(messages, tools, **kwargs):
|
312
|
+
if chunk_enum is ChunkEnum.USAGE:
|
313
|
+
# Display token usage statistics
|
314
|
+
if enable_stream_print:
|
315
|
+
if hasattr(chunk, 'model_dump_json'):
|
316
|
+
print(f"\n<usage>{chunk.model_dump_json(indent=2)}</usage>")
|
317
|
+
else:
|
318
|
+
print(f"\n<usage>{chunk}</usage>")
|
319
|
+
|
320
|
+
elif chunk_enum is ChunkEnum.ANSWER:
|
321
|
+
if enable_stream_print:
|
322
|
+
print(chunk, end="")
|
323
|
+
answer_content += chunk
|
324
|
+
|
325
|
+
elif chunk_enum is ChunkEnum.TOOL:
|
326
|
+
if enable_stream_print:
|
327
|
+
if hasattr(chunk, 'model_dump_json'):
|
328
|
+
print(f"\n<tool>{chunk.model_dump_json()}</tool>", end="")
|
329
|
+
else:
|
330
|
+
print(f"\n<tool>{chunk}</tool>", end="")
|
331
|
+
tool_calls.append(chunk)
|
332
|
+
|
333
|
+
elif chunk_enum is ChunkEnum.ERROR:
|
334
|
+
if enable_stream_print:
|
335
|
+
print(f"\n<error>{chunk}</error>", end="")
|
336
|
+
|
337
|
+
# Construct complete response message
|
338
|
+
return Message(
|
339
|
+
role=Role.ASSISTANT,
|
340
|
+
content=answer_content,
|
341
|
+
tool_calls=tool_calls
|
342
|
+
)
|
343
|
+
|
344
|
+
async def _achat(self, messages: List[Message], tools: List[ToolCall] = None, enable_stream_print: bool = False,
|
345
|
+
**kwargs) -> Message:
|
346
|
+
"""
|
347
|
+
Perform an async complete chat completion by aggregating streaming chunks from LiteLLM.
|
348
|
+
|
349
|
+
This method consumes the entire async streaming response and combines all
|
350
|
+
chunks into a single Message object. It separates regular answer content
|
351
|
+
and tool calls, providing a complete response.
|
352
|
+
|
353
|
+
Args:
|
354
|
+
messages: List of conversation messages
|
355
|
+
tools: Optional list of tools available to the model
|
356
|
+
enable_stream_print: Whether to print streaming response to console
|
357
|
+
**kwargs: Additional parameters passed to LiteLLM
|
358
|
+
|
359
|
+
Returns:
|
360
|
+
Complete Message with all content aggregated
|
361
|
+
"""
|
362
|
+
answer_content = "" # Final response content
|
363
|
+
tool_calls = [] # List of tool calls to execute
|
364
|
+
|
365
|
+
# Consume async streaming response and aggregate chunks by type
|
366
|
+
async for chunk, chunk_enum in self.astream_chat(messages, tools, **kwargs):
|
367
|
+
if chunk_enum is ChunkEnum.USAGE:
|
368
|
+
# Display token usage statistics
|
369
|
+
if enable_stream_print:
|
370
|
+
if hasattr(chunk, 'model_dump_json'):
|
371
|
+
print(f"\n<usage>{chunk.model_dump_json(indent=2)}</usage>")
|
372
|
+
else:
|
373
|
+
print(f"\n<usage>{chunk}</usage>")
|
374
|
+
|
375
|
+
elif chunk_enum is ChunkEnum.ANSWER:
|
376
|
+
if enable_stream_print:
|
377
|
+
print(chunk, end="")
|
378
|
+
answer_content += chunk
|
379
|
+
|
380
|
+
elif chunk_enum is ChunkEnum.TOOL:
|
381
|
+
if enable_stream_print:
|
382
|
+
if hasattr(chunk, 'model_dump_json'):
|
383
|
+
print(f"\n<tool>{chunk.model_dump_json()}</tool>", end="")
|
384
|
+
else:
|
385
|
+
print(f"\n<tool>{chunk}</tool>", end="")
|
386
|
+
tool_calls.append(chunk)
|
387
|
+
|
388
|
+
elif chunk_enum is ChunkEnum.ERROR:
|
389
|
+
if enable_stream_print:
|
390
|
+
print(f"\n<error>{chunk}</error>", end="")
|
391
|
+
|
392
|
+
# Construct complete response message
|
393
|
+
return Message(
|
394
|
+
role=Role.ASSISTANT,
|
395
|
+
content=answer_content,
|
396
|
+
tool_calls=tool_calls
|
397
|
+
)
|
398
|
+
|
399
|
+
|
400
|
+
async def async_main():
|
401
|
+
"""
|
402
|
+
Async test function for LiteLLMBaseLLM.
|
403
|
+
|
404
|
+
This function demonstrates how to use the LiteLLMBaseLLM class
|
405
|
+
with async operations. It requires proper environment variables
|
406
|
+
to be set for the chosen LLM provider.
|
407
|
+
"""
|
408
|
+
from flowllm.utils.common_utils import load_env
|
409
|
+
|
410
|
+
load_env()
|
411
|
+
|
412
|
+
# Example with OpenAI model through LiteLLM
|
413
|
+
model_name = "qwen-max-2025-01-25" # LiteLLM will route to OpenAI
|
414
|
+
llm = LiteLLMBaseLLM(model_name=model_name)
|
415
|
+
|
416
|
+
# Test async chat
|
417
|
+
message: Message = await llm.achat(
|
418
|
+
[Message(role=Role.USER, content="Hello! How are you?")],
|
419
|
+
[],
|
420
|
+
enable_stream_print=True
|
421
|
+
)
|
422
|
+
print("\nAsync result:", message)
|
423
|
+
|
424
|
+
|
425
|
+
def main():
|
426
|
+
"""
|
427
|
+
Sync test function for LiteLLMBaseLLM.
|
428
|
+
|
429
|
+
This function demonstrates how to use the LiteLLMBaseLLM class
|
430
|
+
with synchronous operations. It requires proper environment variables
|
431
|
+
to be set for the chosen LLM provider.
|
432
|
+
"""
|
433
|
+
from flowllm.utils.common_utils import load_env
|
434
|
+
|
435
|
+
load_env()
|
436
|
+
|
437
|
+
# Example with OpenAI model through LiteLLM
|
438
|
+
model_name = "qwen-max-2025-01-25" # LiteLLM will route to OpenAI
|
439
|
+
llm = LiteLLMBaseLLM(model_name=model_name)
|
440
|
+
|
441
|
+
# Test sync chat
|
442
|
+
message: Message = llm.chat(
|
443
|
+
[Message(role=Role.USER, content="Hello! How are you?")],
|
444
|
+
[],
|
445
|
+
enable_stream_print=True
|
446
|
+
)
|
447
|
+
print("\nSync result:", message)
|
448
|
+
|
449
|
+
|
450
|
+
if __name__ == "__main__":
|
451
|
+
main()
|
452
|
+
|
453
|
+
# import asyncio
|
454
|
+
#
|
455
|
+
# asyncio.run(async_main())
|