tinyflow-llm 0.1.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.
- tinyflow/__init__.py +53 -0
- tinyflow/config/helpers.py +62 -0
- tinyflow/config/settings.py +80 -0
- tinyflow/core/__init__.py +31 -0
- tinyflow/core/agent.py +457 -0
- tinyflow/core/exceptions.py +63 -0
- tinyflow/core/logger.py +13 -0
- tinyflow/core/message.py +6 -0
- tinyflow/core/protocol.py +81 -0
- tinyflow/core/tools.py +200 -0
- tinyflow/core/types.py +252 -0
- tinyflow/embeddings/__init__.py +4 -0
- tinyflow/embeddings/base.py +32 -0
- tinyflow/embeddings/factory.py +140 -0
- tinyflow/embeddings/local_embedding.py +65 -0
- tinyflow/embeddings/openai_embedding.py +70 -0
- tinyflow/memory/__init__.py +5 -0
- tinyflow/memory/base.py +16 -0
- tinyflow/memory/simple.py +34 -0
- tinyflow/memory/vector.py +26 -0
- tinyflow/providers/anthropic_llm.py +132 -0
- tinyflow/providers/base/factory.py +81 -0
- tinyflow/providers/base/llm.py +37 -0
- tinyflow/providers/gemini_llm.py +130 -0
- tinyflow/providers/openai_llm.py +198 -0
- tinyflow/tools/builtin/__init__.py +36 -0
- tinyflow/tools/builtin/code_execution.py +143 -0
- tinyflow/tools/builtin/search.py +145 -0
- tinyflow/tools/builtin/web_reader.py +88 -0
- tinyflow/vector/__init__.py +4 -0
- tinyflow/vector/base.py +65 -0
- tinyflow/vector/chroma_db.py +134 -0
- tinyflow/vector/factory.py +84 -0
- tinyflow/vector/qdrant_db.py +198 -0
- tinyflow/workflow/__init__.py +21 -0
- tinyflow/workflow/executor.py +272 -0
- tinyflow/workflow/hooks.py +191 -0
- tinyflow/workflow/state.py +148 -0
- tinyflow/workflow/step.py +74 -0
- tinyflow_llm-0.1.0.dist-info/METADATA +243 -0
- tinyflow_llm-0.1.0.dist-info/RECORD +43 -0
- tinyflow_llm-0.1.0.dist-info/WHEEL +4 -0
- tinyflow_llm-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""TinyFlow Exceptions
|
|
2
|
+
|
|
3
|
+
This module defines the public exception hierarchy for the TinyFlow framework.
|
|
4
|
+
Users can catch these exceptions to handle errors in a structured way.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, Optional
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TinyFlowError(Exception):
|
|
11
|
+
"""Base class for all TinyFlow exceptions."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, message: str):
|
|
14
|
+
super().__init__(message)
|
|
15
|
+
self.message = message
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ConfigurationError(TinyFlowError):
|
|
19
|
+
"""Raised when configuration is missing or invalid."""
|
|
20
|
+
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ProviderError(TinyFlowError):
|
|
25
|
+
"""Raised when an LLM provider fails (API errors, rate limits, etc.)."""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
message: str,
|
|
30
|
+
provider: str,
|
|
31
|
+
status_code: Optional[int] = None,
|
|
32
|
+
raw_response: Optional[Any] = None,
|
|
33
|
+
):
|
|
34
|
+
super().__init__(message)
|
|
35
|
+
self.provider = provider
|
|
36
|
+
self.status_code = status_code
|
|
37
|
+
self.raw_response = raw_response
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ToolError(TinyFlowError):
|
|
41
|
+
"""Raised when a tool execution fails."""
|
|
42
|
+
|
|
43
|
+
def __init__(self, message: str, tool_name: str):
|
|
44
|
+
super().__init__(message)
|
|
45
|
+
self.tool_name = tool_name
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class WorkflowError(TinyFlowError):
|
|
49
|
+
"""Raised when workflow execution fails (dependencies, routing, etc.)."""
|
|
50
|
+
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class VectorError(TinyFlowError):
|
|
55
|
+
"""Raised when vector database operations fail."""
|
|
56
|
+
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class MemoryError(TinyFlowError):
|
|
61
|
+
"""Raised when memory operations fail."""
|
|
62
|
+
|
|
63
|
+
pass
|
tinyflow/core/logger.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def setup_logger():
|
|
5
|
+
"""Setup the main logger for the library with a NullHandler to prevent 'No handler found' warnings."""
|
|
6
|
+
logger = logging.getLogger("tinyflow")
|
|
7
|
+
# Only add NullHandler if no handlers are present to avoid duplication if called multiple times
|
|
8
|
+
if not logger.handlers:
|
|
9
|
+
logger.addHandler(logging.NullHandler())
|
|
10
|
+
return logger
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
logger = setup_logger()
|
tinyflow/core/message.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import AsyncGenerator, Set
|
|
3
|
+
|
|
4
|
+
from tinyflow.core.types import (
|
|
5
|
+
DataUIPart,
|
|
6
|
+
ReasoningUIPart,
|
|
7
|
+
TextUIPart,
|
|
8
|
+
ToolUIPart,
|
|
9
|
+
UIMessage,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class JSONStreamAdapter:
|
|
14
|
+
"""
|
|
15
|
+
Adapts a UIMessage stream to a structured JSON delta stream.
|
|
16
|
+
Each yield is a valid JSON string representing a specific part update.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self):
|
|
20
|
+
self._last_lengths = {} # Track lengths per part type/id
|
|
21
|
+
self._emitted_states: Set[str] = set()
|
|
22
|
+
|
|
23
|
+
async def transform(self, message_stream: AsyncGenerator[UIMessage, None]) -> AsyncGenerator[str, None]:
|
|
24
|
+
async for message in message_stream:
|
|
25
|
+
if message.role != "assistant":
|
|
26
|
+
continue
|
|
27
|
+
|
|
28
|
+
for part in message.parts:
|
|
29
|
+
# Use a combination of type and optional ID for tracking
|
|
30
|
+
part_id = getattr(part, "tool_call_id", None) or getattr(part, "id", "default")
|
|
31
|
+
track_key = f"{part.type}:{part_id}"
|
|
32
|
+
|
|
33
|
+
# 1. Handle Text & Reasoning (Incremental Deltas)
|
|
34
|
+
if isinstance(part, (TextUIPart, ReasoningUIPart)):
|
|
35
|
+
current_text = part.text
|
|
36
|
+
last_len = self._last_lengths.get(track_key, 0)
|
|
37
|
+
|
|
38
|
+
if len(current_text) > last_len:
|
|
39
|
+
delta = current_text[last_len:]
|
|
40
|
+
yield json.dumps({
|
|
41
|
+
"type": f"{part.type}-delta",
|
|
42
|
+
"delta": delta,
|
|
43
|
+
"index": message.parts.index(part) # Help frontend locate the part
|
|
44
|
+
}) + "\n"
|
|
45
|
+
self._last_lengths[track_key] = len(current_text)
|
|
46
|
+
|
|
47
|
+
# 2. Handle Tool Calls (State-based Events)
|
|
48
|
+
elif isinstance(part, ToolUIPart):
|
|
49
|
+
state_key = f"tool:{part.tool_call_id}:{part.state}"
|
|
50
|
+
if state_key not in self._emitted_states:
|
|
51
|
+
payload = {
|
|
52
|
+
"type": "tool-call" if "input" in part.state.value else "tool-result",
|
|
53
|
+
"status": part.state.value,
|
|
54
|
+
"toolCallId": part.tool_call_id,
|
|
55
|
+
"toolName": part.tool_name,
|
|
56
|
+
}
|
|
57
|
+
if part.input is not None:
|
|
58
|
+
payload["input"] = part.input
|
|
59
|
+
if part.output is not None:
|
|
60
|
+
payload["output"] = part.output
|
|
61
|
+
if part.error_text:
|
|
62
|
+
payload["error"] = part.error_text
|
|
63
|
+
|
|
64
|
+
yield json.dumps(payload) + "\n"
|
|
65
|
+
self._emitted_states.add(state_key)
|
|
66
|
+
|
|
67
|
+
# 3. Handle Custom Data
|
|
68
|
+
elif isinstance(part, DataUIPart):
|
|
69
|
+
if track_key not in self._emitted_states:
|
|
70
|
+
yield json.dumps({
|
|
71
|
+
"type": "data",
|
|
72
|
+
"id": part.id,
|
|
73
|
+
"data": part.data
|
|
74
|
+
}) + "\n"
|
|
75
|
+
self._emitted_states.add(track_key)
|
|
76
|
+
|
|
77
|
+
async def to_json_stream(message_stream: AsyncGenerator[UIMessage, None]) -> AsyncGenerator[str, None]:
|
|
78
|
+
"""Helper function for the most common DX: agent.stream() -> to_json_stream()"""
|
|
79
|
+
adapter = JSONStreamAdapter()
|
|
80
|
+
async for chunk in adapter.transform(message_stream):
|
|
81
|
+
yield chunk
|
tinyflow/core/tools.py
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Any, Callable, Dict, Optional, Type
|
|
4
|
+
|
|
5
|
+
from docstring_parser import parse
|
|
6
|
+
from pydantic import BaseModel, Field, create_model
|
|
7
|
+
|
|
8
|
+
from tinyflow.core.exceptions import ToolError
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger("tinyflow.core.tools")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Tool:
|
|
14
|
+
"""
|
|
15
|
+
Explicit declarative tool definition (Vercel AI SDK style).
|
|
16
|
+
|
|
17
|
+
This is the unified tool class of the framework, providing clear control:
|
|
18
|
+
- name: Tool name
|
|
19
|
+
- description: Detailed description (LLM will decide when to call based on this)
|
|
20
|
+
- parameters: Pydantic model defining parameter structure
|
|
21
|
+
- execute: Execution function, receiving the validated Pydantic model instance
|
|
22
|
+
|
|
23
|
+
Example:
|
|
24
|
+
```python
|
|
25
|
+
class WeatherInput(BaseModel):
|
|
26
|
+
city: str = Field(description="City name")
|
|
27
|
+
unit: str = Field(default="celsius", description="Temperature unit")
|
|
28
|
+
|
|
29
|
+
weather_tool = Tool(
|
|
30
|
+
name="get_weather",
|
|
31
|
+
description="Get weather information for a specific city.",
|
|
32
|
+
parameters=WeatherInput,
|
|
33
|
+
execute=lambda args: f"{args.city} is sunny"
|
|
34
|
+
)
|
|
35
|
+
```
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
name: str,
|
|
41
|
+
description: str,
|
|
42
|
+
parameters: Type[BaseModel],
|
|
43
|
+
execute: Callable[[Any], Any],
|
|
44
|
+
):
|
|
45
|
+
self.name = name
|
|
46
|
+
self.description = description
|
|
47
|
+
self.parameters = parameters
|
|
48
|
+
self.execute_func = execute
|
|
49
|
+
self.args_schema = parameters
|
|
50
|
+
self.func = execute
|
|
51
|
+
logger.debug(f"Tool initialized: {self.name}")
|
|
52
|
+
|
|
53
|
+
async def execute(self, **kwargs) -> Any:
|
|
54
|
+
"""Execute the tool with automatic parameter validation."""
|
|
55
|
+
try:
|
|
56
|
+
validated_args = self.parameters.model_validate(kwargs)
|
|
57
|
+
|
|
58
|
+
if inspect.iscoroutinefunction(self.execute_func):
|
|
59
|
+
result = await self.execute_func(validated_args)
|
|
60
|
+
else:
|
|
61
|
+
result = self.execute_func(validated_args)
|
|
62
|
+
|
|
63
|
+
# If result is an Agent or WorkflowExecutor, run it
|
|
64
|
+
# This enables "Agent as a Tool" pattern
|
|
65
|
+
if hasattr(result, "run"):
|
|
66
|
+
if inspect.iscoroutinefunction(result.run):
|
|
67
|
+
# For Agents/Workflows, we might want to return the result directly
|
|
68
|
+
# or handle the run execution here.
|
|
69
|
+
# Simpler approach: execute .run() and return output
|
|
70
|
+
# For WorkflowExecutor, run() returns dict of results
|
|
71
|
+
# For Agent, run() returns str
|
|
72
|
+
output = await result.run(**kwargs) # Pass args if compatible?
|
|
73
|
+
# Actually, usually the tool function prepares the agent/workflow
|
|
74
|
+
# and might run it with specific args derived from validated_args.
|
|
75
|
+
# If the tool function *returns* the runner instance, we run it.
|
|
76
|
+
# But usually the tool body does the running.
|
|
77
|
+
# Let's assume the tool body returns the FINAL result (str/dict).
|
|
78
|
+
# So no change needed here if tool body handles execution.
|
|
79
|
+
|
|
80
|
+
# BUT, if we want to support returning an AsyncGenerator (Streaming Workflow),
|
|
81
|
+
# we must NOT convert to str immediately.
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
return result
|
|
85
|
+
except Exception as e:
|
|
86
|
+
if hasattr(e, "errors"):
|
|
87
|
+
logger.warning(f"Validation Error for tool '{self.name}': {str(e)}")
|
|
88
|
+
# Raise ToolError for validation failure?
|
|
89
|
+
# Or keep string return for LLM loop?
|
|
90
|
+
# The execute method is typically called by Agent which wants a string.
|
|
91
|
+
# However, for library users executing tools directly, an exception is better.
|
|
92
|
+
# Let's wrap it in ToolError but format it friendly.
|
|
93
|
+
raise ToolError(f"Validation Error: {str(e)}", tool_name=self.name) from e
|
|
94
|
+
|
|
95
|
+
logger.error(f"Error executing tool '{self.name}': {str(e)}")
|
|
96
|
+
raise ToolError(f"Execution Error: {str(e)}", tool_name=self.name) from e
|
|
97
|
+
|
|
98
|
+
def to_openai_schema(self, strict: bool = False) -> Dict[str, Any]:
|
|
99
|
+
"""
|
|
100
|
+
Convert to OpenAI tool format.
|
|
101
|
+
|
|
102
|
+
Extract field descriptions from Pydantic model Field descriptions to ensure
|
|
103
|
+
LLM accurately understands the purpose of each parameter.
|
|
104
|
+
"""
|
|
105
|
+
schema = self.parameters.model_json_schema()
|
|
106
|
+
|
|
107
|
+
parameters = {
|
|
108
|
+
"type": "object",
|
|
109
|
+
"properties": schema.get("properties", {}),
|
|
110
|
+
"required": schema.get("required", []),
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if strict:
|
|
114
|
+
parameters["additionalProperties"] = False
|
|
115
|
+
parameters["required"] = list(schema.get("properties", {}).keys())
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
"type": "function",
|
|
119
|
+
"function": {
|
|
120
|
+
"name": self.name,
|
|
121
|
+
"description": self.description,
|
|
122
|
+
"parameters": parameters,
|
|
123
|
+
"strict": strict,
|
|
124
|
+
},
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def tool(
|
|
129
|
+
name: Optional[str] = None,
|
|
130
|
+
description: Optional[str] = None,
|
|
131
|
+
):
|
|
132
|
+
"""
|
|
133
|
+
Tool decorator supporting automatic tool definition derivation from functions.
|
|
134
|
+
|
|
135
|
+
Supports two modes:
|
|
136
|
+
1. Decorating normal functions: Automatically parse description and parameter descriptions from docstring.
|
|
137
|
+
2. Decorating class methods: Use with Tool class.
|
|
138
|
+
|
|
139
|
+
Example:
|
|
140
|
+
```python
|
|
141
|
+
@tool()
|
|
142
|
+
async def get_weather(city: str):
|
|
143
|
+
'''Get weather.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
city: City name
|
|
147
|
+
'''
|
|
148
|
+
return "Sunny"
|
|
149
|
+
|
|
150
|
+
@tool(name="custom_name", description="Custom description")
|
|
151
|
+
def my_tool(input: str):
|
|
152
|
+
return input
|
|
153
|
+
```
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
def decorator(func: Callable) -> Tool:
|
|
157
|
+
tool_name = name or func.__name__
|
|
158
|
+
|
|
159
|
+
tool_description = description
|
|
160
|
+
if tool_description is None:
|
|
161
|
+
doc = parse(func.__doc__ or "")
|
|
162
|
+
tool_description = doc.short_description or f"Tool: {tool_name}"
|
|
163
|
+
|
|
164
|
+
# Create Pydantic model from function signature
|
|
165
|
+
args_schema = _get_args_schema(func)
|
|
166
|
+
|
|
167
|
+
# Create execute function that unwraps Pydantic model to kwargs
|
|
168
|
+
def execute_wrapper(args):
|
|
169
|
+
kwargs = args.model_dump()
|
|
170
|
+
if inspect.iscoroutinefunction(func):
|
|
171
|
+
# Return the coroutine - it will be awaited by Tool.execute
|
|
172
|
+
return func(**kwargs)
|
|
173
|
+
else:
|
|
174
|
+
return func(**kwargs)
|
|
175
|
+
|
|
176
|
+
return Tool(
|
|
177
|
+
name=tool_name,
|
|
178
|
+
description=tool_description,
|
|
179
|
+
parameters=args_schema,
|
|
180
|
+
execute=execute_wrapper,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
return decorator
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _get_args_schema(func: Callable) -> Type[BaseModel]:
|
|
187
|
+
"""Helper function to create Pydantic model from function signature."""
|
|
188
|
+
sig = inspect.signature(func)
|
|
189
|
+
doc = parse(func.__doc__ or "")
|
|
190
|
+
|
|
191
|
+
param_descriptions = {p.arg_name: p.description for p in doc.params}
|
|
192
|
+
|
|
193
|
+
fields = {}
|
|
194
|
+
for param_name, param in sig.parameters.items():
|
|
195
|
+
annotation = param.annotation if param.annotation != inspect.Parameter.empty else str
|
|
196
|
+
default = param.default if param.default != inspect.Parameter.empty else ...
|
|
197
|
+
description = param_descriptions.get(param_name, "")
|
|
198
|
+
fields[param_name] = (annotation, Field(default=default, description=description))
|
|
199
|
+
|
|
200
|
+
return create_model(f"{func.__name__}Schema", **fields)
|
tinyflow/core/types.py
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import Any, Dict, Generic, List, Literal, Optional, TypeVar, Union
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
|
|
6
|
+
# =============================================================================
|
|
7
|
+
# Legacy Types (Backward Compatibility)
|
|
8
|
+
# =============================================================================
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Message(BaseModel):
|
|
12
|
+
"""Base message type for conversation history."""
|
|
13
|
+
|
|
14
|
+
role: Literal["system", "user", "assistant", "tool"]
|
|
15
|
+
content: Optional[str] = ""
|
|
16
|
+
name: Optional[str] = None
|
|
17
|
+
tool_calls: Optional[List[Dict[str, Any]]] = None
|
|
18
|
+
tool_call_id: Optional[str] = None
|
|
19
|
+
reasoning_content: Optional[str] = None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class StreamChunk(BaseModel):
|
|
23
|
+
"""Legacy stream chunk - deprecated in favor of StreamPart types."""
|
|
24
|
+
|
|
25
|
+
content: str
|
|
26
|
+
finish_reason: Optional[str] = None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class LLMResponse(BaseModel):
|
|
30
|
+
"""Response from LLM provider."""
|
|
31
|
+
|
|
32
|
+
content: Optional[str]
|
|
33
|
+
tool_calls: Optional[List[Dict[str, Any]]] = None
|
|
34
|
+
raw_response: Optional[object] = None
|
|
35
|
+
usage: Optional[dict] = None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# =============================================================================
|
|
39
|
+
# UI Message Part State Enums
|
|
40
|
+
# =============================================================================
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class TextPartState(str, Enum):
|
|
44
|
+
"""States for text content parts."""
|
|
45
|
+
|
|
46
|
+
STREAMING = "streaming"
|
|
47
|
+
DONE = "done"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class ToolPartState(str, Enum):
|
|
51
|
+
"""States for tool execution lifecycle.
|
|
52
|
+
|
|
53
|
+
States:
|
|
54
|
+
INPUT_STREAMING: Tool arguments being generated by LLM
|
|
55
|
+
INPUT_AVAILABLE: Arguments complete, ready for execution
|
|
56
|
+
OUTPUT_AVAILABLE: Tool executed, result available
|
|
57
|
+
OUTPUT_ERROR: Tool execution failed
|
|
58
|
+
APPROVAL_REQUESTED: Waiting for user approval
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
INPUT_STREAMING = "input-streaming"
|
|
62
|
+
INPUT_AVAILABLE = "input-available"
|
|
63
|
+
OUTPUT_AVAILABLE = "output-available"
|
|
64
|
+
OUTPUT_ERROR = "output-error"
|
|
65
|
+
APPROVAL_REQUESTED = "approval-requested"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# =============================================================================
|
|
69
|
+
# UI Message Part Types
|
|
70
|
+
# =============================================================================
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class TextUIPart(BaseModel):
|
|
74
|
+
"""Text content with streaming state."""
|
|
75
|
+
|
|
76
|
+
type: Literal["text"] = "text"
|
|
77
|
+
text: str
|
|
78
|
+
state: Optional[TextPartState] = None
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class ReasoningUIPart(BaseModel):
|
|
82
|
+
"""Agent thinking/reasoning trace."""
|
|
83
|
+
|
|
84
|
+
type: Literal["reasoning"] = "reasoning"
|
|
85
|
+
text: str
|
|
86
|
+
state: Optional[TextPartState] = None
|
|
87
|
+
provider_metadata: Optional[Dict[str, Any]] = None
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class ToolUIPart(BaseModel):
|
|
91
|
+
"""Tool invocation and result representation.
|
|
92
|
+
|
|
93
|
+
This part represents the complete lifecycle of a tool call:
|
|
94
|
+
1. INPUT_STREAMING: Tool arguments being generated by LLM
|
|
95
|
+
2. INPUT_AVAILABLE: Arguments complete, ready for execution
|
|
96
|
+
3. OUTPUT_AVAILABLE: Tool executed, result available
|
|
97
|
+
4. OUTPUT_ERROR: Tool execution failed
|
|
98
|
+
5. APPROVAL_REQUESTED: Waiting for user to approve execution
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
type: str = Field(..., description="Format: tool-{tool_name}")
|
|
102
|
+
tool_call_id: str
|
|
103
|
+
tool_name: str
|
|
104
|
+
state: ToolPartState
|
|
105
|
+
|
|
106
|
+
# Input data (when state is INPUT_* or OUTPUT_*)
|
|
107
|
+
input: Optional[Dict[str, Any]] = None
|
|
108
|
+
|
|
109
|
+
# Output data (when state is OUTPUT_AVAILABLE)
|
|
110
|
+
output: Optional[Any] = None
|
|
111
|
+
|
|
112
|
+
# Error text (when state is OUTPUT_ERROR)
|
|
113
|
+
error_text: Optional[str] = None
|
|
114
|
+
|
|
115
|
+
# Approval data (when state is APPROVAL_REQUESTED)
|
|
116
|
+
approval_id: Optional[str] = None
|
|
117
|
+
|
|
118
|
+
# Execution flag
|
|
119
|
+
provider_executed: Optional[bool] = None
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class FileUIPart(BaseModel):
|
|
123
|
+
"""File or image attachment."""
|
|
124
|
+
|
|
125
|
+
type: Literal["file"] = "file"
|
|
126
|
+
media_type: str = Field(..., description="IANA media type (e.g., image/png)")
|
|
127
|
+
filename: Optional[str] = None
|
|
128
|
+
url: str = Field(..., description="Can be URL or data URL (base64)")
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class DataUIPart(BaseModel):
|
|
132
|
+
"""Custom data part for structured data.
|
|
133
|
+
|
|
134
|
+
This allows passing arbitrary structured data to the frontend
|
|
135
|
+
for custom UI components.
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
type: str = Field(..., description="Format: data-{data_name}")
|
|
139
|
+
id: Optional[str] = None
|
|
140
|
+
data: Dict[str, Any]
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class StepStartUIPart(BaseModel):
|
|
144
|
+
"""Marker for step boundary in multi-step tool execution."""
|
|
145
|
+
|
|
146
|
+
type: Literal["step-start"] = "step-start"
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
# Union type for all UI message parts
|
|
150
|
+
UIMessagePart = Union[
|
|
151
|
+
TextUIPart,
|
|
152
|
+
ReasoningUIPart,
|
|
153
|
+
ToolUIPart,
|
|
154
|
+
FileUIPart,
|
|
155
|
+
DataUIPart,
|
|
156
|
+
StepStartUIPart,
|
|
157
|
+
]
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
# =============================================================================
|
|
161
|
+
# UI Message Type
|
|
162
|
+
# =============================================================================
|
|
163
|
+
|
|
164
|
+
METADATA = TypeVar("METADATA", bound=BaseModel)
|
|
165
|
+
DATA_PARTS = TypeVar("DATA_PARTS", bound=BaseModel)
|
|
166
|
+
TOOLS = TypeVar("TOOLS", bound=BaseModel)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class UIMessage(BaseModel, Generic[METADATA, DATA_PARTS, TOOLS]):
|
|
170
|
+
"""UI Message - Complete message representation for frontend rendering.
|
|
171
|
+
|
|
172
|
+
This is the single source of truth for application state, containing
|
|
173
|
+
full message history including metadata, data parts, and contextual
|
|
174
|
+
information for UI rendering.
|
|
175
|
+
|
|
176
|
+
Generic Parameters:
|
|
177
|
+
METADATA: Custom metadata type for additional message information
|
|
178
|
+
DATA_PARTS: Custom data part types for structured components
|
|
179
|
+
TOOLS: Tool definitions for type-safe tool interactions
|
|
180
|
+
"""
|
|
181
|
+
|
|
182
|
+
id: str
|
|
183
|
+
role: Literal["system", "user", "assistant"]
|
|
184
|
+
metadata: Optional[METADATA] = None
|
|
185
|
+
parts: List[UIMessagePart]
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
# =============================================================================
|
|
189
|
+
# Stream Part Types (for Provider-Level Streaming)
|
|
190
|
+
# =============================================================================
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class TextStreamDelta(BaseModel):
|
|
194
|
+
"""Text delta in streaming response."""
|
|
195
|
+
|
|
196
|
+
type: Literal["text"] = "text"
|
|
197
|
+
text: str
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class ReasoningStreamDelta(BaseModel):
|
|
201
|
+
"""Reasoning delta in streaming response."""
|
|
202
|
+
|
|
203
|
+
type: Literal["reasoning"] = "reasoning"
|
|
204
|
+
text: str
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class ToolCallStreamStart(BaseModel):
|
|
208
|
+
"""Tool call streaming starts."""
|
|
209
|
+
|
|
210
|
+
type: Literal["tool-call-streaming-start"] = "tool-call-streaming-start"
|
|
211
|
+
tool_call_id: str
|
|
212
|
+
tool_name: str
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class ToolCallStreamDelta(BaseModel):
|
|
216
|
+
"""Tool call argument delta (streaming)."""
|
|
217
|
+
|
|
218
|
+
type: Literal["tool-call-delta"] = "tool-call-delta"
|
|
219
|
+
tool_call_id: str
|
|
220
|
+
tool_name: str
|
|
221
|
+
args_text_delta: str
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class ToolCallComplete(BaseModel):
|
|
225
|
+
"""Complete tool call (when streaming is off or complete)."""
|
|
226
|
+
|
|
227
|
+
type: Literal["tool-call"] = "tool-call"
|
|
228
|
+
tool_call_id: str
|
|
229
|
+
tool_name: str
|
|
230
|
+
input: Dict[str, Any]
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class ToolResultStream(BaseModel):
|
|
234
|
+
"""Tool execution result."""
|
|
235
|
+
|
|
236
|
+
type: Literal["tool-result"] = "tool-result"
|
|
237
|
+
tool_call_id: str
|
|
238
|
+
tool_name: str
|
|
239
|
+
input: Dict[str, Any]
|
|
240
|
+
output: Any
|
|
241
|
+
is_error: bool = False
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
# Union type for all stream parts
|
|
245
|
+
StreamPart = Union[
|
|
246
|
+
TextStreamDelta,
|
|
247
|
+
ReasoningStreamDelta,
|
|
248
|
+
ToolCallStreamStart,
|
|
249
|
+
ToolCallStreamDelta,
|
|
250
|
+
ToolCallComplete,
|
|
251
|
+
ToolResultStream,
|
|
252
|
+
]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Embedding Abstraction Layer
|
|
2
|
+
|
|
3
|
+
Supports multiple embedding model providers
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from abc import ABC, abstractmethod
|
|
7
|
+
from typing import List
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BaseEmbedding(ABC):
|
|
11
|
+
"""Embedding abstract base class"""
|
|
12
|
+
|
|
13
|
+
@abstractmethod
|
|
14
|
+
async def embed(self, texts: List[str]) -> List[List[float]]:
|
|
15
|
+
"""Convert text list to vectors
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
texts: List of text strings
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
List of vectors (one for each text)
|
|
22
|
+
"""
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
@abstractmethod
|
|
26
|
+
def get_dimension(self) -> int:
|
|
27
|
+
"""Get vector dimension
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Vector dimension
|
|
31
|
+
"""
|
|
32
|
+
pass
|