llmio 0.0.1__tar.gz
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.
- llmio-0.0.1/.gitignore +130 -0
- llmio-0.0.1/LICENSE +21 -0
- llmio-0.0.1/PKG-INFO +73 -0
- llmio-0.0.1/README.md +53 -0
- llmio-0.0.1/llmio/__init__.py +6 -0
- llmio-0.0.1/llmio/assistant.py +262 -0
- llmio-0.0.1/llmio/model.py +31 -0
- llmio-0.0.1/llmio/prompts/__init__.py +82 -0
- llmio-0.0.1/pyproject.toml +38 -0
llmio-0.0.1/.gitignore
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
pip-wheel-metadata/
|
|
24
|
+
share/python-wheels/
|
|
25
|
+
*.egg-info/
|
|
26
|
+
.installed.cfg
|
|
27
|
+
*.egg
|
|
28
|
+
MANIFEST
|
|
29
|
+
|
|
30
|
+
# PyInstaller
|
|
31
|
+
# Usually these files are written by a python script from a template
|
|
32
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
33
|
+
*.manifest
|
|
34
|
+
*.spec
|
|
35
|
+
|
|
36
|
+
# Installer logs
|
|
37
|
+
pip-log.txt
|
|
38
|
+
pip-delete-this-directory.txt
|
|
39
|
+
|
|
40
|
+
# Unit test / coverage reports
|
|
41
|
+
htmlcov/
|
|
42
|
+
.tox/
|
|
43
|
+
.nox/
|
|
44
|
+
.coverage
|
|
45
|
+
.coverage.*
|
|
46
|
+
.cache
|
|
47
|
+
nosetests.xml
|
|
48
|
+
coverage.xml
|
|
49
|
+
*.cover
|
|
50
|
+
*.py,cover
|
|
51
|
+
.hypothesis/
|
|
52
|
+
.pytest_cache/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
target/
|
|
76
|
+
|
|
77
|
+
# Jupyter Notebook
|
|
78
|
+
.ipynb_checkpoints
|
|
79
|
+
|
|
80
|
+
# IPython
|
|
81
|
+
profile_default/
|
|
82
|
+
ipython_config.py
|
|
83
|
+
|
|
84
|
+
# pyenv
|
|
85
|
+
.python-version
|
|
86
|
+
|
|
87
|
+
# pipenv
|
|
88
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
89
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
90
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
91
|
+
# install all needed dependencies.
|
|
92
|
+
#Pipfile.lock
|
|
93
|
+
|
|
94
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
|
95
|
+
__pypackages__/
|
|
96
|
+
|
|
97
|
+
# Celery stuff
|
|
98
|
+
celerybeat-schedule
|
|
99
|
+
celerybeat.pid
|
|
100
|
+
|
|
101
|
+
# SageMath parsed files
|
|
102
|
+
*.sage.py
|
|
103
|
+
|
|
104
|
+
# Environments
|
|
105
|
+
.env
|
|
106
|
+
.venv
|
|
107
|
+
env/
|
|
108
|
+
venv/
|
|
109
|
+
ENV/
|
|
110
|
+
env.bak/
|
|
111
|
+
venv.bak/
|
|
112
|
+
|
|
113
|
+
# Spyder project settings
|
|
114
|
+
.spyderproject
|
|
115
|
+
.spyproject
|
|
116
|
+
|
|
117
|
+
# Rope project settings
|
|
118
|
+
.ropeproject
|
|
119
|
+
|
|
120
|
+
# mkdocs documentation
|
|
121
|
+
/site
|
|
122
|
+
|
|
123
|
+
# mypy
|
|
124
|
+
.mypy_cache/
|
|
125
|
+
.dmypy.json
|
|
126
|
+
dmypy.json
|
|
127
|
+
|
|
128
|
+
# Pyre type checker
|
|
129
|
+
.pyre/
|
|
130
|
+
.DS_Store
|
llmio-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 badgeir
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
llmio-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: llmio
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Easily connect large language models into your application
|
|
5
|
+
Project-URL: Homepage, https://github.com/badgeir/llmio
|
|
6
|
+
Project-URL: Bug Tracker, https://github.com/badgeir/llmio/issues
|
|
7
|
+
Author-email: Peter Leupi <pleupi123@gmail.com>
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
|
+
Requires-Dist: jinja2
|
|
14
|
+
Requires-Dist: openai
|
|
15
|
+
Requires-Dist: polyfactory
|
|
16
|
+
Requires-Dist: pydantic
|
|
17
|
+
Provides-Extra: examples
|
|
18
|
+
Requires-Dist: bs4; extra == 'examples'
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# llmio
|
|
22
|
+
## Easily connect large language models into your application.
|
|
23
|
+
|
|
24
|
+

