Skip to content

Commit 688115a

Browse files
Merge pull request #272 from Pipelex/release/v0.9.6
Add func-registry and mistral-medium
2 parents 665f15b + 598de04 commit 688115a

11 files changed

Lines changed: 300 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Changelog
22

3+
## [v0.9.6] - 2025-09-16
4+
5+
### Added
6+
7+
- Added `FuncRegistryUtils` to register functions in a pipelex folder that have a specific signature.
8+
- Added `mistral-medium` and `mistral-medium-2508` to the LLM deck.
9+
- Added handle for `gemini-2.5-flash` to the LLM deck.
10+
311
## [v0.9.5] - 2025-09-12
412

513
### Highlight

pipelex/cogt/llm/llm_models/llm_family.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ class LLMFamily(StrEnum):
7575
MISTRAL_8X7B = "mistral-8x7b"
7676
MISTRAL_LARGE = "mistral-large"
7777
MISTRAL_SMALL = "mistral-small"
78+
MISTRAL_MEDIUM = "mistral-medium"
7879
MISTRAL_CODESTRAL = "mistral-codestral"
7980
MINISTRAL = "ministral"
8081
PIXTRAL = "pixtral"
@@ -128,6 +129,7 @@ def creator(self) -> LLMCreator:
128129
| LLMFamily.MISTRAL_8X7B
129130
| LLMFamily.MISTRAL_LARGE
130131
| LLMFamily.MISTRAL_SMALL
132+
| LLMFamily.MISTRAL_MEDIUM
131133
| LLMFamily.MISTRAL_CODESTRAL
132134
| LLMFamily.MINISTRAL
133135
| LLMFamily.PIXTRAL

