flashlite 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.
- flashlite/__init__.py +169 -0
- flashlite/cache/__init__.py +14 -0
- flashlite/cache/base.py +194 -0
- flashlite/cache/disk.py +285 -0
- flashlite/cache/memory.py +157 -0
- flashlite/client.py +671 -0
- flashlite/config.py +154 -0
- flashlite/conversation/__init__.py +30 -0
- flashlite/conversation/context.py +319 -0
- flashlite/conversation/manager.py +385 -0
- flashlite/conversation/multi_agent.py +378 -0
- flashlite/core/__init__.py +13 -0
- flashlite/core/completion.py +145 -0
- flashlite/core/messages.py +130 -0
- flashlite/middleware/__init__.py +18 -0
- flashlite/middleware/base.py +90 -0
- flashlite/middleware/cache.py +121 -0
- flashlite/middleware/logging.py +159 -0
- flashlite/middleware/rate_limit.py +211 -0
- flashlite/middleware/retry.py +149 -0
- flashlite/observability/__init__.py +34 -0
- flashlite/observability/callbacks.py +155 -0
- flashlite/observability/inspect_compat.py +266 -0
- flashlite/observability/logging.py +293 -0
- flashlite/observability/metrics.py +221 -0
- flashlite/py.typed +0 -0
- flashlite/structured/__init__.py +31 -0
- flashlite/structured/outputs.py +189 -0
- flashlite/structured/schema.py +165 -0
- flashlite/templating/__init__.py +11 -0
- flashlite/templating/engine.py +217 -0
- flashlite/templating/filters.py +143 -0
- flashlite/templating/registry.py +165 -0
- flashlite/tools/__init__.py +74 -0
- flashlite/tools/definitions.py +382 -0
- flashlite/tools/execution.py +353 -0
- flashlite/types.py +233 -0
- flashlite-0.1.0.dist-info/METADATA +173 -0
- flashlite-0.1.0.dist-info/RECORD +41 -0
- flashlite-0.1.0.dist-info/WHEEL +4 -0
- flashlite-0.1.0.dist-info/licenses/LICENSE.md +21 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""Custom Jinja filters for prompt templating."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from jinja2 import Environment
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def json_encode(value: Any, indent: int | None = None) -> str:
|
|
10
|
+
"""
|
|
11
|
+
Encode a value as JSON string.
|
|
12
|
+
|
|
13
|
+
Usage in template: {{ data | json }}
|
|
14
|
+
"""
|
|
15
|
+
return json.dumps(value, indent=indent, ensure_ascii=False)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def json_encode_pretty(value: Any) -> str:
|
|
19
|
+
"""
|
|
20
|
+
Encode a value as pretty-printed JSON.
|
|
21
|
+
|
|
22
|
+
Usage in template: {{ data | json_pretty }}
|
|
23
|
+
"""
|
|
24
|
+
return json.dumps(value, indent=2, ensure_ascii=False)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def truncate_words(value: str, max_words: int, suffix: str = "...") -> str:
|
|
28
|
+
"""
|
|
29
|
+
Truncate string to a maximum number of words.
|
|
30
|
+
|
|
31
|
+
Usage in template: {{ text | truncate_words(100) }}
|
|
32
|
+
"""
|
|
33
|
+
words = value.split()
|
|
34
|
+
if len(words) <= max_words:
|
|
35
|
+
return value
|
|
36
|
+
return " ".join(words[:max_words]) + suffix
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def truncate_chars(value: str, max_chars: int, suffix: str = "...") -> str:
|
|
40
|
+
"""
|
|
41
|
+
Truncate string to a maximum number of characters.
|
|
42
|
+
|
|
43
|
+
Usage in template: {{ text | truncate_chars(500) }}
|
|
44
|
+
"""
|
|
45
|
+
if len(value) <= max_chars:
|
|
46
|
+
return value
|
|
47
|
+
return value[: max_chars - len(suffix)] + suffix
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def escape_xml(value: str) -> str:
|
|
51
|
+
"""
|
|
52
|
+
Escape XML special characters.
|
|
53
|
+
|
|
54
|
+
Usage in template: {{ text | escape_xml }}
|
|
55
|
+
"""
|
|
56
|
+
replacements = [
|
|
57
|
+
("&", "&"),
|
|
58
|
+
("<", "<"),
|
|
59
|
+
(">", ">"),
|
|
60
|
+
('"', """),
|
|
61
|
+
("'", "'"),
|
|
62
|
+
]
|
|
63
|
+
result = value
|
|
64
|
+
for old, new in replacements:
|
|
65
|
+
result = result.replace(old, new)
|
|
66
|
+
return result
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def strip_tags(value: str) -> str:
|
|
70
|
+
"""
|
|
71
|
+
Remove XML/HTML tags from string.
|
|
72
|
+
|
|
73
|
+
Usage in template: {{ html_content | strip_tags }}
|
|
74
|
+
"""
|
|
75
|
+
import re
|
|
76
|
+
|
|
77
|
+
return re.sub(r"<[^>]+>", "", value)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def bullet_list(items: list[str], bullet: str = "- ") -> str:
|
|
81
|
+
"""
|
|
82
|
+
Format a list as bullet points.
|
|
83
|
+
|
|
84
|
+
Usage in template: {{ items | bullet_list }}
|
|
85
|
+
"""
|
|
86
|
+
return "\n".join(f"{bullet}{item}" for item in items)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def numbered_list(items: list[str], start: int = 1) -> str:
|
|
90
|
+
"""
|
|
91
|
+
Format a list as numbered items.
|
|
92
|
+
|
|
93
|
+
Usage in template: {{ items | numbered_list }}
|
|
94
|
+
"""
|
|
95
|
+
return "\n".join(f"{i}. {item}" for i, item in enumerate(items, start=start))
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def wrap_xml(value: str, tag: str) -> str:
|
|
99
|
+
"""
|
|
100
|
+
Wrap content in XML tags.
|
|
101
|
+
|
|
102
|
+
Usage in template: {{ content | wrap_xml('context') }}
|
|
103
|
+
Produces: <context>content</context>
|
|
104
|
+
"""
|
|
105
|
+
return f"<{tag}>{value}</{tag}>"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def indent_text(value: str, spaces: int = 2, first_line: bool = True) -> str:
|
|
109
|
+
"""
|
|
110
|
+
Indent text by a number of spaces.
|
|
111
|
+
|
|
112
|
+
Usage in template: {{ text | indent_text(4) }}
|
|
113
|
+
"""
|
|
114
|
+
prefix = " " * spaces
|
|
115
|
+
lines = value.split("\n")
|
|
116
|
+
if first_line:
|
|
117
|
+
return "\n".join(prefix + line for line in lines)
|
|
118
|
+
else:
|
|
119
|
+
return lines[0] + "\n" + "\n".join(prefix + line for line in lines[1:])
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def default_if_empty(value: Any, default: Any) -> Any:
|
|
123
|
+
"""
|
|
124
|
+
Return default if value is empty/falsy.
|
|
125
|
+
|
|
126
|
+
Usage in template: {{ maybe_empty | default_if_empty('N/A') }}
|
|
127
|
+
"""
|
|
128
|
+
return value if value else default
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def register_default_filters(env: Environment) -> None:
|
|
132
|
+
"""Register all default filters with a Jinja environment."""
|
|
133
|
+
env.filters["json"] = json_encode
|
|
134
|
+
env.filters["json_pretty"] = json_encode_pretty
|
|
135
|
+
env.filters["truncate_words"] = truncate_words
|
|
136
|
+
env.filters["truncate_chars"] = truncate_chars
|
|
137
|
+
env.filters["escape_xml"] = escape_xml
|
|
138
|
+
env.filters["strip_tags"] = strip_tags
|
|
139
|
+
env.filters["bullet_list"] = bullet_list
|
|
140
|
+
env.filters["numbered_list"] = numbered_list
|
|
141
|
+
env.filters["wrap_xml"] = wrap_xml
|
|
142
|
+
env.filters["indent_text"] = indent_text
|
|
143
|
+
env.filters["default_if_empty"] = default_if_empty
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""Template registry for managing prompt templates."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from jinja2 import Template
|
|
8
|
+
|
|
9
|
+
from ..types import TemplateError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TemplateRegistry:
|
|
13
|
+
"""
|
|
14
|
+
Registry for managing prompt templates.
|
|
15
|
+
|
|
16
|
+
Supports:
|
|
17
|
+
- Loading templates from files
|
|
18
|
+
- Registering templates by name
|
|
19
|
+
- Namespaced template names (e.g., 'prompts.system.default')
|
|
20
|
+
- Template versioning via content hashing
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self) -> None:
|
|
24
|
+
self._templates: dict[str, Template] = {}
|
|
25
|
+
self._sources: dict[str, str] = {} # name -> original source
|
|
26
|
+
self._hashes: dict[str, str] = {} # name -> content hash
|
|
27
|
+
|
|
28
|
+
def register(self, name: str, template: Template | str) -> None:
|
|
29
|
+
"""
|
|
30
|
+
Register a template by name.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
name: Template name (can use dots for namespacing)
|
|
34
|
+
template: Jinja Template object or template string
|
|
35
|
+
"""
|
|
36
|
+
if isinstance(template, str):
|
|
37
|
+
source = template
|
|
38
|
+
template = Template(template)
|
|
39
|
+
else:
|
|
40
|
+
source = template.source if hasattr(template, "source") else ""
|
|
41
|
+
|
|
42
|
+
self._templates[name] = template
|
|
43
|
+
self._sources[name] = source
|
|
44
|
+
self._hashes[name] = self._compute_hash(source)
|
|
45
|
+
|
|
46
|
+
def get(self, name: str) -> Template:
|
|
47
|
+
"""
|
|
48
|
+
Get a template by name.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
name: Template name
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
The Jinja Template
|
|
55
|
+
|
|
56
|
+
Raises:
|
|
57
|
+
TemplateError: If template not found
|
|
58
|
+
"""
|
|
59
|
+
if name not in self._templates:
|
|
60
|
+
raise TemplateError(f"Template not found: {name}")
|
|
61
|
+
return self._templates[name]
|
|
62
|
+
|
|
63
|
+
def render(self, name: str, variables: dict[str, Any] | None = None) -> str:
|
|
64
|
+
"""
|
|
65
|
+
Render a template by name.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
name: Template name
|
|
69
|
+
variables: Variables to pass to template
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Rendered template string
|
|
73
|
+
"""
|
|
74
|
+
template = self.get(name)
|
|
75
|
+
return template.render(**(variables or {}))
|
|
76
|
+
|
|
77
|
+
def has(self, name: str) -> bool:
|
|
78
|
+
"""Check if a template exists."""
|
|
79
|
+
return name in self._templates
|
|
80
|
+
|
|
81
|
+
def list_templates(self, prefix: str | None = None) -> list[str]:
|
|
82
|
+
"""
|
|
83
|
+
List registered template names.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
prefix: Optional prefix to filter by (e.g., 'prompts.')
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
List of template names
|
|
90
|
+
"""
|
|
91
|
+
names = list(self._templates.keys())
|
|
92
|
+
if prefix:
|
|
93
|
+
names = [n for n in names if n.startswith(prefix)]
|
|
94
|
+
return sorted(names)
|
|
95
|
+
|
|
96
|
+
def get_hash(self, name: str) -> str:
|
|
97
|
+
"""
|
|
98
|
+
Get the content hash of a template.
|
|
99
|
+
|
|
100
|
+
Useful for reproducibility tracking.
|
|
101
|
+
"""
|
|
102
|
+
if name not in self._hashes:
|
|
103
|
+
raise TemplateError(f"Template not found: {name}")
|
|
104
|
+
return self._hashes[name]
|
|
105
|
+
|
|
106
|
+
def get_source(self, name: str) -> str:
|
|
107
|
+
"""Get the original source of a template."""
|
|
108
|
+
if name not in self._sources:
|
|
109
|
+
raise TemplateError(f"Template not found: {name}")
|
|
110
|
+
return self._sources[name]
|
|
111
|
+
|
|
112
|
+
def _compute_hash(self, source: str) -> str:
|
|
113
|
+
"""Compute SHA256 hash of template source."""
|
|
114
|
+
return hashlib.sha256(source.encode()).hexdigest()[:16]
|
|
115
|
+
|
|
116
|
+
def load_from_directory(
|
|
117
|
+
self,
|
|
118
|
+
directory: Path | str,
|
|
119
|
+
prefix: str = "",
|
|
120
|
+
extensions: tuple[str, ...] = (".jinja", ".j2", ".txt", ".md"),
|
|
121
|
+
) -> int:
|
|
122
|
+
"""
|
|
123
|
+
Load all templates from a directory.
|
|
124
|
+
|
|
125
|
+
Template names are derived from file paths:
|
|
126
|
+
- 'prompts/system/default.jinja' -> 'system.default'
|
|
127
|
+
- With prefix='prompts': 'prompts.system.default'
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
directory: Directory to load from
|
|
131
|
+
prefix: Prefix to add to template names
|
|
132
|
+
extensions: File extensions to load
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
Number of templates loaded
|
|
136
|
+
"""
|
|
137
|
+
directory = Path(directory)
|
|
138
|
+
if not directory.exists():
|
|
139
|
+
raise TemplateError(f"Template directory not found: {directory}")
|
|
140
|
+
|
|
141
|
+
count = 0
|
|
142
|
+
for file_path in directory.rglob("*"):
|
|
143
|
+
if file_path.is_file() and file_path.suffix in extensions:
|
|
144
|
+
# Convert path to template name
|
|
145
|
+
relative = file_path.relative_to(directory)
|
|
146
|
+
# Remove extension and convert path separators to dots
|
|
147
|
+
name_parts = list(relative.parts)
|
|
148
|
+
name_parts[-1] = relative.stem # Remove extension from last part
|
|
149
|
+
name = ".".join(name_parts)
|
|
150
|
+
|
|
151
|
+
if prefix:
|
|
152
|
+
name = f"{prefix}.{name}"
|
|
153
|
+
|
|
154
|
+
# Load and register
|
|
155
|
+
source = file_path.read_text()
|
|
156
|
+
self.register(name, source)
|
|
157
|
+
count += 1
|
|
158
|
+
|
|
159
|
+
return count
|
|
160
|
+
|
|
161
|
+
def clear(self) -> None:
|
|
162
|
+
"""Remove all registered templates."""
|
|
163
|
+
self._templates.clear()
|
|
164
|
+
self._sources.clear()
|
|
165
|
+
self._hashes.clear()
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Tool/function calling helpers for Flashlite.
|
|
2
|
+
|
|
3
|
+
This module provides utilities for:
|
|
4
|
+
- Defining tools using decorators (@tool) or Pydantic models
|
|
5
|
+
- Converting tools to OpenAI/Anthropic formats
|
|
6
|
+
- Running tool execution loops for agentic patterns
|
|
7
|
+
|
|
8
|
+
Example:
|
|
9
|
+
from flashlite import Flashlite
|
|
10
|
+
from flashlite.tools import tool, run_tool_loop
|
|
11
|
+
|
|
12
|
+
@tool()
|
|
13
|
+
def get_weather(location: str, unit: str = "celsius") -> str:
|
|
14
|
+
'''Get the current weather for a location.'''
|
|
15
|
+
# Implementation
|
|
16
|
+
return f"Weather in {location}: 72°F"
|
|
17
|
+
|
|
18
|
+
@tool()
|
|
19
|
+
def search_web(query: str, max_results: int = 5) -> list[str]:
|
|
20
|
+
'''Search the web for information.'''
|
|
21
|
+
return ["result1", "result2"]
|
|
22
|
+
|
|
23
|
+
client = Flashlite(default_model="gpt-4o")
|
|
24
|
+
|
|
25
|
+
# Run tool loop
|
|
26
|
+
result = await run_tool_loop(
|
|
27
|
+
client=client,
|
|
28
|
+
messages=[{"role": "user", "content": "What's the weather in NYC?"}],
|
|
29
|
+
tools=[get_weather, search_web],
|
|
30
|
+
)
|
|
31
|
+
print(result.content)
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from .definitions import (
|
|
35
|
+
ToolDefinition,
|
|
36
|
+
ToolRegistry,
|
|
37
|
+
format_tool_result,
|
|
38
|
+
get_tool_definition,
|
|
39
|
+
tool,
|
|
40
|
+
tool_from_pydantic,
|
|
41
|
+
tools_to_anthropic,
|
|
42
|
+
tools_to_openai,
|
|
43
|
+
)
|
|
44
|
+
from .execution import (
|
|
45
|
+
ToolCall,
|
|
46
|
+
ToolLoopResult,
|
|
47
|
+
ToolResult,
|
|
48
|
+
build_tool_registry,
|
|
49
|
+
execute_tool,
|
|
50
|
+
execute_tools_parallel,
|
|
51
|
+
extract_tool_calls,
|
|
52
|
+
run_tool_loop,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
__all__ = [
|
|
56
|
+
# Definitions
|
|
57
|
+
"tool",
|
|
58
|
+
"tool_from_pydantic",
|
|
59
|
+
"ToolDefinition",
|
|
60
|
+
"ToolRegistry",
|
|
61
|
+
"get_tool_definition",
|
|
62
|
+
"tools_to_openai",
|
|
63
|
+
"tools_to_anthropic",
|
|
64
|
+
"format_tool_result",
|
|
65
|
+
# Execution
|
|
66
|
+
"ToolCall",
|
|
67
|
+
"ToolResult",
|
|
68
|
+
"ToolLoopResult",
|
|
69
|
+
"run_tool_loop",
|
|
70
|
+
"execute_tool",
|
|
71
|
+
"execute_tools_parallel",
|
|
72
|
+
"extract_tool_calls",
|
|
73
|
+
"build_tool_registry",
|
|
74
|
+
]
|