|
|
25
|
+

|
|
26
|
+

|
|
27
|
+

|
|
28
|
+
|
|
29
|
+
# Setup
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
pip install llmio
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
# Example
|
|
36
|
+
|
|
37
|
+
``` python
|
|
38
|
+
import llmio
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
assistant = llmio.Assistant(
|
|
42
|
+
description="""
|
|
43
|
+
You are a calculator.
|
|
44
|
+
Always use the provided commands to perform calculations,
|
|
45
|
+
never try to calculate on your own.
|
|
46
|
+
When being given a math problem, do not explain the steps,
|
|
47
|
+
only execute them necessary commands and then present the answer.
|
|
48
|
+
""",
|
|
49
|
+
key="<openai-key>",
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@assistant.command
|
|
54
|
+
def add(num1: float, num2: float) -> float:
|
|
55
|
+
"""
|
|
56
|
+
Add two numbers
|
|
57
|
+
"""
|
|
58
|
+
print(f"Adding {num1} + {num2}")
|
|
59
|
+
return num1 + num2
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@assistant.command
|
|
63
|
+
def multiply(num1: float, num2: float) -> float:
|
|
64
|
+
"""
|
|
65
|
+
Multiply two numbers
|
|
66
|
+
"""
|
|
67
|
+
print(f"Multiplying {num1} * {num2}")
|
|
68
|
+
return num1 * num2
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
reply, _ = assistant.speak("calculate the answer of (10 + 20) * 1337")
|
|
72
|
+
print(reply)
|
|
73
|
+
```
|
llmio-0.0.1/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# llmio
|
|
2
|
+
## Easily connect large language models into your application.
|
|
3
|
+
|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
# Setup
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
pip install llmio
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
# Example
|
|
16
|
+
|
|
17
|
+
``` python
|
|
18
|
+
import llmio
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
assistant = llmio.Assistant(
|
|
22
|
+
description="""
|
|
23
|
+
You are a calculator.
|
|
24
|
+
Always use the provided commands to perform calculations,
|
|
25
|
+
never try to calculate on your own.
|
|
26
|
+
When being given a math problem, do not explain the steps,
|
|
27
|
+
only execute them necessary commands and then present the answer.
|
|
28
|
+
""",
|
|
29
|
+
key="<openai-key>",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@assistant.command
|
|
34
|
+
def add(num1: float, num2: float) -> float:
|
|
35
|
+
"""
|
|
36
|
+
Add two numbers
|
|
37
|
+
"""
|
|
38
|
+
print(f"Adding {num1} + {num2}")
|
|
39
|
+
return num1 + num2
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@assistant.command
|
|
43
|
+
def multiply(num1: float, num2: float) -> float:
|
|
44
|
+
"""
|
|
45
|
+
Multiply two numbers
|
|
46
|
+
"""
|
|
47
|
+
print(f"Multiplying {num1} * {num2}")
|
|
48
|
+
return num1 * num2
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
reply, _ = assistant.speak("calculate the answer of (10 + 20) * 1337")
|
|
52
|
+
print(reply)
|
|
53
|
+
```
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
from typing import Literal, Optional, Callable, Type, Any
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
import textwrap
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from inspect import isclass, signature
|
|
6
|
+
|
|
7
|
+
import jinja2
|
|
8
|
+
import pydantic
|
|
9
|
+
import openai
|
|
10
|
+
from polyfactory.factories.pydantic_factory import ModelFactory
|
|
11
|
+
|
|
12
|
+
from llmio import model, prompts
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
ENGINES = {
|
|
16
|
+
"gpt-3.5-turbo",
|
|
17
|
+
"gpt-4",
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class Command:
|
|
23
|
+
function: Callable
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def name(self):
|
|
27
|
+
return self.function.__name__
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def is_pydantic_input(self):
|
|
31
|
+
return bool(
|
|
32
|
+
len(self.input_annotations) == 1
|
|
33
|
+
and issubclass(list(self.input_annotations.values())[0], pydantic.BaseModel)
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def is_pydantic_output(self):
|
|
38
|
+
annotation = self.function.__annotations__["return"]
|
|
39
|
+
if isclass(annotation) and issubclass(annotation, pydantic.BaseModel):
|
|
40
|
+
return True
|
|
41
|
+
return False
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def input_annotations(self):
|
|
45
|
+
return {
|
|
46
|
+
name: annotation
|
|
47
|
+
for name, annotation in self.function.__annotations__.items()
|
|
48
|
+
if name not in {"return", "state"}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def params(self) -> pydantic.BaseModel:
|
|
53
|
+
if self.is_pydantic_input:
|
|
54
|
+
return list(self.input_annotations.values())[0]
|
|
55
|
+
|
|
56
|
+
return model.model_from_function(self.function)
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def returns(self) -> Type[pydantic.BaseModel]:
|
|
60
|
+
annotation = self.function.__annotations__["return"]
|
|
61
|
+
if isclass(annotation) and issubclass(annotation, pydantic.BaseModel):
|
|
62
|
+
return annotation
|
|
63
|
+
|
|
64
|
+
return pydantic.create_model("Result", result=(annotation, ...))
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def description(self):
|
|
68
|
+
if self.function.__doc__ is None:
|
|
69
|
+
return ""
|
|
70
|
+
return textwrap.dedent(self.function.__doc__).strip()
|
|
71
|
+
|
|
72
|
+
def explain(self):
|
|
73
|
+
return jinja2.Template(
|
|
74
|
+
prompts.DEFAULT_COMMAND_PROMPT,
|
|
75
|
+
trim_blocks=True,
|
|
76
|
+
lstrip_blocks=True,
|
|
77
|
+
).render(
|
|
78
|
+
name=self.name,
|
|
79
|
+
description=self.description,
|
|
80
|
+
params=[
|
|
81
|
+
(
|
|
82
|
+
key,
|
|
83
|
+
value["type"],
|
|
84
|
+
value.get("description", "-"),
|
|
85
|
+
key in self.params.schema()["required"],
|
|
86
|
+
)
|
|
87
|
+
for key, value in self.params.schema()["properties"].items()
|
|
88
|
+
],
|
|
89
|
+
returns=[
|
|
90
|
+
(key, value["type"], value.get("description", "-"))
|
|
91
|
+
for key, value in self.returns.schema()["properties"].items()
|
|
92
|
+
],
|
|
93
|
+
mock_data=self.mock_data().json(),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def command_model(self) -> Any:
|
|
97
|
+
return pydantic.create_model(
|
|
98
|
+
"Model", command=(Literal[self.name], ...), params=(self.params, ...)
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
def mock_data(self):
|
|
102
|
+
class Mocker(ModelFactory):
|
|
103
|
+
__model__ = self.command_model()
|
|
104
|
+
|
|
105
|
+
return Mocker.build()
|
|
106
|
+
|
|
107
|
+
def execute(self, params, state=None):
|
|
108
|
+
kwargs = {}
|
|
109
|
+
if "state" in signature(self.function).parameters:
|
|
110
|
+
kwargs["state"] = state
|
|
111
|
+
|
|
112
|
+
if self.is_pydantic_input:
|
|
113
|
+
result = self.function(params, **kwargs)
|
|
114
|
+
else:
|
|
115
|
+
result = self.function(**params.dict(), **kwargs)
|
|
116
|
+
|
|
117
|
+
if self.is_pydantic_output:
|
|
118
|
+
return result
|
|
119
|
+
return self.returns(result=result)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class Assistant:
|
|
123
|
+
def __init__(
|
|
124
|
+
self,
|
|
125
|
+
key: str,
|
|
126
|
+
description: str,
|
|
127
|
+
engine: str = "gpt-4",
|
|
128
|
+
command_header: Optional[str] = None,
|
|
129
|
+
):
|
|
130
|
+
openai.api_key = key
|
|
131
|
+
|
|
132
|
+
if engine not in ENGINES:
|
|
133
|
+
raise ValueError(f"Unknown engine {engine}")
|
|
134
|
+
|
|
135
|
+
self.engine = engine
|
|
136
|
+
self.description = description
|
|
137
|
+
self.commands: list[Command] = []
|
|
138
|
+
|
|
139
|
+
if command_header is None:
|
|
140
|
+
self.command_header = prompts.DEFAULT_COMMAND_HEADER
|
|
141
|
+
else:
|
|
142
|
+
self.command_header = command_header
|
|
143
|
+
|
|
144
|
+
self._prompt_inspectors: list[Callable] = []
|
|
145
|
+
self._output_inspectors: list[Callable] = []
|
|
146
|
+
|
|
147
|
+
def system_prompt(self) -> str:
|
|
148
|
+
return jinja2.Template(
|
|
149
|
+
prompts.DEFAULT_SYSTEM_PROMPT,
|
|
150
|
+
trim_blocks=True,
|
|
151
|
+
lstrip_blocks=True,
|
|
152
|
+
).render(
|
|
153
|
+
description=self.description,
|
|
154
|
+
command_header=self.command_header,
|
|
155
|
+
commands=self.commands,
|
|
156
|
+
current_time=datetime.now().isoformat(),
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
def _get_system_prompt(self) -> dict[str, str]:
|
|
160
|
+
return {"role": "system", "content": self.system_prompt()}
|
|
161
|
+
|
|
162
|
+
def create_prompt(
|
|
163
|
+
self, message_history: list[dict[str, str]]
|
|
164
|
+
) -> list[dict[str, str]]:
|
|
165
|
+
return [
|
|
166
|
+
self._get_system_prompt(),
|
|
167
|
+
*message_history,
|
|
168
|
+
]
|
|
169
|
+
|
|
170
|
+
def command(self, function: Callable):
|
|
171
|
+
input_annotations = {
|
|
172
|
+
name: annotation
|
|
173
|
+
for name, annotation in function.__annotations__.items()
|
|
174
|
+
if name not in {"return", "state"}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
assert (
|
|
178
|
+
len(input_annotations) == 1
|
|
179
|
+
and issubclass(list(input_annotations.values())[0], pydantic.BaseModel)
|
|
180
|
+
) or (
|
|
181
|
+
not any(
|
|
182
|
+
issubclass(annotation, pydantic.BaseModel)
|
|
183
|
+
for annotation in input_annotations.values()
|
|
184
|
+
)
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
assert "return" in function.__annotations__
|
|
188
|
+
|
|
189
|
+
self.commands.append(
|
|
190
|
+
Command(function=function),
|
|
191
|
+
)
|
|
192
|
+
return function
|
|
193
|
+
|
|
194
|
+
def inspect_prompt(self, function: Callable):
|
|
195
|
+
self._prompt_inspectors.append(function)
|
|
196
|
+
return function
|
|
197
|
+
|
|
198
|
+
def inspect_output(self, function: Callable):
|
|
199
|
+
self._output_inspectors.append(function)
|
|
200
|
+
return function
|
|
201
|
+
|
|
202
|
+
def _run_prompt_inspectors(self, prompt: list[dict[str, str]], state) -> None:
|
|
203
|
+
for inspector in self._prompt_inspectors:
|
|
204
|
+
kwargs = {}
|
|
205
|
+
if "state" in signature(inspector).parameters:
|
|
206
|
+
kwargs["state"] = state
|
|
207
|
+
inspector(prompt, **kwargs)
|
|
208
|
+
|
|
209
|
+
def _run_content_inspectors(self, content: str, state) -> None:
|
|
210
|
+
for inspector in self._output_inspectors:
|
|
211
|
+
kwargs = {}
|
|
212
|
+
if "state" in signature(inspector).parameters:
|
|
213
|
+
kwargs["state"] = state
|
|
214
|
+
inspector(content, **kwargs)
|
|
215
|
+
|
|
216
|
+
def speak(
|
|
217
|
+
self,
|
|
218
|
+
message: str,
|
|
219
|
+
history: Optional[list[dict[str, str]]] = None,
|
|
220
|
+
state=None,
|
|
221
|
+
role="user",
|
|
222
|
+
) -> tuple[str, list[dict[str, str]]]:
|
|
223
|
+
if history is None:
|
|
224
|
+
history = []
|
|
225
|
+
history = history[:]
|
|
226
|
+
history.append(
|
|
227
|
+
{
|
|
228
|
+
"role": role,
|
|
229
|
+
"content": message,
|
|
230
|
+
}
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
prompt = self.create_prompt(history)
|
|
234
|
+
self._run_prompt_inspectors(prompt, state)
|
|
235
|
+
|
|
236
|
+
result = openai.ChatCompletion.create(
|
|
237
|
+
model=self.engine,
|
|
238
|
+
messages=prompt,
|
|
239
|
+
)
|
|
240
|
+
content = result["choices"][0]["message"]["content"]
|
|
241
|
+
self._run_content_inspectors(content, state)
|
|
242
|
+
|
|
243
|
+
history.append(
|
|
244
|
+
{
|
|
245
|
+
"role": "assistant",
|
|
246
|
+
"content": content,
|
|
247
|
+
}
|
|
248
|
+
)
|
|
249
|
+
for command in self.commands:
|
|
250
|
+
cmd_model = command.command_model()
|
|
251
|
+
try:
|
|
252
|
+
inputs = cmd_model.parse_raw(content)
|
|
253
|
+
except pydantic.ValidationError:
|
|
254
|
+
continue
|
|
255
|
+
result = command.execute(inputs.params, state=state)
|
|
256
|
+
return self.speak(
|
|
257
|
+
result.json(),
|
|
258
|
+
history=history,
|
|
259
|
+
role="system",
|
|
260
|
+
state=state,
|
|
261
|
+
)
|
|
262
|
+
return content, history
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from typing import Any, Callable, Dict, Mapping, Tuple
|
|
2
|
+
from inspect import Parameter, signature
|
|
3
|
+
|
|
4
|
+
from pydantic import create_model
|
|
5
|
+
from pydantic.typing import get_all_type_hints
|
|
6
|
+
from pydantic.utils import to_camel
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def model_from_function(function: Callable):
|
|
10
|
+
parameters: Mapping[str, Parameter] = signature(function).parameters
|
|
11
|
+
|
|
12
|
+
type_hints = get_all_type_hints(function)
|
|
13
|
+
fields: Dict[str, Tuple[Any, Any]] = {}
|
|
14
|
+
for name, param in parameters.items():
|
|
15
|
+
if name == "state":
|
|
16
|
+
continue
|
|
17
|
+
|
|
18
|
+
if param.annotation is param.empty:
|
|
19
|
+
annotation = Any
|
|
20
|
+
else:
|
|
21
|
+
annotation = type_hints[name]
|
|
22
|
+
|
|
23
|
+
default = ... if param.default is param.empty else param.default
|
|
24
|
+
if param.kind == Parameter.POSITIONAL_OR_KEYWORD:
|
|
25
|
+
fields[name] = annotation, default
|
|
26
|
+
else:
|
|
27
|
+
raise ValueError(
|
|
28
|
+
"Unable to parse function signature. Only named arguments supported."
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
return create_model(to_camel(function.__name__), **fields) # type: ignore
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import textwrap
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
DEFAULT_COMMAND_HEADER = (
|
|
5
|
+
textwrap.dedent(
|
|
6
|
+
"""
|
|
7
|
+
The following commands can be used.
|
|
8
|
+
If you intend to execute a command, only write a valid command and nothing else.
|
|
9
|
+
Do not try to both speak and execute a command at the same time,
|
|
10
|
+
as it will not be accepted as a command.
|
|
11
|
+
Also do not try to execute multiple commands at once.
|
|
12
|
+
You can chain commands, but if so, only execute one command at a time,
|
|
13
|
+
and then execute the next commands afterward.
|
|
14
|
+
Every time a command is executed, the results will be shown as a system message,
|
|
15
|
+
and you then get to either execute a new command or output a normal message
|
|
16
|
+
intended to the user.
|
|
17
|
+
Every time you return a normal text, this will stop the command iteration,
|
|
18
|
+
and the text will be shown to the user. Because of this, do not hint that
|
|
19
|
+
you will execute a command by saying something like "Ok, I will now do X".
|
|
20
|
+
Instead, first execute the command, and then write a normal message to the user.
|
|
21
|
+
Do not talk explicitly about the commands to the user,
|
|
22
|
+
these are hidden and only serve as your interface to the application backend.
|
|
23
|
+
|
|
24
|
+
Remember to always EITHER output a command (in json), or a normal message,
|
|
25
|
+
never both at the same time.
|
|
26
|
+
"""
|
|
27
|
+
)
|
|
28
|
+
.strip()
|
|
29
|
+
.replace("\n", " ")
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
DEFAULT_COMMAND_PROMPT = textwrap.dedent(
|
|
34
|
+
"""
|
|
35
|
+
Command: {{name}}
|
|
36
|
+
{% if description %}
|
|
37
|
+
Description:
|
|
38
|
+
{{description}}
|
|
39
|
+
{% endif %}
|
|
40
|
+
{% if params %}
|
|
41
|
+
|
|
42
|
+
Parameters:
|
|
43
|
+
| Name | Type | Description | Required |
|
|
44
|
+
| ---- | ---- | ----------- | -------- |
|
|
45
|
+
{% for param_name, param_type, param_desc, param_required in params %}
|
|
46
|
+
| {{param_name}} | {{param_type}} | {{param_desc}} | {{param_required}} |
|
|
47
|
+
{% endfor %}
|
|
48
|
+
{% endif %}
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
| Name | Type | Description |
|
|
52
|
+
| ---- | ---- | ----------- |
|
|
53
|
+
{% for res_name, res_type, res_desc in returns %}
|
|
54
|
+
| {{res_name}} | {{res_type}} | {{res_desc}} |
|
|
55
|
+
{% endfor %}
|
|
56
|
+
|
|
57
|
+
Example usage:
|
|
58
|
+
{{mock_data}}
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
"""
|
|
62
|
+
).strip()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
DEFAULT_SYSTEM_PROMPT = textwrap.dedent(
|
|
66
|
+
"""
|
|
67
|
+
{{description}}
|
|
68
|
+
|
|
69
|
+
{{command_header}}
|
|
70
|
+
|
|
71
|
+
{% for command in commands %}
|
|
72
|
+
{{command.explain()}}
|
|
73
|
+
{% endfor %} \
|
|
74
|
+
|
|
75
|
+
System parameters:
|
|
76
|
+
The current time is {{current_time}}
|
|
77
|
+
|
|
78
|
+
You are limited to only answer question regarding the scope described above
|
|
79
|
+
and the available commands defined below.
|
|
80
|
+
For all other questions, politely decline to answer.
|
|
81
|
+
"""
|
|
82
|
+
).strip()
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
[project]
|
|
7
|
+
name = "llmio"
|
|
8
|
+
version = "0.0.1"
|
|
9
|
+
authors = [
|
|
10
|
+
{ name="Peter Leupi", email="pleupi123@gmail.com" },
|
|
11
|
+
]
|
|
12
|
+
description = "Easily connect large language models into your application"
|
|
13
|
+
readme = "README.md"
|
|
14
|
+
requires-python = ">=3.10"
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
]
|
|
20
|
+
dependencies = [
|
|
21
|
+
"openai",
|
|
22
|
+
"pydantic",
|
|
23
|
+
"jinja2",
|
|
24
|
+
"polyfactory",
|
|
25
|
+
]
|
|
26
|
+
[project.optional-dependencies]
|
|
27
|
+
examples = [
|
|
28
|
+
"bs4",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[tool.hatch.build]
|
|
32
|
+
include = [
|
|
33
|
+
"llmio",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
"Homepage" = "https://github.com/badgeir/llmio"
|
|
38
|
+
"Bug Tracker" = "https://github.com/badgeir/llmio/issues"
|