llm-function 0.0.1__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.
Files changed (157) hide show
  1. llm_function/.paa.tracking/.paa.config +12 -0
  2. llm_function/.paa.tracking/.paa.version +1 -0
  3. llm_function/.paa.tracking/git/dependencies/llm_func_deps/llm_function_config.py +35 -0
  4. llm_function/.paa.tracking/git/dependencies/llm_func_deps/tool_registry.py +250 -0
  5. llm_function/.paa.tracking/git/docs_cache/llm_function.md +0 -0
  6. llm_function/.paa.tracking/git/module/llm_function.py +250 -0
  7. llm_function/.paa.tracking/git/tracking/release_notes.md +5 -0
  8. llm_function/.paa.tracking/git_repo/COMMIT_EDITMSG +1 -0
  9. llm_function/.paa.tracking/git_repo/HEAD +1 -0
  10. llm_function/.paa.tracking/git_repo/config +8 -0
  11. llm_function/.paa.tracking/git_repo/description +1 -0
  12. llm_function/.paa.tracking/git_repo/hooks/applypatch-msg.sample +15 -0
  13. llm_function/.paa.tracking/git_repo/hooks/commit-msg.sample +24 -0
  14. llm_function/.paa.tracking/git_repo/hooks/fsmonitor-watchman.sample +174 -0
  15. llm_function/.paa.tracking/git_repo/hooks/post-update.sample +8 -0
  16. llm_function/.paa.tracking/git_repo/hooks/pre-applypatch.sample +14 -0
  17. llm_function/.paa.tracking/git_repo/hooks/pre-commit.sample +49 -0
  18. llm_function/.paa.tracking/git_repo/hooks/pre-merge-commit.sample +13 -0
  19. llm_function/.paa.tracking/git_repo/hooks/pre-push.sample +53 -0
  20. llm_function/.paa.tracking/git_repo/hooks/pre-rebase.sample +169 -0
  21. llm_function/.paa.tracking/git_repo/hooks/pre-receive.sample +24 -0
  22. llm_function/.paa.tracking/git_repo/hooks/prepare-commit-msg.sample +42 -0
  23. llm_function/.paa.tracking/git_repo/hooks/push-to-checkout.sample +78 -0
  24. llm_function/.paa.tracking/git_repo/hooks/sendemail-validate.sample +77 -0
  25. llm_function/.paa.tracking/git_repo/hooks/update.sample +128 -0
  26. llm_function/.paa.tracking/git_repo/index +0 -0
  27. llm_function/.paa.tracking/git_repo/info/exclude +6 -0
  28. llm_function/.paa.tracking/git_repo/logs/HEAD +1 -0
  29. llm_function/.paa.tracking/git_repo/logs/refs/heads/master +1 -0
  30. llm_function/.paa.tracking/git_repo/objects/13/9201e8ffeb4e42262e90eeaefd5267debb6e1d +1 -0
  31. llm_function/.paa.tracking/git_repo/objects/23/d10b479e3102724a6eb510225cc24bb2ce9d78 +0 -0
  32. llm_function/.paa.tracking/git_repo/objects/2d/fa3abff2848ff521ead3abb4364bd2c3ca01a6 +0 -0
  33. llm_function/.paa.tracking/git_repo/objects/45/6fc88abc846d76e0f82a3ef180425e497f8281 +0 -0
  34. llm_function/.paa.tracking/git_repo/objects/55/d196aad68e2cc20a2ea496529d9dabeb75bb0e +0 -0
  35. llm_function/.paa.tracking/git_repo/objects/63/9282ebc80b9db9b677374a7c16bc7472d5e71c +0 -0
  36. llm_function/.paa.tracking/git_repo/objects/91/f1d4c0830abe4ebb0902dc052f5dfe9e8fade7 +0 -0
  37. llm_function/.paa.tracking/git_repo/objects/9d/2a8151dd42e6abfffbbbd674b850a7066daa1a +0 -0
  38. llm_function/.paa.tracking/git_repo/objects/aa/0b3847c3f0683eed107a405b9443ec040c3eec +0 -0
  39. llm_function/.paa.tracking/git_repo/objects/cf/024b71ae427969a985d86a267adf154f526cd8 +0 -0
  40. llm_function/.paa.tracking/git_repo/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 +0 -0
  41. llm_function/.paa.tracking/git_repo/objects/fb/834f2f59b432c0688699b91926899c0e0f2a5d +0 -0
  42. llm_function/.paa.tracking/git_repo/refs/heads/master +1 -0
  43. llm_function/.paa.tracking/git_repo/refs/tags/v0.0.1 +1 -0
  44. llm_function/.paa.tracking/lsts_package_versions.yml +3 -0
  45. llm_function/.paa.tracking/package_licenses.json +1 -0
  46. llm_function/.paa.tracking/package_mapping.json +1 -0
  47. llm_function/.paa.tracking/python_modules/components/llm_func_deps/llm_function_config.py +35 -0
  48. llm_function/.paa.tracking/python_modules/components/llm_func_deps/tool_registry.py +250 -0
  49. llm_function/.paa.tracking/python_modules/llm_function.py +250 -0
  50. llm_function/.paa.tracking/release_notes.md +5 -0
  51. llm_function/.paa.tracking/version_logs.csv +5 -0
  52. llm_function/__init__.py +17 -0
  53. llm_function/artifacts/.paa.tracking/git/dependencies/llm_func_deps/llm_function_config.py +35 -0
  54. llm_function/artifacts/.paa.tracking/git/dependencies/llm_func_deps/tool_registry.py +250 -0
  55. llm_function/artifacts/.paa.tracking/git/docs_cache/llm_function.md +0 -0
  56. llm_function/artifacts/.paa.tracking/git/module/llm_function.py +250 -0
  57. llm_function/artifacts/.paa.tracking/git/tracking/release_notes.md +5 -0
  58. llm_function/artifacts/.paa.tracking/git_repo/COMMIT_EDITMSG +1 -0
  59. llm_function/artifacts/.paa.tracking/git_repo/HEAD +1 -0
  60. llm_function/artifacts/.paa.tracking/git_repo/config +8 -0
  61. llm_function/artifacts/.paa.tracking/git_repo/description +1 -0
  62. llm_function/artifacts/.paa.tracking/git_repo/hooks/applypatch-msg.sample +15 -0
  63. llm_function/artifacts/.paa.tracking/git_repo/hooks/commit-msg.sample +24 -0
  64. llm_function/artifacts/.paa.tracking/git_repo/hooks/fsmonitor-watchman.sample +174 -0
  65. llm_function/artifacts/.paa.tracking/git_repo/hooks/post-update.sample +8 -0
  66. llm_function/artifacts/.paa.tracking/git_repo/hooks/pre-applypatch.sample +14 -0
  67. llm_function/artifacts/.paa.tracking/git_repo/hooks/pre-commit.sample +49 -0
  68. llm_function/artifacts/.paa.tracking/git_repo/hooks/pre-merge-commit.sample +13 -0
  69. llm_function/artifacts/.paa.tracking/git_repo/hooks/pre-push.sample +53 -0
  70. llm_function/artifacts/.paa.tracking/git_repo/hooks/pre-rebase.sample +169 -0
  71. llm_function/artifacts/.paa.tracking/git_repo/hooks/pre-receive.sample +24 -0
  72. llm_function/artifacts/.paa.tracking/git_repo/hooks/prepare-commit-msg.sample +42 -0
  73. llm_function/artifacts/.paa.tracking/git_repo/hooks/push-to-checkout.sample +78 -0
  74. llm_function/artifacts/.paa.tracking/git_repo/hooks/sendemail-validate.sample +77 -0
  75. llm_function/artifacts/.paa.tracking/git_repo/hooks/update.sample +128 -0
  76. llm_function/artifacts/.paa.tracking/git_repo/index +0 -0
  77. llm_function/artifacts/.paa.tracking/git_repo/info/exclude +6 -0
  78. llm_function/artifacts/.paa.tracking/git_repo/logs/HEAD +1 -0
  79. llm_function/artifacts/.paa.tracking/git_repo/logs/refs/heads/master +1 -0
  80. llm_function/artifacts/.paa.tracking/git_repo/objects/13/9201e8ffeb4e42262e90eeaefd5267debb6e1d +1 -0
  81. llm_function/artifacts/.paa.tracking/git_repo/objects/23/d10b479e3102724a6eb510225cc24bb2ce9d78 +0 -0
  82. llm_function/artifacts/.paa.tracking/git_repo/objects/2d/fa3abff2848ff521ead3abb4364bd2c3ca01a6 +0 -0
  83. llm_function/artifacts/.paa.tracking/git_repo/objects/45/6fc88abc846d76e0f82a3ef180425e497f8281 +0 -0
  84. llm_function/artifacts/.paa.tracking/git_repo/objects/55/d196aad68e2cc20a2ea496529d9dabeb75bb0e +0 -0
  85. llm_function/artifacts/.paa.tracking/git_repo/objects/63/9282ebc80b9db9b677374a7c16bc7472d5e71c +0 -0
  86. llm_function/artifacts/.paa.tracking/git_repo/objects/91/f1d4c0830abe4ebb0902dc052f5dfe9e8fade7 +0 -0
  87. llm_function/artifacts/.paa.tracking/git_repo/objects/9d/2a8151dd42e6abfffbbbd674b850a7066daa1a +0 -0
  88. llm_function/artifacts/.paa.tracking/git_repo/objects/aa/0b3847c3f0683eed107a405b9443ec040c3eec +0 -0
  89. llm_function/artifacts/.paa.tracking/git_repo/objects/cf/024b71ae427969a985d86a267adf154f526cd8 +0 -0
  90. llm_function/artifacts/.paa.tracking/git_repo/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 +0 -0
  91. llm_function/artifacts/.paa.tracking/git_repo/objects/fb/834f2f59b432c0688699b91926899c0e0f2a5d +0 -0
  92. llm_function/artifacts/.paa.tracking/git_repo/refs/heads/master +1 -0
  93. llm_function/artifacts/.paa.tracking/git_repo/refs/tags/v0.0.1 +1 -0
  94. llm_function/llm_function.py +518 -0
  95. llm_function/mkdocs/docs/css/extra.css +38 -0
  96. llm_function/mkdocs/docs/index.md +24 -0
  97. llm_function/mkdocs/docs/llm_function_tools.md +168 -0
  98. llm_function/mkdocs/docs/release-notes.md +5 -0
  99. llm_function/mkdocs/mkdocs.yml +37 -0
  100. llm_function/mkdocs/site/404.html +370 -0
  101. llm_function/mkdocs/site/assets/images/favicon.png +0 -0
  102. llm_function/mkdocs/site/assets/javascripts/bundle.fe8b6f2b.min.js +29 -0
  103. llm_function/mkdocs/site/assets/javascripts/bundle.fe8b6f2b.min.js.map +7 -0
  104. llm_function/mkdocs/site/assets/javascripts/lunr/min/lunr.ar.min.js +1 -0
  105. llm_function/mkdocs/site/assets/javascripts/lunr/min/lunr.da.min.js +18 -0
  106. llm_function/mkdocs/site/assets/javascripts/lunr/min/lunr.de.min.js +18 -0
  107. llm_function/mkdocs/site/assets/javascripts/lunr/min/lunr.du.min.js +18 -0
  108. llm_function/mkdocs/site/assets/javascripts/lunr/min/lunr.el.min.js +1 -0
  109. llm_function/mkdocs/site/assets/javascripts/lunr/min/lunr.es.min.js +18 -0
  110. llm_function/mkdocs/site/assets/javascripts/lunr/min/lunr.fi.min.js +18 -0
  111. llm_function/mkdocs/site/assets/javascripts/lunr/min/lunr.fr.min.js +18 -0
  112. llm_function/mkdocs/site/assets/javascripts/lunr/min/lunr.he.min.js +1 -0
  113. llm_function/mkdocs/site/assets/javascripts/lunr/min/lunr.hi.min.js +1 -0
  114. llm_function/mkdocs/site/assets/javascripts/lunr/min/lunr.hu.min.js +18 -0
  115. llm_function/mkdocs/site/assets/javascripts/lunr/min/lunr.hy.min.js +1 -0
  116. llm_function/mkdocs/site/assets/javascripts/lunr/min/lunr.it.min.js +18 -0
  117. llm_function/mkdocs/site/assets/javascripts/lunr/min/lunr.ja.min.js +1 -0
  118. llm_function/mkdocs/site/assets/javascripts/lunr/min/lunr.jp.min.js +1 -0
  119. llm_function/mkdocs/site/assets/javascripts/lunr/min/lunr.kn.min.js +1 -0
  120. llm_function/mkdocs/site/assets/javascripts/lunr/min/lunr.ko.min.js +1 -0
  121. llm_function/mkdocs/site/assets/javascripts/lunr/min/lunr.multi.min.js +1 -0
  122. llm_function/mkdocs/site/assets/javascripts/lunr/min/lunr.nl.min.js +18 -0
  123. llm_function/mkdocs/site/assets/javascripts/lunr/min/lunr.no.min.js +18 -0
  124. llm_function/mkdocs/site/assets/javascripts/lunr/min/lunr.pt.min.js +18 -0
  125. llm_function/mkdocs/site/assets/javascripts/lunr/min/lunr.ro.min.js +18 -0
  126. llm_function/mkdocs/site/assets/javascripts/lunr/min/lunr.ru.min.js +18 -0
  127. llm_function/mkdocs/site/assets/javascripts/lunr/min/lunr.sa.min.js +1 -0
  128. llm_function/mkdocs/site/assets/javascripts/lunr/min/lunr.stemmer.support.min.js +1 -0
  129. llm_function/mkdocs/site/assets/javascripts/lunr/min/lunr.sv.min.js +18 -0
  130. llm_function/mkdocs/site/assets/javascripts/lunr/min/lunr.ta.min.js +1 -0
  131. llm_function/mkdocs/site/assets/javascripts/lunr/min/lunr.te.min.js +1 -0
  132. llm_function/mkdocs/site/assets/javascripts/lunr/min/lunr.th.min.js +1 -0
  133. llm_function/mkdocs/site/assets/javascripts/lunr/min/lunr.tr.min.js +18 -0
  134. llm_function/mkdocs/site/assets/javascripts/lunr/min/lunr.vi.min.js +1 -0
  135. llm_function/mkdocs/site/assets/javascripts/lunr/min/lunr.zh.min.js +1 -0
  136. llm_function/mkdocs/site/assets/javascripts/lunr/tinyseg.js +206 -0
  137. llm_function/mkdocs/site/assets/javascripts/lunr/wordcut.js +6708 -0
  138. llm_function/mkdocs/site/assets/javascripts/workers/search.b8dbb3d2.min.js +42 -0
  139. llm_function/mkdocs/site/assets/javascripts/workers/search.b8dbb3d2.min.js.map +7 -0
  140. llm_function/mkdocs/site/assets/stylesheets/main.3cba04c6.min.css +1 -0
  141. llm_function/mkdocs/site/assets/stylesheets/main.3cba04c6.min.css.map +1 -0
  142. llm_function/mkdocs/site/assets/stylesheets/palette.06af60db.min.css +1 -0
  143. llm_function/mkdocs/site/assets/stylesheets/palette.06af60db.min.css.map +1 -0
  144. llm_function/mkdocs/site/css/extra.css +38 -0
  145. llm_function/mkdocs/site/index.html +476 -0
  146. llm_function/mkdocs/site/llm_function_tools/index.html +622 -0
  147. llm_function/mkdocs/site/release-notes/index.html +468 -0
  148. llm_function/mkdocs/site/search/search_index.json +1 -0
  149. llm_function/mkdocs/site/sitemap.xml +3 -0
  150. llm_function/mkdocs/site/sitemap.xml.gz +0 -0
  151. llm_function/setup.py +47 -0
  152. llm_function-0.0.1.dist-info/METADATA +44 -0
  153. llm_function-0.0.1.dist-info/RECORD +157 -0
  154. llm_function-0.0.1.dist-info/WHEEL +5 -0
  155. llm_function-0.0.1.dist-info/licenses/LICENSE +201 -0
  156. llm_function-0.0.1.dist-info/licenses/NOTICE +4 -0
  157. llm_function-0.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,12 @@