pipelex/libraries/library_manager.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from pipelex.libraries.library_config import LibraryConfig
2929
from pipelex.libraries.library_manager_abstract import LibraryManagerAbstract
3030
from pipelex.tools.class_registry_utils import ClassRegistryUtils
31+
from pipelex.tools.func_registry_utils import FuncRegistryUtils
3132
from pipelex.tools.misc.file_utils import find_files_in_dir
3233
from pipelex.tools.misc.json_utils import deep_update
3334
from pipelex.tools.misc.toml_utils import TOMLValidationError, load_toml_from_path, validate_toml_file
@@ -243,6 +244,7 @@ def load_libraries(self, library_dirs: Optional[List[Path]] = None, library_file
243244
# Register classes in the directories
244245
for library_dir in dirs_to_use:
245246
ClassRegistryUtils.register_classes_in_folder(folder_path=str(library_dir))
247+
FuncRegistryUtils.register_funcs_in_folder(folder_path=str(library_dir))
246248

247249
if library_file_paths is not None:
248250
all_plx_paths = library_file_paths

pipelex/libraries/llm_deck/base_llm_deck.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ llm_external_handles = []
1313
gpt-4o-2024-11-20 = { llm_name = "gpt-4o", llm_version = "2024-11-20" }
1414
base-claude = "claude-4-sonnet"
1515
base-gpt = "gpt-5"
16+
base-gemini = "gemini-2.5-flash"
17+
base-mistral = "mistral-medium"
1618

1719
best-gpt = "gpt-5"
1820
best-claude = "claude-4-1-opus"
@@ -47,6 +49,9 @@ llm_for_testing_gen_object = { llm_handle = "base_llm_for_object", temperature =
4749
####################################################################################################
4850
# LLM Presets — Specific skills
4951

52+
# Cheap llm
53+
llm_cheap = { llm_handle = "base-gemini", temperature = 0.1 }
54+
5055
# Generation skills
5156
llm_for_factual_writing = { llm_handle = "base-gpt", temperature = 0.1 }
5257
llm_for_creative_writing = { llm_handle = "best-claude", temperature = 0.9 }
@@ -81,7 +86,6 @@ llm_to_extract_invoice_from_scan = { llm_handle = "best-claude", temperature = 0
8186
llm_to_extract_legal_terms = { llm_handle = "best-claude", temperature = 0.1 }
8287
llm_to_extract_tables = { llm_handle = "best-claude", temperature = 0.1 }
8388

84-
8589
####################################################################################################
8690
# LLM Choices
8791
####################################################################################################

pipelex/libraries/llm_integrations/mistral.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,16 @@ is_vision_supported = true
6868
cost_per_million_tokens_usd = { input = 2.0, output = 6.0 }
6969
platform_llm_id = { mistral = "pixtral-large-latest" }
7070

71+
[mistral-medium.mistral-medium.latest]
72+
max_tokens = 128000
73+
is_gen_object_supported = true
74+
cost_per_million_tokens_usd = { input = 0.4, output = 2.0 }
75+
platform_llm_id = { mistral = "mistral-medium-latest" }
76+
77+
78+
[mistral-medium.mistral-medium."2508"]
79+
max_tokens = 128000
80+
is_gen_object_supported = true
81+
cost_per_million_tokens_usd = { input = 0.4, output = 2.0 }
82+
platform_llm_id = { mistral = "mistral-medium-2508" }
83+

pipelex/plugins/openai/openai_llm_worker.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ async def _gen_text(
115115
| LLMFamily.MISTRAL_8X7B
116116
| LLMFamily.MISTRAL_LARGE
117117
| LLMFamily.MISTRAL_SMALL
118+
| LLMFamily.MISTRAL_MEDIUM
118119
| LLMFamily.MISTRAL_CODESTRAL
119120
| LLMFamily.MINISTRAL
120121
| LLMFamily.PIXTRAL
@@ -220,6 +221,7 @@ async def _gen_object(
220221
| LLMFamily.MISTRAL_8X7B
221222
| LLMFamily.MISTRAL_LARGE
222223
| LLMFamily.MISTRAL_SMALL
224+
| LLMFamily.MISTRAL_MEDIUM
223225
| LLMFamily.MISTRAL_CODESTRAL
224226
| LLMFamily.MINISTRAL
225227
| LLMFamily.PIXTRAL
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import inspect
2+
from pathlib import Path
3+
from typing import Any, Callable, List, get_type_hints
4+
5+
from pipelex.core.memory.working_memory import WorkingMemory
6+
from pipelex.core.stuffs.stuff_content import StuffContent
7+
from pipelex.tools.func_registry import func_registry
8+
from pipelex.tools.typing.module_inspector import import_module_from_file
9+
10+
11+
class FuncRegistryUtils:
12+
@classmethod
13+
def register_funcs_in_folder(
14+
cls,
15+
folder_path: str,
16+
is_recursive: bool = True,
17+
) -> None:
18+
"""
19+
Registers all functions in Python files within a folder that have:
20+
- Exactly 1 parameter named "working_memory" with type WorkingMemory
21+
- Return type that is a subclass of StuffContent
22+
23+
The function name is used as the registry key.
24+
25+
Args:
26+
folder_path: Path to folder containing Python files
27+
is_recursive: Whether to search recursively in subdirectories
28+
"""
29+
python_files = cls._find_files_in_dir(
30+
dir_path=folder_path,
31+
pattern="*.py",
32+
is_recursive=is_recursive,
33+
)
34+
35+
for python_file in python_files:
36+
cls._register_funcs_in_file(file_path=str(python_file))
37+
38+
@classmethod
39+
def _register_funcs_in_file(cls, file_path: str) -> None:
40+
"""Processes a Python file to find and register eligible functions."""
41+
try:
42+
module = import_module_from_file(file_path)
43+
44+
# Find functions that match criteria
45+
functions_to_register = cls._find_functions_in_module(module)
46+
47+
for func in functions_to_register:
48+
func_registry.register_function(
49+
func=func,
50+
name=func.__name__,
51+
should_warn_if_already_registered=True,
52+
)
53+
except Exception as e:
54+
# Log error but continue processing other files
55+
print(f"Error processing file {file_path}: {e}")
56+
57+
@classmethod
58+
def _find_functions_in_module(cls, module: Any) -> List[Callable[..., Any]]:
59+
"""
60+
Finds all functions in a module that match the criteria:
61+
- Exactly 1 parameter named "working_memory" with type WorkingMemory
62+
- Return type that is a subclass of StuffContent
63+
"""
64+
functions: List[Callable[..., Any]] = []
65+
module_name = module.__name__
66+
67+
# Find all functions in the module (not imported ones)
68+
for _, obj in inspect.getmembers(module, inspect.isfunction):
69+
# Skip functions imported from other modules
70+
if obj.__module__ != module_name:
71+
continue
72+
73+
if cls._is_eligible_function(obj):
74+
functions.append(obj)
75+
76+
return functions
77+
78+
@classmethod
79+
def _is_eligible_function(cls, func: Callable[..., Any]) -> bool:
80+
"""
81+
Checks if a function matches the criteria:
82+
- Exactly 1 parameter named "working_memory" with type WorkingMemory
83+
- Return type that is a subclass of StuffContent
84+
"""
85+
try:
86+
# Get function signature
87+
sig = inspect.signature(func)
88+
params = list(sig.parameters.values())
89+
90+
# Check parameter count and name
91+
if len(params) != 1:
92+
return False
93+
94+
param = params[0]
95+
if param.name != "working_memory":
96+
return False
97+
98+
# Get type hints
99+
type_hints = get_type_hints(func)
100+
101+
# Check parameter type
102+
if "working_memory" not in type_hints:
103+
return False
104+
105+
param_type = type_hints["working_memory"]
106+
if param_type != WorkingMemory:
107+
return False
108+
109+
# Check return type
110+
if "return" not in type_hints:
111+
return False
112+
113+
return_type = type_hints["return"]
114+
115+
# Check if return type is a subclass of StuffContent
116+
try:
117+
if inspect.isclass(return_type) and issubclass(return_type, StuffContent):
118+
return True
119+
# Handle generic types like ListContent[SomeType]
120+
if hasattr(return_type, "__origin__"):
121+
origin = getattr(return_type, "__origin__")
122+
if inspect.isclass(origin) and issubclass(origin, StuffContent):
123+
return True
124+
except TypeError:
125+
# Handle cases where issubclass fails on generic types
126+
pass
127+
128+
return False
129+
130+
except Exception:
131+
# If we can't analyze the function, skip it
132+
return False
133+
134+
@classmethod
135+
def _find_files_in_dir(cls, dir_path: str, pattern: str, is_recursive: bool) -> List[Path]:
136+
"""
137+
Find files matching a pattern in a directory.
138+
139+
Args:
140+
dir_path: Directory path to search in
141+
pattern: File pattern to match (e.g. "*.py")
142+
is_recursive: Whether to search recursively in subdirectories
143+
144+
Returns:
145+
List of matching Path objects
146+
"""
147+
path = Path(dir_path)
148+
if is_recursive:
149+
return list(path.rglob(pattern))
150+
else:
151+
return list(path.glob(pattern))

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "pipelex"
3-
version = "0.9.5"
3+
version = "0.9.6"
44
description = "Pipelex is an open-source dev tool based on a simple declarative language that lets you define replicable, structured, composable LLM pipelines."
55
authors = [{ name = "Evotis S.A.S.", email = "evotis@pipelex.com" }]
66
maintainers = [{ name = "Pipelex staff", email = "oss@pipelex.com" }]

tests/test_pipelines/test_file_func_registry.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
from pipelex.core.memory.working_memory import WorkingMemory
66
from pipelex.core.stuffs.stuff_content import ListContent, StructuredContent
7-
from pipelex.tools.func_registry import func_registry
87

98

109
class FilePath(StructuredContent):
@@ -44,6 +43,3 @@ def read_file_content(working_memory: WorkingMemory) -> ListContent[CodebaseFile
4443
)
4544

4645
return ListContent[CodebaseFileContent](items=codebase_files)
47-
48-
49-
func_registry.register_function(read_file_content, name="read_file_content")
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
#!/usr/bin/env python3
2+
"""Test script for FuncRegistryUtils"""
3+
4+
import tempfile
5+
from pathlib import Path
6+
7+
from pydantic import Field
8+
9+
from pipelex.core.stuffs.stuff_content import StructuredContent
10+
from pipelex.tools.func_registry import func_registry
11+
from pipelex.tools.func_registry_utils import FuncRegistryUtils
12+
13+
14+
class FilePath(StructuredContent):
15+
"""A path to a file in the codebase."""
16+
17+
path: str = Field(description="Path to the file")
18+
19+
20+
class CodebaseFileContent(StructuredContent):
21+
"""Content of a codebase file."""
22+
23+
file_path: str = Field(description="Path to the codebase file")
24+
file_content: str = Field(description="Content of the codebase file")
25+
26+
27+
def test_func_registry_utils():
28+
"""Test the FuncRegistryUtils implementation."""
29+
30+
# Create a temporary directory and file with the example function
31+
with tempfile.TemporaryDirectory() as temp_dir:
32+
test_file = Path(temp_dir) / "test_functions.py"
33+
34+
# Write the example function to the test file
35+
test_file.write_text("""
36+
from typing import List
37+
38+
from pydantic import Field
39+
40+
from pipelex.core.memory.working_memory import WorkingMemory
41+
from pipelex.core.stuffs.stuff_content import ListContent, StructuredContent
42+
43+
44+
class FilePath(StructuredContent):
45+
path: str = Field(description="Path to the file")
46+
47+
48+
class CodebaseFileContent(StructuredContent):
49+
file_path: str = Field(description="Path to the codebase file")
50+
file_content: str = Field(description="Content of the codebase file")
51+
52+
53+
def read_file_content(working_memory: WorkingMemory) -> ListContent[CodebaseFileContent]:
54+
'''Read the content of related codebase files.'''
55+
56+
file_paths_list = working_memory.get_stuff_as_list("related_file_paths", item_type=FilePath)
57+
58+
codebase_files: List[CodebaseFileContent] = []
59+
for file_path in file_paths_list.items:
60+
try:
61+
with open(file_path.path, "r", encoding="utf-8") as file:
62+
content = file.read()
63+
codebase_files.append(CodebaseFileContent(file_path=file_path.path, file_content=content))
64+
except Exception as e:
65+
codebase_files.append(
66+
CodebaseFileContent(file_path=file_path.path, file_content=f"# File not found or unreadable: {file_path.path}\\n# Error: {str(e)}")
67+
)
68+
69+
return ListContent[CodebaseFileContent](items=codebase_files)
70+
71+
72+
def invalid_function_wrong_param_name(other_param: WorkingMemory) -> StructuredContent:
73+
'''This should not be registered - wrong parameter name.'''
74+
pass
75+
76+
77+
def invalid_function_wrong_param_type(working_memory: str) -> StructuredContent:
78+
'''This should not be registered - wrong parameter type.'''
79+
pass
80+
81+
82+
def invalid_function_wrong_return_type(working_memory: WorkingMemory) -> str:
83+
'''This should not be registered - wrong return type.'''
84+
return "test"
85+
86+
87+
def invalid_function_too_many_params(working_memory: WorkingMemory, extra_param: str) -> StructuredContent:
88+
'''This should not be registered - too many parameters.'''
89+
pass
90+
""")
91+
92+
# Clear the registry to start fresh
93+
func_registry.teardown()
94+
95+
# Test the registration
96+
FuncRegistryUtils.register_funcs_in_folder(folder_path=temp_dir, is_recursive=False)
97+
98+
# Check that only the valid function was registered
99+
assert func_registry.has_function("read_file_content"), "read_file_content should be registered"
100+
assert not func_registry.has_function("invalid_function_wrong_param_name"), "invalid_function_wrong_param_name should not be registered"
101+
assert not func_registry.has_function("invalid_function_wrong_param_type"), "invalid_function_wrong_param_type should not be registered"
102+
assert not func_registry.has_function("invalid_function_wrong_return_type"), "invalid_function_wrong_return_type should not be registered"
103+
assert not func_registry.has_function("invalid_function_too_many_params"), "invalid_function_too_many_params should not be registered"
104+
105+
# Verify we can get the function
106+
registered_func = func_registry.get_function("read_file_content")
107+
assert registered_func is not None, "Registered function should be retrievable"
108+
assert registered_func.__name__ == "read_file_content", "Function name should match"
109+
110+
print("✅ All tests passed!")
111+
print(f"✅ Successfully registered function: {registered_func.__name__}")
112+
113+
114+
if __name__ == "__main__":
115+
test_func_registry_utils()

0 commit comments

Comments
 (0)