trulens-apps-nemo 1.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.
- trulens_apps_nemo-1.0.1/PKG-INFO +26 -0
- trulens_apps_nemo-1.0.1/README.md +1 -0
- trulens_apps_nemo-1.0.1/pyproject.toml +38 -0
- trulens_apps_nemo-1.0.1/trulens/apps/nemo/__init__.py +21 -0
- trulens_apps_nemo-1.0.1/trulens/apps/nemo/py.typed +0 -0
- trulens_apps_nemo-1.0.1/trulens/apps/nemo/tru_rails.py +448 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: trulens-apps-nemo
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: Library to systematically track and evaluate LLM based applications.
|
|
5
|
+
Home-page: https://trulens.org/
|
|
6
|
+
License: MIT
|
|
7
|
+
Author: Snowflake Inc.
|
|
8
|
+
Author-email: ml-observability-wg-dl@snowflake.com
|
|
9
|
+
Requires-Python: >=3.9,<3.12
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Requires-Dist: nemoguardrails (>=0.9,<0.10)
|
|
18
|
+
Requires-Dist: pydantic (>=2,<3)
|
|
19
|
+
Requires-Dist: trulens-apps-langchain (>=1.0.0,<2.0.0) ; python_version < "3.13"
|
|
20
|
+
Requires-Dist: trulens-core (>=1.0.0,<2.0.0)
|
|
21
|
+
Project-URL: Documentation, https://trulens.org/trulens/getting_started/
|
|
22
|
+
Project-URL: Repository, https://github.com/truera/trulens
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# trulens-apps-nemo
|
|
26
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# trulens-apps-nemo
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
build-backend = "poetry.core.masonry.api"
|
|
3
|
+
requires = [
|
|
4
|
+
"poetry-core",
|
|
5
|
+
]
|
|
6
|
+
|
|
7
|
+
[tool.poetry]
|
|
8
|
+
name = "trulens-apps-nemo"
|
|
9
|
+
version = "1.0.1"
|
|
10
|
+
description = "Library to systematically track and evaluate LLM based applications."
|
|
11
|
+
authors = [
|
|
12
|
+
"Snowflake Inc. <ml-observability-wg-dl@snowflake.com>",
|
|
13
|
+
]
|
|
14
|
+
license = "MIT"
|
|
15
|
+
readme = "README.md"
|
|
16
|
+
packages = [
|
|
17
|
+
{ include = "trulens" },
|
|
18
|
+
]
|
|
19
|
+
homepage = "https://trulens.org/"
|
|
20
|
+
documentation = "https://trulens.org/trulens/getting_started/"
|
|
21
|
+
repository = "https://github.com/truera/trulens"
|
|
22
|
+
classifiers = [
|
|
23
|
+
"Programming Language :: Python :: 3",
|
|
24
|
+
"Operating System :: OS Independent",
|
|
25
|
+
"Development Status :: 3 - Alpha",
|
|
26
|
+
"License :: OSI Approved :: MIT License",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[tool.poetry.dependencies]
|
|
30
|
+
python = ">=3.9,<3.12"
|
|
31
|
+
trulens-core = { version = "^1.0.0" }
|
|
32
|
+
trulens-apps-langchain = { version = "^1.0.0", python = "<3.13" }
|
|
33
|
+
pydantic = "^2"
|
|
34
|
+
nemoguardrails = "^0.9"
|
|
35
|
+
|
|
36
|
+
[tool.poetry.group.dev.dependencies]
|
|
37
|
+
trulens-core = { path = "../../core" }
|
|
38
|
+
trulens-apps-langchain = { path = "../langchain", python = "<3.13" }
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
!!! note "Additional Dependency Required"
|
|
3
|
+
|
|
4
|
+
To use this module, you must have the `trulens-apps-nemo` package installed.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
pip install trulens-apps-nemo
|
|
8
|
+
```
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from importlib.metadata import version
|
|
12
|
+
|
|
13
|
+
from trulens.apps.nemo.tru_rails import RailsActionSelect
|
|
14
|
+
from trulens.apps.nemo.tru_rails import RailsInstrument
|
|
15
|
+
from trulens.apps.nemo.tru_rails import TruRails
|
|
16
|
+
from trulens.core.utils.imports import safe_importlib_package_name
|
|
17
|
+
|
|
18
|
+
__version__ = version(safe_importlib_package_name(__package__ or __name__))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
__all__ = ["TruRails", "RailsInstrument", "RailsActionSelect"]
|
|
File without changes
|
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
"""
|
|
2
|
+
NeMo Guardrails instrumentation and monitoring.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import inspect
|
|
6
|
+
from inspect import BoundArguments
|
|
7
|
+
from inspect import Signature
|
|
8
|
+
import logging
|
|
9
|
+
from pprint import pformat
|
|
10
|
+
from typing import Any, Callable, ClassVar, Dict, List, Optional, Tuple, Union
|
|
11
|
+
|
|
12
|
+
from langchain_core.language_models.base import BaseLanguageModel
|
|
13
|
+
from nemoguardrails import LLMRails
|
|
14
|
+
from nemoguardrails import RailsConfig
|
|
15
|
+
from nemoguardrails.actions.action_dispatcher import ActionDispatcher
|
|
16
|
+
from nemoguardrails.actions.actions import ActionResult
|
|
17
|
+
from nemoguardrails.actions.actions import action
|
|
18
|
+
from nemoguardrails.actions.llm.generation import LLMGenerationActions
|
|
19
|
+
from nemoguardrails.kb.kb import KnowledgeBase
|
|
20
|
+
from pydantic import Field
|
|
21
|
+
from trulens.apps.langchain import LangChainInstrument
|
|
22
|
+
from trulens.core import app as mod_app
|
|
23
|
+
from trulens.core import feedback
|
|
24
|
+
from trulens.core.instruments import ClassFilter
|
|
25
|
+
from trulens.core.instruments import Instrument
|
|
26
|
+
from trulens.core.schema import Select
|
|
27
|
+
from trulens.core.utils.containers import dict_set_with_multikey
|
|
28
|
+
from trulens.core.utils.json import jsonify
|
|
29
|
+
from trulens.core.utils.pyschema import Class
|
|
30
|
+
from trulens.core.utils.pyschema import FunctionOrMethod
|
|
31
|
+
from trulens.core.utils.python import safe_hasattr
|
|
32
|
+
from trulens.core.utils.serial import JSON
|
|
33
|
+
from trulens.core.utils.serial import Lens
|
|
34
|
+
from trulens.core.utils.text import retab
|
|
35
|
+
|
|
36
|
+
logger = logging.getLogger(__name__)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class RailsActionSelect(Select):
|
|
40
|
+
"""Selector shorthands for _NeMo Guardrails_ apps when used for evaluating
|
|
41
|
+
feedback in actions.
|
|
42
|
+
|
|
43
|
+
These should not be used for feedback functions given to `TruRails` but
|
|
44
|
+
instead for selectors in the `FeedbackActions` action invoked from with a
|
|
45
|
+
rails app.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
Action = Lens().action
|
|
49
|
+
"""Selector for action call parameters."""
|
|
50
|
+
|
|
51
|
+
Events = Action.events
|
|
52
|
+
"""Selector for events in action call parameters."""
|
|
53
|
+
|
|
54
|
+
Context = Action.context
|
|
55
|
+
"""Selector for context in action call parameters.
|
|
56
|
+
|
|
57
|
+
Warning:
|
|
58
|
+
This is not the same "context" as in RAG triad. This is a parameter to rails
|
|
59
|
+
actions that stores context of the rails app execution.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
LLM = Action.llm
|
|
63
|
+
"""Selector for the language model in action call parameters."""
|
|
64
|
+
|
|
65
|
+
Config = Action.config
|
|
66
|
+
"""Selector for the configuration in action call parameters."""
|
|
67
|
+
|
|
68
|
+
RetrievalContexts = Context.relevant_chunks_sep
|
|
69
|
+
"""Selector for the retrieved contexts chunks returned from a KB search.
|
|
70
|
+
|
|
71
|
+
Equivalent to `$relevant_chunks_sep` in colang."""
|
|
72
|
+
|
|
73
|
+
UserMessage = Context.user_message
|
|
74
|
+
"""Selector for the user message.
|
|
75
|
+
|
|
76
|
+
Equivalent to `$user_message` in colang."""
|
|
77
|
+
|
|
78
|
+
BotMessage = Context.bot_message
|
|
79
|
+
"""Selector for the bot message.
|
|
80
|
+
|
|
81
|
+
Equivalent to `$bot_message` in colang."""
|
|
82
|
+
|
|
83
|
+
LastUserMessage = Context.last_user_message
|
|
84
|
+
"""Selector for the last user message.
|
|
85
|
+
|
|
86
|
+
Equivalent to `$last_user_message` in colang."""
|
|
87
|
+
|
|
88
|
+
LastBotMessage = Context.last_bot_message
|
|
89
|
+
"""Selector for the last bot message.
|
|
90
|
+
|
|
91
|
+
Equivalent to `$last_bot_message` in colang."""
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# NOTE(piotrm): Cannot have this inside FeedbackActions presently due to perhaps
|
|
95
|
+
# some closure-related issues with the @action decorator below.
|
|
96
|
+
registered_feedback_functions = {}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class FeedbackActions:
|
|
100
|
+
"""Feedback action action for _NeMo Guardrails_ apps.
|
|
101
|
+
|
|
102
|
+
See docstring of method `feedback`.
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
@staticmethod
|
|
106
|
+
def register_feedback_functions(
|
|
107
|
+
*args: Tuple[feedback.Feedback, ...],
|
|
108
|
+
**kwargs: Dict[str, feedback.Feedback],
|
|
109
|
+
):
|
|
110
|
+
"""Register one or more feedback functions to use in rails `feedback`
|
|
111
|
+
action.
|
|
112
|
+
|
|
113
|
+
All keyword arguments indicate the key as the keyword. All
|
|
114
|
+
positional arguments use the feedback name as the key.
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
for name, feedback_instance in kwargs.items():
|
|
118
|
+
if not isinstance(feedback_instance, feedback.Feedback):
|
|
119
|
+
raise ValueError(
|
|
120
|
+
f"Invalid feedback function: {feedback_instance}; "
|
|
121
|
+
f"expected a Feedback class instance."
|
|
122
|
+
)
|
|
123
|
+
print(f"registered feedback function under name {name}")
|
|
124
|
+
registered_feedback_functions[name] = feedback_instance
|
|
125
|
+
|
|
126
|
+
for feedback_instance in args:
|
|
127
|
+
if not isinstance(feedback_instance, feedback.Feedback):
|
|
128
|
+
raise ValueError(
|
|
129
|
+
f"Invalid feedback function: {feedback_instance}; "
|
|
130
|
+
f"expected a Feedback class instance."
|
|
131
|
+
)
|
|
132
|
+
print(
|
|
133
|
+
f"registered feedback function under name {feedback_instance.name}"
|
|
134
|
+
)
|
|
135
|
+
registered_feedback_functions[feedback_instance.name] = feedback
|
|
136
|
+
|
|
137
|
+
@staticmethod
|
|
138
|
+
def action_of_feedback(
|
|
139
|
+
feedback_instance: feedback.Feedback, verbose: bool = False
|
|
140
|
+
) -> Callable:
|
|
141
|
+
"""Create a custom rails action for the given feedback function.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
feedback_instance: A feedback function to register as an action.
|
|
145
|
+
|
|
146
|
+
verbose: Print out info on invocation upon invocation.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
A custom action that will run the feedback function. The name is
|
|
150
|
+
the same as the feedback function's name.
|
|
151
|
+
"""
|
|
152
|
+
|
|
153
|
+
if not isinstance(feedback_instance, feedback.Feedback):
|
|
154
|
+
raise ValueError(
|
|
155
|
+
f"Invalid feedback function: {feedback_instance}; "
|
|
156
|
+
f"expected a Feedback class instance."
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
@action(name=feedback_instance.name)
|
|
160
|
+
async def run_feedback(*args, **kwargs):
|
|
161
|
+
if verbose:
|
|
162
|
+
print(
|
|
163
|
+
f"Running feedback function {feedback_instance.name} with:"
|
|
164
|
+
)
|
|
165
|
+
print(f" args = {args}")
|
|
166
|
+
print(f" kwargs = {kwargs}")
|
|
167
|
+
|
|
168
|
+
res = feedback_instance.run(*args, **kwargs).result
|
|
169
|
+
|
|
170
|
+
if verbose:
|
|
171
|
+
print(f" result = {res}")
|
|
172
|
+
|
|
173
|
+
return res
|
|
174
|
+
|
|
175
|
+
return run_feedback
|
|
176
|
+
|
|
177
|
+
@action(name="feedback")
|
|
178
|
+
@staticmethod
|
|
179
|
+
async def feedback_action(
|
|
180
|
+
# Default action arguments:
|
|
181
|
+
events: Optional[List[Dict]] = None,
|
|
182
|
+
context: Optional[Dict] = None,
|
|
183
|
+
llm: Optional[BaseLanguageModel] = None,
|
|
184
|
+
config: Optional[RailsConfig] = None,
|
|
185
|
+
# Rest of arguments are specific to this action.
|
|
186
|
+
function: Optional[str] = None,
|
|
187
|
+
selectors: Optional[Dict[str, Union[str, Lens]]] = None,
|
|
188
|
+
verbose: bool = False,
|
|
189
|
+
) -> ActionResult:
|
|
190
|
+
"""Run the specified feedback function from trulens.
|
|
191
|
+
|
|
192
|
+
To use this action, it needs to be registered with your rails app and
|
|
193
|
+
feedback functions themselves need to be registered with this function.
|
|
194
|
+
The name under which this action is registered for rails is `feedback`.
|
|
195
|
+
|
|
196
|
+
Usage:
|
|
197
|
+
```python
|
|
198
|
+
rails: LLMRails = ... # your app
|
|
199
|
+
language_match: Feedback = Feedback(...) # your feedback function
|
|
200
|
+
|
|
201
|
+
# First we register some feedback functions with the custom action:
|
|
202
|
+
FeedbackAction.register_feedback_functions(language_match)
|
|
203
|
+
|
|
204
|
+
# Can also use kwargs expansion from dict like produced by rag_triad:
|
|
205
|
+
# FeedbackAction.register_feedback_functions(**rag_triad(...))
|
|
206
|
+
|
|
207
|
+
# Then the feedback method needs to be registered with the rails app:
|
|
208
|
+
rails.register_action(FeedbackAction.feedback)
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
events: See [Action
|
|
213
|
+
parameters](https://github.com/NVIDIA/NeMo-Guardrails/blob/develop/docs/user_guides/python-api.md#special-parameters).
|
|
214
|
+
|
|
215
|
+
context: See [Action
|
|
216
|
+
parameters](https://github.com/NVIDIA/NeMo-Guardrails/blob/develop/docs/user_guides/python-api.md#special-parameters).
|
|
217
|
+
|
|
218
|
+
llm: See [Action
|
|
219
|
+
parameters](https://github.com/NVIDIA/NeMo-Guardrails/blob/develop/docs/user_guides/python-api.md#special-parameters).
|
|
220
|
+
|
|
221
|
+
config: See [Action
|
|
222
|
+
parameters](https://github.com/NVIDIA/NeMo-Guardrails/blob/develop/docs/user_guides/python-api.md#special-parameters).
|
|
223
|
+
|
|
224
|
+
function: Name of the feedback function to run.
|
|
225
|
+
|
|
226
|
+
selectors: Selectors for the function. Can be provided either as
|
|
227
|
+
strings to be parsed into lenses or lenses themselves.
|
|
228
|
+
|
|
229
|
+
verbose: Print the values of the selectors before running feedback
|
|
230
|
+
and print the result after running feedback.
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
ActionResult: An action result containing the result of the feedback.
|
|
234
|
+
|
|
235
|
+
Example:
|
|
236
|
+
```colang
|
|
237
|
+
define subflow check language match
|
|
238
|
+
$result = execute feedback(\\
|
|
239
|
+
function="language_match",\\
|
|
240
|
+
selectors={\\
|
|
241
|
+
"text1":"action.context.last_user_message",\\
|
|
242
|
+
"text2":"action.context.bot_message"\\
|
|
243
|
+
}\\
|
|
244
|
+
)
|
|
245
|
+
if $result < 0.8
|
|
246
|
+
bot inform language mismatch
|
|
247
|
+
stop
|
|
248
|
+
```
|
|
249
|
+
"""
|
|
250
|
+
|
|
251
|
+
feedback_function = registered_feedback_functions.get(function)
|
|
252
|
+
|
|
253
|
+
if feedback_function is None:
|
|
254
|
+
raise ValueError(
|
|
255
|
+
f"Invalid feedback function: {function}; "
|
|
256
|
+
f"there is/are {len(registered_feedback_functions)} registered function(s):\n\t"
|
|
257
|
+
+ "\n\t".join(registered_feedback_functions.keys())
|
|
258
|
+
+ "\n"
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
fname = feedback_function.name
|
|
262
|
+
|
|
263
|
+
if selectors is None:
|
|
264
|
+
raise ValueError(
|
|
265
|
+
f"Need selectors for feedback function: {fname} "
|
|
266
|
+
f"with signature {inspect.signature(feedback_function.imp)}"
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
selectors = {
|
|
270
|
+
argname: (
|
|
271
|
+
Lens.of_string(arglens) if isinstance(arglens, str) else arglens
|
|
272
|
+
)
|
|
273
|
+
for argname, arglens in selectors.items()
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
feedback_function = feedback_function.on(**selectors)
|
|
277
|
+
|
|
278
|
+
source_data = dict(
|
|
279
|
+
action=dict(events=events, context=context, llm=llm, config=config)
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
if verbose:
|
|
283
|
+
print(fname)
|
|
284
|
+
for argname, lens in feedback_function.selectors.items():
|
|
285
|
+
print(f" {argname} = ", end=None)
|
|
286
|
+
# use pretty print for the potentially big thing here:
|
|
287
|
+
print(
|
|
288
|
+
retab(
|
|
289
|
+
tab=" ", s=pformat(lens.get_sole_item(source_data))
|
|
290
|
+
)
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
context_updates = {}
|
|
294
|
+
|
|
295
|
+
try:
|
|
296
|
+
result = feedback_function.run(source_data=source_data)
|
|
297
|
+
context_updates["result"] = result.result
|
|
298
|
+
|
|
299
|
+
if verbose:
|
|
300
|
+
print(f" {fname} result = {result.result}")
|
|
301
|
+
|
|
302
|
+
except Exception:
|
|
303
|
+
context_updates["result"] = None
|
|
304
|
+
|
|
305
|
+
return ActionResult(
|
|
306
|
+
return_value=context_updates["result"],
|
|
307
|
+
context_updates=context_updates,
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
return ActionResult(
|
|
311
|
+
return_value=context_updates["result"],
|
|
312
|
+
context_updates=context_updates,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
class RailsInstrument(Instrument):
|
|
317
|
+
"""Instrumentation specification for _NeMo Guardrails_ apps."""
|
|
318
|
+
|
|
319
|
+
class Default:
|
|
320
|
+
"""Default instrumentation specification."""
|
|
321
|
+
|
|
322
|
+
MODULES = {"nemoguardrails"}.union(LangChainInstrument.Default.MODULES)
|
|
323
|
+
"""Modules to instrument by name prefix.
|
|
324
|
+
|
|
325
|
+
Note that _NeMo Guardrails_ uses _LangChain_ internally for some things.
|
|
326
|
+
"""
|
|
327
|
+
|
|
328
|
+
CLASSES = lambda: {
|
|
329
|
+
LLMRails,
|
|
330
|
+
KnowledgeBase,
|
|
331
|
+
LLMGenerationActions,
|
|
332
|
+
ActionDispatcher,
|
|
333
|
+
FeedbackActions,
|
|
334
|
+
}.union(LangChainInstrument.Default.CLASSES())
|
|
335
|
+
"""Instrument only these classes."""
|
|
336
|
+
|
|
337
|
+
METHODS: Dict[str, ClassFilter] = dict_set_with_multikey(
|
|
338
|
+
dict(LangChainInstrument.Default.METHODS), # copy
|
|
339
|
+
{
|
|
340
|
+
("execute_action"): ActionDispatcher,
|
|
341
|
+
(
|
|
342
|
+
"generate",
|
|
343
|
+
"generate_async",
|
|
344
|
+
"stream_async",
|
|
345
|
+
"generate_events",
|
|
346
|
+
"generate_events_async",
|
|
347
|
+
"_get_events_for_messages",
|
|
348
|
+
): LLMRails,
|
|
349
|
+
"search_relevant_chunks": KnowledgeBase,
|
|
350
|
+
(
|
|
351
|
+
"generate_user_intent",
|
|
352
|
+
"generate_next_step",
|
|
353
|
+
"generate_bot_message",
|
|
354
|
+
"generate_value",
|
|
355
|
+
"generate_intent_steps_message",
|
|
356
|
+
): LLMGenerationActions,
|
|
357
|
+
# TODO: Include feedback method in FeedbackActions, currently
|
|
358
|
+
# bugged and will not be logged.
|
|
359
|
+
"feedback": FeedbackActions,
|
|
360
|
+
},
|
|
361
|
+
)
|
|
362
|
+
"""Instrument only methods with these names and of these classes."""
|
|
363
|
+
|
|
364
|
+
def __init__(self, *args, **kwargs):
|
|
365
|
+
super().__init__(
|
|
366
|
+
include_modules=RailsInstrument.Default.MODULES,
|
|
367
|
+
include_classes=RailsInstrument.Default.CLASSES(),
|
|
368
|
+
include_methods=RailsInstrument.Default.METHODS,
|
|
369
|
+
*args,
|
|
370
|
+
**kwargs,
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
class TruRails(mod_app.App):
|
|
375
|
+
"""Recorder for apps defined using _NeMo Guardrails_.
|
|
376
|
+
|
|
377
|
+
Args:
|
|
378
|
+
app: A _NeMo Guardrails_ application.
|
|
379
|
+
"""
|
|
380
|
+
|
|
381
|
+
model_config: ClassVar[dict] = {"arbitrary_types_allowed": True}
|
|
382
|
+
|
|
383
|
+
app: LLMRails
|
|
384
|
+
|
|
385
|
+
root_callable: ClassVar[FunctionOrMethod] = Field(
|
|
386
|
+
default_factory=lambda: FunctionOrMethod.of_callable(LLMRails.generate)
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
def __init__(self, app: LLMRails, **kwargs):
|
|
390
|
+
# TruLlama specific:
|
|
391
|
+
kwargs["app"] = app
|
|
392
|
+
kwargs["root_class"] = Class.of_object(app) # TODO: make class property
|
|
393
|
+
kwargs["instrument"] = RailsInstrument(app=self)
|
|
394
|
+
|
|
395
|
+
super().__init__(**kwargs)
|
|
396
|
+
|
|
397
|
+
def main_output(
|
|
398
|
+
self, func: Callable, sig: Signature, bindings: BoundArguments, ret: Any
|
|
399
|
+
) -> JSON:
|
|
400
|
+
"""
|
|
401
|
+
Determine the main out string for the given function `func` with
|
|
402
|
+
signature `sig` after it is called with the given `bindings` and has
|
|
403
|
+
returned `ret`.
|
|
404
|
+
"""
|
|
405
|
+
|
|
406
|
+
if isinstance(ret, dict):
|
|
407
|
+
if "content" in ret:
|
|
408
|
+
return ret["content"]
|
|
409
|
+
|
|
410
|
+
return jsonify(ret)
|
|
411
|
+
|
|
412
|
+
def main_input(
|
|
413
|
+
self, func: Callable, sig: Signature, bindings: BoundArguments
|
|
414
|
+
) -> JSON:
|
|
415
|
+
"""
|
|
416
|
+
Determine the main input string for the given function `func` with
|
|
417
|
+
signature `sig` after it is called with the given `bindings` and has
|
|
418
|
+
returned `ret`.
|
|
419
|
+
"""
|
|
420
|
+
|
|
421
|
+
if "messages" in bindings.arguments:
|
|
422
|
+
messages = bindings.arguments["messages"]
|
|
423
|
+
if len(messages) == 1:
|
|
424
|
+
message = messages[0]
|
|
425
|
+
if "content" in message:
|
|
426
|
+
return message["content"]
|
|
427
|
+
|
|
428
|
+
return jsonify(bindings.arguments)
|
|
429
|
+
|
|
430
|
+
@classmethod
|
|
431
|
+
def select_context(cls, app: Optional[LLMRails] = None) -> Lens:
|
|
432
|
+
"""
|
|
433
|
+
Get the path to the context in the query output.
|
|
434
|
+
"""
|
|
435
|
+
return Select.RecordCalls.kb.search_relevant_chunks.rets[:].body
|
|
436
|
+
|
|
437
|
+
def __getattr__(self, name):
|
|
438
|
+
if name == "__name__":
|
|
439
|
+
return self.__class__.__name__ # Return the class name of TruRails
|
|
440
|
+
elif safe_hasattr(self.app, name):
|
|
441
|
+
return getattr(
|
|
442
|
+
self.app, name
|
|
443
|
+
) # Delegate to the wrapped app if it has the attribute
|
|
444
|
+
else:
|
|
445
|
+
raise AttributeError(f"TruRails has no attribute named {name}")
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
TruRails.model_rebuild()
|