1
+ use_commit_messages: true
2
+ check_vulnerabilities: true
3
+ check_dependencies_licenses: false
4
+ add_artifacts: true
5
+ add_mkdocs_site: true
6
+ pylint_threshold: 8.0
7
+ license_label: 'apache-2.0'
8
+ allowed_licenses: ['mit', 'apache-2.0', 'lgpl-3.0',
9
+ 'bsd-3-clause', 'bsd-2-clause', '-', 'mpl-2.0']
10
+ gh_pages_base_url: https://kiril-mordan.github.io/adaptive-reusables
11
+ source_repo_url: https://github.com/Kiril-Mordan/adaptive-reusables
12
+ source_repo_name: adaptive-reusables
@@ -0,0 +1 @@
1
+ 1.0.4
@@ -0,0 +1,35 @@
1
+ """
2
+ Config models for llm_function runtime wiring.
3
+ """
4
+
5
+ from typing import Any, Optional, Sequence
6
+
7
+ from pydantic import BaseModel, Field
8
+
9
+
10
+ class LlmRuntimeConfig(BaseModel):
11
+ """
12
+ Reusable runtime settings for llm_function execution.
13
+ """
14
+
15
+ llm_handler_params: dict = Field(description="Parameters passed to WorkflowAutoAssembler LLM handler.")
16
+ storage_path: Optional[str] = Field(default=None, description="Optional storage path for workflow persistence.")
17
+ force_replan: bool = Field(default=False, description="Force workflow replanning instead of reusing cached workflows.")
18
+ max_retry: Optional[int] = Field(default=None, description="Optional max retry override for planning loops.")
19
+ reset_loops: Optional[int] = Field(default=None, description="Optional reset loop override for planning loops.")
20
+ compare_params: Optional[dict] = Field(default=None, description="Optional compare parameters for workflow validation.")
21
+ test_params: Optional[list] = Field(default=None, description="Optional test cases for planning/validation.")
22
+
23
+
24
+ class LlmFunctionConfig(BaseModel):
25
+ """
26
+ Bundled llm_function configuration for future config-driven decorator usage.
27
+ """
28
+
29
+ runtime: LlmRuntimeConfig = Field(description="Runtime settings for workflow execution.")
30
+ tool_sources: Optional[Sequence[Any]] = Field(default=None, description="Configured tool sources for runtime loading.")
31
+ tool_registry: Optional[Any] = Field(default=None, description="Optional prebuilt tool registry.")
32
+
33
+ model_config = {
34
+ "arbitrary_types_allowed": True,
35
+ }
@@ -0,0 +1,250 @@
1
+ """
2
+ Runtime tool loading helpers for llm_function.
3
+ """
4
+
5
+ import attrs
6
+ import attrsx
7
+ import inspect
8
+ from pathlib import Path
9
+ from typing import Any, Callable, Dict, List, Optional, Sequence, Union
10
+
11
+ from pydantic import BaseModel, Field, SkipValidation
12
+ from workflow_auto_assembler import LlmFunctionItem, make_uid
13
+
14
+ from llm_function_tools import (
15
+ ToolSpec,
16
+ load_tools_from_module,
17
+ load_tools_from_python_file,
18
+ tool_from_callable,
19
+ )
20
+
21
+
22
+ class ResolvedTool(BaseModel):
23
+ """
24
+ Runtime-ready tool definition with provenance metadata.
25
+ """
26
+
27
+ tool_spec: ToolSpec = Field(description="Normalized tool definition.")
28
+ func: SkipValidation[Callable[..., Any]] = Field(description="Runtime callable for the tool.")
29
+ source_type: str = Field(description="Source kind, for example file or module.")
30
+ location_type: str = Field(description="Location kind, for example local or external.")
31
+ package_name: Optional[str] = Field(default=None, description="Optional package name.")
32
+ package_version: Optional[str] = Field(default=None, description="Optional resolved package version.")
33
+ module_name: Optional[str] = Field(default=None, description="Python module that defined the tool.")
34
+ file_path: Optional[str] = Field(default=None, description="Local file path when the tool was loaded from a file.")
35
+ origin_ref: Optional[str] = Field(default=None, description="Original source reference used to load the tool.")
36
+ metadata: dict = Field(default_factory=dict, description="Additional runtime metadata.")
37
+
38
+ model_config = {
39
+ "arbitrary_types_allowed": True,
40
+ }
41
+
42
+
43
+ @attrsx.define
44
+ class InMemoryToolSource:
45
+ """
46
+ Resolve tools directly from callables, ToolSpec objects, or ResolvedTool objects.
47
+ """
48
+
49
+ tools: Sequence[Union[Callable[..., Any], ToolSpec, ResolvedTool]] = attrs.field()
50
+ location_type: str = attrs.field(default="local")
51
+ package_name: Optional[str] = attrs.field(default=None)
52
+ package_version: Optional[str] = attrs.field(default=None)
53
+ origin_ref: Optional[str] = attrs.field(default=None)
54
+
55
+ def __attrs_post_init__(self):
56
+ self.tools = list(self.tools)
57
+
58
+ def load_tools(self) -> List[ResolvedTool]:
59
+ """
60
+ Resolve in-memory tools into runtime tool records.
61
+ """
62
+
63
+ resolved_tools: List[ResolvedTool] = []
64
+ for item in self.tools:
65
+ if isinstance(item, ResolvedTool):
66
+ resolved_tools.append(item)
67
+ continue
68
+
69
+ tool_spec = item if isinstance(item, ToolSpec) else tool_from_callable(item)
70
+ resolved_tools.append(
71
+ ResolvedTool(
72
+ tool_spec=tool_spec,
73
+ func=tool_spec.func,
74
+ source_type="memory",
75
+ location_type=self.location_type,
76
+ package_name=self.package_name,
77
+ package_version=self.package_version,
78
+ module_name=getattr(tool_spec.func, "__module__", None),
79
+ origin_ref=self.origin_ref,
80
+ )
81
+ )
82
+
83
+ return resolved_tools
84
+
85
+
86
+ @attrsx.define
87
+ class PythonModuleToolSource:
88
+ """
89
+ Resolve tools from an importable Python module.
90
+ """
91
+
92
+ module_name: str = attrs.field()
93
+ include_plain_typed: bool = attrs.field(default=False)
94
+ location_type: str = attrs.field(default="local")
95
+ package_name: Optional[str] = attrs.field(default=None)
96
+ package_version: Optional[str] = attrs.field(default=None)
97
+
98
+ def load_tools(self) -> List[ResolvedTool]:
99
+ """
100
+ Import module and resolve tools declared inside it.
101
+ """
102
+
103
+ resolved_tools: List[ResolvedTool] = []
104
+ for tool_spec in load_tools_from_module(
105
+ self.module_name,
106
+ include_plain_typed=self.include_plain_typed,
107
+ ):
108
+ resolved_tools.append(
109
+ ResolvedTool(
110
+ tool_spec=tool_spec,
111
+ func=tool_spec.func,
112
+ source_type="module",
113
+ location_type=self.location_type,
114
+ package_name=self.package_name,
115
+ package_version=self.package_version,
116
+ module_name=self.module_name,
117
+ origin_ref=self.module_name,
118
+ )
119
+ )
120
+
121
+ return resolved_tools
122
+
123
+
124
+ @attrsx.define
125
+ class PythonFileToolSource:
126
+ """
127
+ Resolve tools from a standalone Python file path.
128
+ """
129
+
130
+ file_path: str = attrs.field()
131
+ include_plain_typed: bool = attrs.field(default=False)
132
+ location_type: str = attrs.field(default="local")
133
+ package_name: Optional[str] = attrs.field(default=None)
134
+ package_version: Optional[str] = attrs.field(default=None)
135
+ module_name: Optional[str] = attrs.field(default=None)
136
+
137
+ def load_tools(self) -> List[ResolvedTool]:
138
+ """
139
+ Import file and resolve tools declared inside it.
140
+ """
141
+
142
+ resolved_path = str(Path(self.file_path).expanduser().resolve())
143
+ resolved_tools: List[ResolvedTool] = []
144
+ for tool_spec in load_tools_from_python_file(
145
+ resolved_path,
146
+ include_plain_typed=self.include_plain_typed,
147
+ module_name=self.module_name,
148
+ ):
149
+ resolved_tools.append(
150
+ ResolvedTool(
151
+ tool_spec=tool_spec,
152
+ func=tool_spec.func,
153
+ source_type="file",
154
+ location_type=self.location_type,
155
+ package_name=self.package_name,
156
+ package_version=self.package_version,
157
+ module_name=getattr(tool_spec.func, "__module__", self.module_name),
158
+ file_path=resolved_path,
159
+ origin_ref=resolved_path,
160
+ )
161
+ )
162
+
163
+ return resolved_tools
164
+
165
+
166
+ @attrsx.define
167
+ class ToolRegistry:
168
+ """
169
+ Aggregate tools from one or more sources and expose WAA-compatible tool lists.
170
+ """
171
+
172
+ sources: Sequence[object] = attrs.field()
173
+ _resolved_tools: Optional[List[ResolvedTool]] = attrs.field(default=None)
174
+
175
+ def __attrs_post_init__(self):
176
+ self.sources = list(self.sources)
177
+
178
+ def load_tools(self, reload: bool = False) -> List[ResolvedTool]:
179
+ """
180
+ Resolve all configured sources into runtime tools.
181
+ """
182
+
183
+ if self._resolved_tools is not None and not reload:
184
+ return list(self._resolved_tools)
185
+
186
+ resolved_tools: List[ResolvedTool] = []
187
+ seen_keys = set()
188
+
189
+ for source in self.sources:
190
+ for resolved_tool in source.load_tools():
191
+ dedupe_key = (
192
+ resolved_tool.tool_spec.name,
193
+ resolved_tool.module_name,
194
+ resolved_tool.file_path,
195
+ resolved_tool.package_name,
196
+ resolved_tool.package_version,
197
+ )
198
+ if dedupe_key in seen_keys:
199
+ continue
200
+ seen_keys.add(dedupe_key)
201
+ resolved_tools.append(resolved_tool)
202
+
203
+ self._resolved_tools = resolved_tools
204
+ return list(self._resolved_tools)
205
+
206
+ @staticmethod
207
+ def _safe_source_code(func: Callable[..., Any]) -> str:
208
+ """
209
+ Best-effort source capture for stable tool hashing.
210
+ """
211
+
212
+ try:
213
+ return inspect.getsource(func)
214
+ except (OSError, TypeError):
215
+ return getattr(func, "__qualname__", getattr(func, "__name__", ""))
216
+
217
+ def build_available_tools(self) -> Dict[str, Any]:
218
+ """
219
+ Convert resolved tools into WorkflowAutoAssembler-compatible structures.
220
+ """
221
+
222
+ available_functions: List[LlmFunctionItem] = []
223
+ available_callables: Dict[str, Callable[..., Any]] = {}
224
+
225
+ for resolved_tool in self.load_tools():
226
+ llm_func_item = {
227
+ "name": resolved_tool.tool_spec.name,
228
+ "description": resolved_tool.tool_spec.description,
229
+ "input_schema_json": resolved_tool.tool_spec.input_model.model_json_schema(),
230
+ "output_schema_json": resolved_tool.tool_spec.output_model.model_json_schema(),
231
+ "code": self._safe_source_code(resolved_tool.func),
232
+ }
233
+ func_id = make_uid(d=llm_func_item)
234
+
235
+ available_functions.append(
236
+ LlmFunctionItem(
237
+ func_id=func_id,
238
+ name=llm_func_item["name"],
239
+ description=llm_func_item["description"],
240
+ input_schema_json=llm_func_item["input_schema_json"],
241
+ output_schema_json=llm_func_item["output_schema_json"],
242
+ )
243
+ )
244
+ available_callables[func_id] = resolved_tool.func
245
+
246
+ return {
247
+ "available_functions": available_functions,
248
+ "available_callables": available_callables,
249
+ "resolved_tools": self.load_tools(),
250
+ }
@@ -0,0 +1,250 @@
1
+ """
2
+ `llm_function` helps you build reusable LLM functions with normal Python signatures.
3
+
4
+ You define a function with Pydantic input and output models, describe what it should do
5
+ in the docstring, and provide a set of available tools. At runtime, `llm_function`
6
+ uses [`workflow_auto_assembler`](https://pypi.org/project/workflow-auto-assembler/) to
7
+ assemble and execute a workflow that satisfies that typed function contract.
8
+
9
+ The result is an LLM-backed function that can be reused like any other Python function,
10
+ while still being grounded in explicit tools, schemas, and config.
11
+
12
+ Tool definition and discovery live in
13
+ [`llm_function_tools`](https://pypi.org/project/llm-function-tools/).
14
+ """
15
+
16
+ import asyncio
17
+ import attrs
18
+ import attrsx
19
+ import inspect
20
+ import threading
21
+ from functools import wraps
22
+ from typing import Any, Callable, Dict, List, Optional, Type, get_type_hints
23
+
24
+ from pydantic import BaseModel
25
+ from workflow_auto_assembler import WorkflowAutoAssembler
26
+ from .components.llm_func_deps.llm_function_config import LlmFunctionConfig, LlmRuntimeConfig
27
+ from .components.llm_func_deps.tool_registry import InMemoryToolSource, ToolRegistry
28
+
29
+ __package_metadata__ = {
30
+ "author": "Kyrylo Mordan",
31
+ "author_email": "parachute.repo@gmail.com",
32
+ "description": "Llm function is a decorator that uses llm to assemble reusable workflows from available tools to match input and output models.",
33
+ "url" : 'https://kiril-mordan.github.io/adaptive-reusables/llm-function/',
34
+ }
35
+
36
+
37
+ @attrsx.define
38
+ class LlmFunction:
39
+ available_functions: Optional[List[Any]] = attrs.field(default=None)
40
+ available_callables: Optional[Dict[str, Callable[..., Any]]] = attrs.field(default=None)
41
+ tool_registry: Optional[ToolRegistry] = attrs.field(default=None)
42
+ tool_sources: Optional[List[object]] = attrs.field(default=None)
43
+ config: Optional[LlmFunctionConfig] = attrs.field(default=None)
44
+ llm_handler_params: Optional[dict] = attrs.field(default=None)
45
+ storage_path: Optional[str] = attrs.field(default=None)
46
+ force_replan: bool = attrs.field(default=False)
47
+ max_retry: Optional[int] = attrs.field(default=None)
48
+ reset_loops: Optional[int] = attrs.field(default=None)
49
+ compare_params: Optional[dict] = attrs.field(default=None)
50
+ test_params: Optional[list] = attrs.field(default=None)
51
+ resolved_tools: Optional[Dict[str, Any]] = attrs.field(default=None, init=False)
52
+
53
+ def __attrs_post_init__(self):
54
+ self._apply_config_defaults()
55
+
56
+ if self.llm_handler_params is None:
57
+ raise ValueError("llm_handler_params is required either directly or via config.")
58
+
59
+ self.resolved_tools = self._resolve_available_tools()
60
+
61
+ def _apply_config_defaults(self):
62
+ if self.config is None:
63
+ return
64
+
65
+ if self.llm_handler_params is None:
66
+ self.llm_handler_params = self.config.runtime.llm_handler_params
67
+ if self.storage_path is None:
68
+ self.storage_path = self.config.runtime.storage_path
69
+ if self.force_replan is False:
70
+ self.force_replan = self.config.runtime.force_replan
71
+ if self.max_retry is None:
72
+ self.max_retry = self.config.runtime.max_retry
73
+ if self.reset_loops is None:
74
+ self.reset_loops = self.config.runtime.reset_loops
75
+ if self.compare_params is None:
76
+ self.compare_params = self.config.runtime.compare_params
77
+ if self.test_params is None:
78
+ self.test_params = self.config.runtime.test_params
79
+ if self.tool_registry is None:
80
+ self.tool_registry = self.config.tool_registry
81
+ if self.tool_sources is None and self.config.tool_sources is not None:
82
+ self.tool_sources = list(self.config.tool_sources)
83
+
84
+ def _normalize_task_description(self, func: Callable[..., Any]) -> str:
85
+ task_description = inspect.cleandoc(func.__doc__ or "").strip()
86
+ if not task_description:
87
+ raise ValueError("Decorated function must define a docstring to use as task_description.")
88
+
89
+ return task_description
90
+
91
+ def _extract_io_models(self, func: Callable[..., Any]) -> tuple[Type[BaseModel], Type[BaseModel], str]:
92
+ signature = inspect.signature(func)
93
+ params = list(signature.parameters.values())
94
+ if len(params) != 1:
95
+ raise ValueError("Decorated function must accept exactly one parameter annotated with a BaseModel subclass.")
96
+
97
+ input_param = params[0]
98
+ hints = get_type_hints(func)
99
+ input_model = hints.get(input_param.name)
100
+ output_model = hints.get("return")
101
+
102
+ if not inspect.isclass(input_model) or not issubclass(input_model, BaseModel):
103
+ raise TypeError("Decorated function input annotation must be a Pydantic BaseModel subclass.")
104
+
105
+ if not inspect.isclass(output_model) or not issubclass(output_model, BaseModel):
106
+ raise TypeError("Decorated function return annotation must be a Pydantic BaseModel subclass.")
107
+
108
+ return input_model, output_model, input_param.name
109
+
110
+ def _coerce_run_inputs(self, input_model: Type[BaseModel], run_inputs: Any) -> BaseModel:
111
+ if isinstance(run_inputs, input_model):
112
+ return run_inputs
113
+
114
+ if hasattr(input_model, "model_validate"):
115
+ return input_model.model_validate(run_inputs)
116
+
117
+ return input_model.parse_obj(run_inputs)
118
+
119
+ def _run_coro_blocking(self, coro):
120
+ try:
121
+ asyncio.get_running_loop()
122
+ except RuntimeError:
123
+ return asyncio.run(coro)
124
+
125
+ result: Dict[str, Any] = {}
126
+ error: Dict[str, BaseException] = {}
127
+
128
+ def _thread_main():
129
+ try:
130
+ result["value"] = asyncio.run(coro)
131
+ except BaseException as exc: # pragma: no cover
132
+ error["value"] = exc
133
+
134
+ thread = threading.Thread(target=_thread_main, daemon=True)
135
+ thread.start()
136
+ thread.join()
137
+
138
+ if "value" in error:
139
+ raise error["value"]
140
+
141
+ return result["value"]
142
+
143
+ def _resolve_available_tools(self) -> Dict[str, Any]:
144
+ if self.tool_registry is not None and self.tool_sources is not None:
145
+ raise ValueError("Use either tool_registry or tool_sources, not both.")
146
+
147
+ if self.tool_registry is not None:
148
+ return self.tool_registry.build_available_tools()
149
+
150
+ if self.tool_sources is not None:
151
+ return ToolRegistry(sources=self.tool_sources).build_available_tools()
152
+
153
+ if self.available_functions is None or self.available_callables is None:
154
+ raise ValueError(
155
+ "Provide either available_functions and available_callables, or tool_registry/tool_sources."
156
+ )
157
+
158
+ return {
159
+ "available_functions": self.available_functions,
160
+ "available_callables": self.available_callables,
161
+ "resolved_tools": None,
162
+ }
163
+
164
+ def as_decorator(self):
165
+ def decorator(func: Callable[..., Any]):
166
+ signature = inspect.signature(func)
167
+ task_description = self._normalize_task_description(func)
168
+ input_model, output_model, input_name = self._extract_io_models(func)
169
+
170
+ async def _invoke_async(run_inputs: Any):
171
+ wa = WorkflowAutoAssembler(
172
+ available_functions=self.resolved_tools["available_functions"],
173
+ available_callables=self.resolved_tools["available_callables"],
174
+ storage_path=self.storage_path,
175
+ llm_handler_params=self.llm_handler_params,
176
+ )
177
+
178
+ return await wa.actualize_workflow(
179
+ task_description=task_description,
180
+ force_replan=self.force_replan,
181
+ run_inputs=self._coerce_run_inputs(input_model=input_model, run_inputs=run_inputs),
182
+ test_params=self.test_params,
183
+ compare_params=self.compare_params,
184
+ input_model=input_model,
185
+ output_model=output_model,
186
+ max_retry=self.max_retry,
187
+ reset_loops=self.reset_loops,
188
+ )
189
+
190
+ if inspect.iscoroutinefunction(func):
191
+
192
+ @wraps(func)
193
+ async def async_wrapper(*args, **kwargs):
194
+ bound = signature.bind(*args, **kwargs)
195
+ return await _invoke_async(bound.arguments[input_name])
196
+
197
+ async_wrapper.input_model = input_model
198
+ async_wrapper.output_model = output_model
199
+ async_wrapper.task_description = task_description
200
+ async_wrapper.ainvoke = _invoke_async
201
+ async_wrapper.resolved_tools = self.resolved_tools["resolved_tools"]
202
+ return async_wrapper
203
+
204
+ @wraps(func)
205
+ def sync_wrapper(*args, **kwargs):
206
+ bound = signature.bind(*args, **kwargs)
207
+ return self._run_coro_blocking(_invoke_async(bound.arguments[input_name]))
208
+
209
+ sync_wrapper.input_model = input_model
210
+ sync_wrapper.output_model = output_model
211
+ sync_wrapper.task_description = task_description
212
+ sync_wrapper.ainvoke = _invoke_async
213
+ sync_wrapper.resolved_tools = self.resolved_tools["resolved_tools"]
214
+ return sync_wrapper
215
+
216
+ return decorator
217
+
218
+
219
+ def llm_function(
220
+ *,
221
+ available_functions: Optional[List[Any]] = None,
222
+ available_callables: Optional[Dict[str, Callable[..., Any]]] = None,
223
+ tool_registry: Optional[ToolRegistry] = None,
224
+ tool_sources: Optional[List[object]] = None,
225
+ config: Optional[LlmFunctionConfig] = None,
226
+ llm_handler_params: Optional[dict] = None,
227
+ storage_path: Optional[str] = None,
228
+ force_replan: bool = False,
229
+ max_retry: Optional[int] = None,
230
+ reset_loops: Optional[int] = None,
231
+ compare_params: Optional[dict] = None,
232
+ test_params: Optional[list] = None,
233
+ ):
234
+ """
235
+ Decorate a typed function and route its calls through WorkflowAutoAssembler.
236
+ """
237
+ return LlmFunction(
238
+ available_functions=available_functions,
239
+ available_callables=available_callables,
240
+ tool_registry=tool_registry,
241
+ tool_sources=tool_sources,
242
+ config=config,
243
+ llm_handler_params=llm_handler_params,
244
+ storage_path=storage_path,
245
+ force_replan=force_replan,
246
+ max_retry=max_retry,
247
+ reset_loops=reset_loops,
248
+ compare_params=compare_params,
249
+ test_params=test_params,
250
+ ).as_decorator()
@@ -0,0 +1,5 @@
1
+ # Release notes
2
+
3
+ ### 0.0.1
4
+
5
+ - initial version of llm-function
@@ -0,0 +1 @@
1
+ checkpoint[make-package] version=0.0.1 ts=2026-03-29T15:54:16.335632+00:00
@@ -0,0 +1 @@
1
+ ref: refs/heads/master
@@ -0,0 +1,8 @@
1
+ [core]
2
+ repositoryformatversion = 0
3
+ filemode = true
4
+ bare = false
5
+ logallrefupdates = true
6
+ [user]
7
+ name = PAA Checkpoint
8
+ email = paa@local
@@ -0,0 +1 @@
1
+ Unnamed repository; edit this file 'description' to name the repository.
@@ -0,0 +1,15 @@
1
+ #!/bin/sh
2
+ #
3
+ # An example hook script to check the commit log message taken by
4
+ # applypatch from an e-mail message.
5
+ #
6
+ # The hook should exit with non-zero status after issuing an
7
+ # appropriate message if it wants to stop the commit. The hook is
8
+ # allowed to edit the commit message file.
9
+ #
10
+ # To enable this hook, rename this file to "applypatch-msg".
11
+
12
+ . git-sh-setup
13
+ commitmsg="$(git rev-parse --git-path hooks/commit-msg)"
14
+ test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"}
15
+ :
@@ -0,0 +1,24 @@
1
+ #!/bin/sh
2
+ #
3
+ # An example hook script to check the commit log message.
4
+ # Called by "git commit" with one argument, the name of the file
5
+ # that has the commit message. The hook should exit with non-zero
6
+ # status after issuing an appropriate message if it wants to stop the
7
+ # commit. The hook is allowed to edit the commit message file.
8
+ #
9
+ # To enable this hook, rename this file to "commit-msg".
10
+
11
+ # Uncomment the below to add a Signed-off-by line to the message.
12
+ # Doing this in a hook is a bad idea in general, but the prepare-commit-msg
13
+ # hook is more suited to it.
14
+ #
15
+ # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
16
+ # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
17
+
18
+ # This example catches duplicate Signed-off-by lines.
19
+
20
+ test "" = "$(grep '^Signed-off-by: ' "$1" |
21
+ sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
22
+ echo >&2 Duplicate Signed-off-by lines.
23
+ exit 1
24
+ }