clams-python 1.7.1__tar.gz → 1.7.3__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.
- {clams_python-1.7.1 → clams_python-1.7.3}/CHANGELOG.md +17 -0
- {clams_python-1.7.1/clams_python.egg-info → clams_python-1.7.3}/PKG-INFO +2 -2
- {clams_python-1.7.1 → clams_python-1.7.3}/clams/app/__init__.py +176 -15
- {clams_python-1.7.1 → clams_python-1.7.3}/clams/develop/templates/app/cli.py.template +21 -4
- {clams_python-1.7.1 → clams_python-1.7.3/clams_python.egg-info}/PKG-INFO +2 -2
- {clams_python-1.7.1 → clams_python-1.7.3}/clams_python.egg-info/SOURCES.txt +3 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/clams_python.egg-info/requires.txt +1 -1
- clams_python-1.7.3/clams_python.egg-info/scm_file_list.json +96 -0
- clams_python-1.7.3/clams_python.egg-info/scm_version.json +8 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/documentation/app-baseclasses.rst +61 -1
- clams_python-1.7.3/documentation/autodoc/clams.backends.rst +15 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/documentation/conf.py +3 -1
- {clams_python-1.7.1 → clams_python-1.7.3}/documentation/target-versions.csv +2 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/pyproject.toml +1 -1
- {clams_python-1.7.1 → clams_python-1.7.3}/tests/test_promptable.py +156 -14
- {clams_python-1.7.1 → clams_python-1.7.3}/.github/workflows/codecov.yml +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/.github/workflows/container.yml +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/.github/workflows/containers.yml +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/.github/workflows/issue-assign.yml +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/.github/workflows/issue-close.yml +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/.github/workflows/issue-infra-project.yml +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/.github/workflows/publish.yml +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/.github/workflows/release-check.yml +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/.gitignore +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/.pytype.cfg +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/CONTRIBUTING.md +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/LICENSE +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/README.md +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/build-tools/build.py +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/build-tools/clean.py +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/build-tools/docs.py +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/build-tools/prep_release.py +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/build-tools/publish.py +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/build-tools/test.py +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/clams/__init__.py +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/clams/appmetadata/__init__.py +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/clams/backends/__init__.py +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/clams/backends/hf.py +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/clams/develop/__init__.py +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/clams/develop/templates/app/.dockerignore.template +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/clams/develop/templates/app/.gitignore.template +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/clams/develop/templates/app/Containerfile.template +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/clams/develop/templates/app/LICENSE.template +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/clams/develop/templates/app/README.md.template +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/clams/develop/templates/app/app.py.template +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/clams/develop/templates/app/metadata.py.template +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/clams/develop/templates/app/requirements.txt.template +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/clams/develop/templates/gha/for-clams-team.md.template +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/clams/develop/templates/gha/workflows/issue-apps-project.yml.template +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/clams/develop/templates/gha/workflows/issue-assign.yml.template +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/clams/develop/templates/gha/workflows/issue-close.yml.template +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/clams/develop/templates/gha/workflows/publish.yml.template +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/clams/develop/templates/utl-tf/__init__.py.template +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/clams/develop/templates/utl-tf/timeframe.py.template +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/clams/envelop/__init__.py +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/clams/restify/__init__.py +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/clams/serve/__init__.py +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/clams/ver/__init__.py +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/clams_python.egg-info/dependency_links.txt +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/clams_python.egg-info/entry_points.txt +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/clams_python.egg-info/top_level.txt +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/container/Containerfile +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/container/ffmpeg-hf.containerfile +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/container/ffmpeg-hf4.containerfile +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/container/ffmpeg-hf5.containerfile +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/container/ffmpeg-tf2.containerfile +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/container/ffmpeg-torch2.containerfile +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/container/ffmpeg.containerfile +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/container/generate_containers_yml.py +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/container/hf.containerfile +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/container/hf4.containerfile +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/container/hf5.containerfile +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/container/jdk8.containerfile +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/container/opencv4-hf.containerfile +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/container/opencv4-hf4.containerfile +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/container/opencv4-hf5.containerfile +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/container/opencv4-tf2.containerfile +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/container/opencv4-torch2.containerfile +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/container/opencv4.containerfile +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/container/tf2.containerfile +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/container/torch2.containerfile +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/documentation/appdirectory.rst +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/documentation/appmetadata.rst +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/documentation/autodoc/clams.app.rst +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/documentation/autodoc/clams.appmetadata.rst +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/documentation/autodoc/clams.mmif_utils.rst +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/documentation/autodoc/clams.restify.rst +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/documentation/clamsapp.rst +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/documentation/cli.rst +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/documentation/gpu-apps.rst +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/documentation/index.rst +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/documentation/input-output.rst +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/documentation/introduction.rst +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/documentation/modules.rst +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/documentation/runtime-params.rst +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/documentation/tutorial.md +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/setup.cfg +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/tests/metadata.py +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/tests/pillow-logo.png +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/tests/test_backends_hf.py +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/tests/test_clamsapp.py +0 -0
- {clams_python-1.7.1 → clams_python-1.7.3}/tests/test_envelope.py +0 -0
|
@@ -1,4 +1,21 @@
|
|
|
1
1
|
|
|
2
|
+
## releasing 1.7.2 (2026-07-07)
|
|
3
|
+
### Overview
|
|
4
|
+
This patch adds experimental handling of "reasoning traces" of propmtable apps
|
|
5
|
+
|
|
6
|
+
### Additions
|
|
7
|
+
* common parameter `useReasoning` (bool) is added
|
|
8
|
+
* when reasoning is on, in-line tagged reasoning traces (commonly within `<think>...</think>`) are separately stored in `modelReasoningTrace` of the TextDocument
|
|
9
|
+
|
|
10
|
+
> [!NOTE]
|
|
11
|
+
> The new names are tentative and can be changed in the near future.
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## releasing 1.7.1 (2026-06-19)
|
|
15
|
+
### Overview
|
|
16
|
+
Updates the `mmif-python` SDK dependency to 1.5.1 to incorporate a bugfix in VDH, with some other minor documentation generation improvements.
|
|
17
|
+
|
|
18
|
+
|
|
2
19
|
## releasing 1.7.0 (2026-05-29)
|
|
3
20
|
### Overview
|
|
4
21
|
This release introduces a specialized base class `ClamsPromptableApp` and its HuggingFace specialization `ClamsHFPromptableApp` for CLAMS apps that wrap LLM / VLM / ALM / LMM-based analyzers, along with other HF-related helper code in various places. Additionally, it adds `appTags` field for app categorization in app metadata, and new timeframe related local utility recipe for `clams develop --recipe utl-tf` scaffold.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: clams-python
|
|
3
|
-
Version: 1.7.
|
|
3
|
+
Version: 1.7.3
|
|
4
4
|
Summary: A collection of APIs to develop CLAMS app for python
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Project-URL: homepage, https://clams.ai
|
|
@@ -13,7 +13,7 @@ Classifier: Programming Language :: Python :: 3 :: Only
|
|
|
13
13
|
Requires-Python: >=3.10
|
|
14
14
|
Description-Content-Type: text/markdown
|
|
15
15
|
License-File: LICENSE
|
|
16
|
-
Requires-Dist: mmif-python==1.5.
|
|
16
|
+
Requires-Dist: mmif-python==1.5.3
|
|
17
17
|
Requires-Dist: Flask>=2
|
|
18
18
|
Requires-Dist: Flask-RESTful>=0.3.9
|
|
19
19
|
Requires-Dist: gunicorn>=20
|
|
@@ -711,6 +711,26 @@ class ClamsPromptableApp(ClamsApp):
|
|
|
711
711
|
'values grow the KV cache linearly and increase GPU memory '
|
|
712
712
|
'usage; reduce if VRAM is constrained.',
|
|
713
713
|
},
|
|
714
|
+
{
|
|
715
|
+
'name': 'useReasoning', 'type': 'boolean', 'default': False,
|
|
716
|
+
'description':
|
|
717
|
+
'Request the model\'s reasoning ("thinking") mode. Off by '
|
|
718
|
+
'default. Honored only by apps whose backing model has a '
|
|
719
|
+
'distinct reasoning mode; apps without one ignore it. When '
|
|
720
|
+
'honored and enabled, the reasoning trace is split from the '
|
|
721
|
+
'answer and stored in the ``modelReasoningTrace`` property of '
|
|
722
|
+
'the output ``TextDocument`` (kept out of the document text). '
|
|
723
|
+
'Reasoning is markedly slower and far more token-hungry: the '
|
|
724
|
+
'whole trace is generated before the answer and drawn from the '
|
|
725
|
+
'same budget capped by ``maxNewTokens``, so raise '
|
|
726
|
+
'``maxNewTokens`` substantially (thousands of tokens, not '
|
|
727
|
+
'hundreds) when enabling this, or the trace may consume the '
|
|
728
|
+
'entire budget and the answer be truncated or empty. Small '
|
|
729
|
+
'reasoning models (as a rule of thumb, roughly 4B parameters '
|
|
730
|
+
'and under) are especially prone to non-terminating "thinking '
|
|
731
|
+
'loops" that exhaust the budget without producing an answer; '
|
|
732
|
+
'validate termination per model before relying on it.',
|
|
733
|
+
},
|
|
714
734
|
{
|
|
715
735
|
'name': 'temperature', 'type': 'number', 'default': 0.0,
|
|
716
736
|
'description':
|
|
@@ -1005,8 +1025,10 @@ class ClamsPromptableApp(ClamsApp):
|
|
|
1005
1025
|
"""
|
|
1006
1026
|
Persist a single LLM text response into a view. Writes one
|
|
1007
1027
|
``TextDocument`` (containing the response) plus possible
|
|
1008
|
-
grounding via an ``Alignment`` annotation and ``origins`` /
|
|
1009
|
-
``origination`` properties on the TD.
|
|
1028
|
+
grounding via an ``Alignment`` annotation and ``origins`` /
|
|
1029
|
+
``origination`` properties on the TD. The TD also inherits the
|
|
1030
|
+
``document`` of its ``source`` annotation, so it records which
|
|
1031
|
+
medium the response describes.
|
|
1010
1032
|
|
|
1011
1033
|
The two grounding link kinds are semantically distinct:
|
|
1012
1034
|
|
|
@@ -1028,7 +1050,12 @@ class ClamsPromptableApp(ClamsApp):
|
|
|
1028
1050
|
``Alignment`` first if needed.
|
|
1029
1051
|
:param source: ``id`` of the annotation to record as the
|
|
1030
1052
|
cross-modal anchor of the response (see above).
|
|
1031
|
-
:param response: the text
|
|
1053
|
+
:param response: the text to store as the TextDocument's
|
|
1054
|
+
value. Typically the model's generated answer, but the
|
|
1055
|
+
caller MAY pass a post-processed form (e.g. with a
|
|
1056
|
+
reasoning block stripped, whitespace trimmed, or otherwise
|
|
1057
|
+
reformatted); this method stores whatever it is given,
|
|
1058
|
+
verbatim, as the ``text``.
|
|
1032
1059
|
:param origins: optional list of ``id``\\s of annotations the
|
|
1033
1060
|
response was *derived* from. Must be paired with
|
|
1034
1061
|
``origination``.
|
|
@@ -1039,15 +1066,24 @@ class ClamsPromptableApp(ClamsApp):
|
|
|
1039
1066
|
``origins``.
|
|
1040
1067
|
:param reasoning_trace: optional model-side reasoning trace
|
|
1041
1068
|
(a chain-of-thought / scratchpad string, NOT a Python
|
|
1042
|
-
traceback).
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1069
|
+
traceback). When non-``None``, it is stored on the
|
|
1070
|
+
returned TextDocument as the ``modelReasoningTrace``
|
|
1071
|
+
property, kept OUT of the ``text`` (which holds whatever
|
|
1072
|
+
``response`` the caller passed). The point is separation,
|
|
1073
|
+
not any claim about the ``text`` being verbatim: storing a
|
|
1074
|
+
trace adds no annotation and no new type, so the type-level
|
|
1075
|
+
output shape is identical whether or not a trace is stored.
|
|
1076
|
+
Pass ``None`` (the default) to store no trace. The property
|
|
1077
|
+
name is provisional pending the
|
|
1078
|
+
vocabulary convention (clamsproject/clams-vocabulary#11).
|
|
1046
1079
|
:return: ``(TextDocument, Alignment)`` tuple of the new
|
|
1047
1080
|
annotations.
|
|
1048
1081
|
:raises ValueError: if exactly one of ``origins`` /
|
|
1049
|
-
``origination`` is set
|
|
1050
|
-
or
|
|
1082
|
+
``origination`` is set (they must be supplied together or
|
|
1083
|
+
both omitted), or if the ``source`` annotation carries no
|
|
1084
|
+
``document`` to ground the response to (a malformed input).
|
|
1085
|
+
:raises KeyError: if ``source`` does not resolve to an
|
|
1086
|
+
annotation in the MMIF.
|
|
1051
1087
|
"""
|
|
1052
1088
|
if bool(origins) != bool(origination):
|
|
1053
1089
|
raise ValueError(
|
|
@@ -1056,6 +1092,20 @@ class ClamsPromptableApp(ClamsApp):
|
|
|
1056
1092
|
f"origins={origins!r}, origination={origination!r}."
|
|
1057
1093
|
)
|
|
1058
1094
|
td = view.new_textdocument(text=response)
|
|
1095
|
+
# Ground the TextDocument to the medium it describes: copy `document`
|
|
1096
|
+
# from the source annotation (e.g. the parent TimeFrame's video) onto
|
|
1097
|
+
# the TD, so it records which document the response pertains to. A
|
|
1098
|
+
# grounded response must be traceable to a document, so a source that
|
|
1099
|
+
# carries no `document` means the upstream MMIF is malformed -- raise
|
|
1100
|
+
# rather than silently drop the grounding.
|
|
1101
|
+
source_ann = view._parent_mmif[source]
|
|
1102
|
+
source_doc = source_ann.get_property('document')
|
|
1103
|
+
if not source_doc:
|
|
1104
|
+
raise ValueError(
|
|
1105
|
+
f"source annotation {source!r} has no `document` property to "
|
|
1106
|
+
f"ground the response to; the input MMIF is malformed."
|
|
1107
|
+
)
|
|
1108
|
+
td.add_property('document', source_doc)
|
|
1059
1109
|
if origins:
|
|
1060
1110
|
td.add_property('origins', origins)
|
|
1061
1111
|
td.add_property('origination', origination)
|
|
@@ -1065,13 +1115,71 @@ class ClamsPromptableApp(ClamsApp):
|
|
|
1065
1115
|
target=td.id,
|
|
1066
1116
|
)
|
|
1067
1117
|
if reasoning_trace is not None:
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1118
|
+
# Store the trace as a property on the TD rather than a separate
|
|
1119
|
+
# document: keeps the type-level output shape invariant (same
|
|
1120
|
+
# annotation, same `contains`, same graph) whether or not a trace is
|
|
1121
|
+
# present -- a strict enrichment, independent of whatever `response`
|
|
1122
|
+
# the caller put in `text`. Property name provisional pending
|
|
1123
|
+
# clamsproject/clams-vocabulary#11.
|
|
1124
|
+
td.add_property('modelReasoningTrace', reasoning_trace)
|
|
1072
1125
|
return td, align
|
|
1073
1126
|
|
|
1074
|
-
|
|
1127
|
+
@staticmethod
|
|
1128
|
+
def split_tagged_reasoning_trace(
|
|
1129
|
+
text: str,
|
|
1130
|
+
open_tag: str = '<think>',
|
|
1131
|
+
close_tag: str = '</think>',
|
|
1132
|
+
assume_open: bool = False,
|
|
1133
|
+
) -> Tuple[str, Optional[str]]:
|
|
1134
|
+
"""
|
|
1135
|
+
Split a reasoning model's output into ``(answer, trace)`` for the
|
|
1136
|
+
inline XML-tag style: a reasoning block delimited by ``open_tag`` /
|
|
1137
|
+
``close_tag`` (default ``<think>`` / ``</think>``), e.g. as emitted
|
|
1138
|
+
by DeepSeek-R1 or Qwen3.5.
|
|
1139
|
+
|
|
1140
|
+
Prefilled opening tags: some chat templates inject the OPENING tag
|
|
1141
|
+
into the prompt when reasoning is enabled (Qwen3.5 does this), so the
|
|
1142
|
+
decoded output carries only the CLOSING tag -- the model never emits
|
|
1143
|
+
the opener. Pass ``assume_open=True`` in that situation (typically
|
|
1144
|
+
wired to the app's reasoning toggle) so the trace is still recovered.
|
|
1145
|
+
The cases:
|
|
1146
|
+
|
|
1147
|
+
* close tag present: ``answer`` is the text after it; ``trace`` is the
|
|
1148
|
+
text between the tags, or everything before the close tag when the
|
|
1149
|
+
opening tag is absent (prefilled).
|
|
1150
|
+
* close tag absent, ``assume_open=True``: the output is unterminated
|
|
1151
|
+
reasoning (e.g. the trace overran ``maxNewTokens`` before closing) --
|
|
1152
|
+
there is no answer, so returns ``('', text.strip())``.
|
|
1153
|
+
* close tag absent, ``assume_open=False`` (default): treated as a
|
|
1154
|
+
plain, non-reasoning answer -- returns ``(text.strip(), None)``.
|
|
1155
|
+
|
|
1156
|
+
Only the inline-tag style is handled; models that isolate reasoning in
|
|
1157
|
+
a separate token-delimited channel (e.g. gpt-oss, Gemma) need their own
|
|
1158
|
+
parsing in the app.
|
|
1159
|
+
|
|
1160
|
+
:param text: raw decoded model output.
|
|
1161
|
+
:param open_tag: opening marker of the inline reasoning block.
|
|
1162
|
+
:param close_tag: closing marker of the inline reasoning block.
|
|
1163
|
+
:param assume_open: treat the text as the body of a reasoning block
|
|
1164
|
+
whose opening tag was prefilled into the prompt, so a missing close
|
|
1165
|
+
tag means unterminated reasoning (empty answer) rather than a plain
|
|
1166
|
+
answer.
|
|
1167
|
+
:return: ``(answer, trace_or_None)``.
|
|
1168
|
+
"""
|
|
1169
|
+
ci = text.rfind(close_tag)
|
|
1170
|
+
if ci == -1:
|
|
1171
|
+
return ('', text.strip()) if assume_open else (text.strip(), None)
|
|
1172
|
+
answer = text[ci + len(close_tag):].strip()
|
|
1173
|
+
oi = text.find(open_tag)
|
|
1174
|
+
if oi != -1 and oi < ci:
|
|
1175
|
+
trace = text[oi + len(open_tag):ci].strip()
|
|
1176
|
+
elif assume_open:
|
|
1177
|
+
# opening tag was prefilled into the prompt (not generated), so the
|
|
1178
|
+
# trace is everything up to the close tag
|
|
1179
|
+
trace = text[:ci].strip()
|
|
1180
|
+
else:
|
|
1181
|
+
trace = None
|
|
1182
|
+
return answer, trace
|
|
1075
1183
|
class ClamsHFPromptableApp(ClamsPromptableApp):
|
|
1076
1184
|
"""
|
|
1077
1185
|
Base class for promptable CLAMS apps backed by a local
|
|
@@ -1289,7 +1397,7 @@ class ClamsHFPromptableApp(ClamsPromptableApp):
|
|
|
1289
1397
|
dtype=self.DTYPE,
|
|
1290
1398
|
padding_side=self.PADDING_SIDE,
|
|
1291
1399
|
revision=revision,
|
|
1292
|
-
model_kwargs=self.
|
|
1400
|
+
model_kwargs=self.model_load_kwargs(model_id, revision),
|
|
1293
1401
|
processor_kwargs=self.PROCESSOR_KWARGS,
|
|
1294
1402
|
)
|
|
1295
1403
|
self.logger.info(f"HF model loaded on {triple[2]}")
|
|
@@ -1297,6 +1405,32 @@ class ClamsHFPromptableApp(ClamsPromptableApp):
|
|
|
1297
1405
|
self.processor, self.model, self.device = triple
|
|
1298
1406
|
return triple
|
|
1299
1407
|
|
|
1408
|
+
def model_load_kwargs(self, model_id: str, revision: str) -> dict:
|
|
1409
|
+
"""
|
|
1410
|
+
The ``model_kwargs`` forwarded to
|
|
1411
|
+
:func:`~clams.backends.hf.load_hf_model` (and on to
|
|
1412
|
+
``from_pretrained``) for a SPECIFIC family member. The base
|
|
1413
|
+
implementation returns a copy of the class-level
|
|
1414
|
+
:py:attr:`MODEL_KWARGS`, applied uniformly to every variant.
|
|
1415
|
+
|
|
1416
|
+
Override when a family mixes per-variant load requirements that
|
|
1417
|
+
a single class-level ``MODEL_KWARGS`` cannot express -- e.g. a
|
|
1418
|
+
``analyzer_versions`` family whose members span full-precision,
|
|
1419
|
+
FP8, and GPTQ-Int4 builds, where only the quantized members need
|
|
1420
|
+
a ``quantization_config``::
|
|
1421
|
+
|
|
1422
|
+
def model_load_kwargs(self, model_id, revision):
|
|
1423
|
+
kw = super().model_load_kwargs(model_id, revision)
|
|
1424
|
+
if model_id.endswith('-GPTQ-Int4'):
|
|
1425
|
+
kw['quantization_config'] = ... # gptq config
|
|
1426
|
+
return kw
|
|
1427
|
+
|
|
1428
|
+
:param model_id: resolved HF model id (no ``@revision`` suffix).
|
|
1429
|
+
:param revision: resolved commit/revision being loaded.
|
|
1430
|
+
:return: ``model_kwargs`` dict for this variant.
|
|
1431
|
+
"""
|
|
1432
|
+
return dict(self.MODEL_KWARGS or {})
|
|
1433
|
+
|
|
1300
1434
|
def generate(
|
|
1301
1435
|
self,
|
|
1302
1436
|
prompt: List[str],
|
|
@@ -1341,6 +1475,7 @@ class ClamsHFPromptableApp(ClamsPromptableApp):
|
|
|
1341
1475
|
if n == 0:
|
|
1342
1476
|
return []
|
|
1343
1477
|
gen_kwargs = self.build_gen_kwargs(**generation_params)
|
|
1478
|
+
template_kwargs = self.build_template_kwargs(**generation_params)
|
|
1344
1479
|
try:
|
|
1345
1480
|
conversations = [
|
|
1346
1481
|
self.build_conversation(
|
|
@@ -1357,6 +1492,7 @@ class ClamsHFPromptableApp(ClamsPromptableApp):
|
|
|
1357
1492
|
return_dict=True,
|
|
1358
1493
|
padding=True,
|
|
1359
1494
|
return_tensors="pt",
|
|
1495
|
+
**template_kwargs,
|
|
1360
1496
|
)
|
|
1361
1497
|
inputs = inputs.to(self.device)
|
|
1362
1498
|
if (self.DTYPE is not None
|
|
@@ -1405,6 +1541,31 @@ class ClamsHFPromptableApp(ClamsPromptableApp):
|
|
|
1405
1541
|
})
|
|
1406
1542
|
return gen_kwargs
|
|
1407
1543
|
|
|
1544
|
+
def build_template_kwargs(self, **generation_params) -> dict:
|
|
1545
|
+
"""
|
|
1546
|
+
Extra keyword arguments to pass to
|
|
1547
|
+
``processor.apply_chat_template`` in :py:meth:`generate`, beyond
|
|
1548
|
+
the SDK-fixed ones (``add_generation_prompt``, ``tokenize``,
|
|
1549
|
+
``return_dict``, ``padding``, ``return_tensors``). The base
|
|
1550
|
+
implementation returns ``{}``.
|
|
1551
|
+
|
|
1552
|
+
Override to inject model-specific chat-template controls without
|
|
1553
|
+
having to reimplement :py:meth:`generate`. This is where an app
|
|
1554
|
+
honors the SDK ``useReasoning`` parameter, mapping it onto the
|
|
1555
|
+
backend's reasoning switch. Common cases:
|
|
1556
|
+
|
|
1557
|
+
* ``{'enable_thinking': bool(generation_params.get('use_reasoning'))}``
|
|
1558
|
+
to bind ``useReasoning`` to a reasoning ("thinking") model's
|
|
1559
|
+
template switch;
|
|
1560
|
+
* ``{'tools': [...]}`` or ``{'documents': [...]}`` for tool-use /
|
|
1561
|
+
RAG chat templates.
|
|
1562
|
+
|
|
1563
|
+
Keys MUST NOT collide with the SDK-fixed kwargs above. Unknown
|
|
1564
|
+
keys are passed through to the template's render context, where
|
|
1565
|
+
a template that does not reference them simply ignores them.
|
|
1566
|
+
"""
|
|
1567
|
+
return {}
|
|
1568
|
+
|
|
1408
1569
|
|
|
1409
1570
|
class ParameterCaster(object):
|
|
1410
1571
|
|
|
@@ -6,13 +6,17 @@ DO NOT CHANGE the name of the file
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import argparse
|
|
9
|
+
import json
|
|
9
10
|
import sys
|
|
10
11
|
from contextlib import redirect_stdout
|
|
11
12
|
|
|
13
|
+
import jsonschema
|
|
14
|
+
|
|
12
15
|
import app
|
|
13
16
|
|
|
14
17
|
import clams.app
|
|
15
18
|
from clams import AppMetadata
|
|
19
|
+
from clams.envelop import EnvelopeError
|
|
16
20
|
|
|
17
21
|
|
|
18
22
|
def metadata_to_argparser(app_metadata: AppMetadata) -> argparse.ArgumentParser:
|
|
@@ -81,11 +85,24 @@ if __name__ == "__main__":
|
|
|
81
85
|
params[pname] = pvalue
|
|
82
86
|
else:
|
|
83
87
|
params[pname] = [pvalue]
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
# Mirror the HTTP server's error handling (see clams.restify): an invalid
|
|
89
|
+
# input is reported and exits non-zero, while an app-level failure is
|
|
90
|
+
# recorded as an error view instead of crashing with a raw traceback.
|
|
91
|
+
try:
|
|
92
|
+
if args.OUT_MMIF_FILE.name == '<stdout>':
|
|
93
|
+
with redirect_stdout(sys.stderr):
|
|
94
|
+
out_mmif = clamsapp.annotate(in_data, **params)
|
|
95
|
+
else:
|
|
86
96
|
out_mmif = clamsapp.annotate(in_data, **params)
|
|
87
|
-
|
|
88
|
-
|
|
97
|
+
except (jsonschema.exceptions.ValidationError, json.JSONDecodeError, EnvelopeError) as e:
|
|
98
|
+
detail = e.message if isinstance(e, jsonschema.exceptions.ValidationError) else str(e)
|
|
99
|
+
print(f"Invalid input data. See below for validation error.\n\n{detail}", file=sys.stderr)
|
|
100
|
+
sys.exit(1)
|
|
101
|
+
except Exception:
|
|
102
|
+
clamsapp.logger.exception("Error in annotation")
|
|
103
|
+
out_mmif = clamsapp.record_error(in_data, **params).serialize(pretty=True)
|
|
104
|
+
args.OUT_MMIF_FILE.write(out_mmif)
|
|
105
|
+
sys.exit(1)
|
|
89
106
|
args.OUT_MMIF_FILE.write(out_mmif)
|
|
90
107
|
else:
|
|
91
108
|
arg_parser.print_help()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: clams-python
|
|
3
|
-
Version: 1.7.
|
|
3
|
+
Version: 1.7.3
|
|
4
4
|
Summary: A collection of APIs to develop CLAMS app for python
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Project-URL: homepage, https://clams.ai
|
|
@@ -13,7 +13,7 @@ Classifier: Programming Language :: Python :: 3 :: Only
|
|
|
13
13
|
Requires-Python: >=3.10
|
|
14
14
|
Description-Content-Type: text/markdown
|
|
15
15
|
License-File: LICENSE
|
|
16
|
-
Requires-Dist: mmif-python==1.5.
|
|
16
|
+
Requires-Dist: mmif-python==1.5.3
|
|
17
17
|
Requires-Dist: Flask>=2
|
|
18
18
|
Requires-Dist: Flask-RESTful>=0.3.9
|
|
19
19
|
Requires-Dist: gunicorn>=20
|
|
@@ -50,6 +50,8 @@ clams_python.egg-info/SOURCES.txt
|
|
|
50
50
|
clams_python.egg-info/dependency_links.txt
|
|
51
51
|
clams_python.egg-info/entry_points.txt
|
|
52
52
|
clams_python.egg-info/requires.txt
|
|
53
|
+
clams_python.egg-info/scm_file_list.json
|
|
54
|
+
clams_python.egg-info/scm_version.json
|
|
53
55
|
clams_python.egg-info/top_level.txt
|
|
54
56
|
container/Containerfile
|
|
55
57
|
container/ffmpeg-hf.containerfile
|
|
@@ -87,6 +89,7 @@ documentation/target-versions.csv
|
|
|
87
89
|
documentation/tutorial.md
|
|
88
90
|
documentation/autodoc/clams.app.rst
|
|
89
91
|
documentation/autodoc/clams.appmetadata.rst
|
|
92
|
+
documentation/autodoc/clams.backends.rst
|
|
90
93
|
documentation/autodoc/clams.mmif_utils.rst
|
|
91
94
|
documentation/autodoc/clams.restify.rst
|
|
92
95
|
tests/metadata.py
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
{
|
|
2
|
+
"files": [
|
|
3
|
+
"README.md",
|
|
4
|
+
"LICENSE",
|
|
5
|
+
"pyproject.toml",
|
|
6
|
+
".pytype.cfg",
|
|
7
|
+
"CHANGELOG.md",
|
|
8
|
+
"CONTRIBUTING.md",
|
|
9
|
+
".gitignore",
|
|
10
|
+
"build-tools/publish.py",
|
|
11
|
+
"build-tools/prep_release.py",
|
|
12
|
+
"build-tools/build.py",
|
|
13
|
+
"build-tools/docs.py",
|
|
14
|
+
"build-tools/clean.py",
|
|
15
|
+
"build-tools/test.py",
|
|
16
|
+
"clams/__init__.py",
|
|
17
|
+
"clams/develop/__init__.py",
|
|
18
|
+
"clams/develop/templates/gha/for-clams-team.md.template",
|
|
19
|
+
"clams/develop/templates/gha/workflows/issue-assign.yml.template",
|
|
20
|
+
"clams/develop/templates/gha/workflows/publish.yml.template",
|
|
21
|
+
"clams/develop/templates/gha/workflows/issue-apps-project.yml.template",
|
|
22
|
+
"clams/develop/templates/gha/workflows/issue-close.yml.template",
|
|
23
|
+
"clams/develop/templates/utl-tf/__init__.py.template",
|
|
24
|
+
"clams/develop/templates/utl-tf/timeframe.py.template",
|
|
25
|
+
"clams/develop/templates/app/Containerfile.template",
|
|
26
|
+
"clams/develop/templates/app/metadata.py.template",
|
|
27
|
+
"clams/develop/templates/app/README.md.template",
|
|
28
|
+
"clams/develop/templates/app/.dockerignore.template",
|
|
29
|
+
"clams/develop/templates/app/requirements.txt.template",
|
|
30
|
+
"clams/develop/templates/app/LICENSE.template",
|
|
31
|
+
"clams/develop/templates/app/.gitignore.template",
|
|
32
|
+
"clams/develop/templates/app/app.py.template",
|
|
33
|
+
"clams/develop/templates/app/cli.py.template",
|
|
34
|
+
"clams/restify/__init__.py",
|
|
35
|
+
"clams/envelop/__init__.py",
|
|
36
|
+
"clams/appmetadata/__init__.py",
|
|
37
|
+
"clams/app/__init__.py",
|
|
38
|
+
"clams/serve/__init__.py",
|
|
39
|
+
"clams/ver/__init__.py",
|
|
40
|
+
"clams/backends/__init__.py",
|
|
41
|
+
"clams/backends/hf.py",
|
|
42
|
+
"documentation/modules.rst",
|
|
43
|
+
"documentation/target-versions.csv",
|
|
44
|
+
"documentation/app-baseclasses.rst",
|
|
45
|
+
"documentation/gpu-apps.rst",
|
|
46
|
+
"documentation/clamsapp.rst",
|
|
47
|
+
"documentation/appdirectory.rst",
|
|
48
|
+
"documentation/index.rst",
|
|
49
|
+
"documentation/tutorial.md",
|
|
50
|
+
"documentation/introduction.rst",
|
|
51
|
+
"documentation/appmetadata.rst",
|
|
52
|
+
"documentation/cli.rst",
|
|
53
|
+
"documentation/runtime-params.rst",
|
|
54
|
+
"documentation/input-output.rst",
|
|
55
|
+
"documentation/conf.py",
|
|
56
|
+
"documentation/autodoc/clams.restify.rst",
|
|
57
|
+
"documentation/autodoc/clams.app.rst",
|
|
58
|
+
"documentation/autodoc/clams.backends.rst",
|
|
59
|
+
"documentation/autodoc/clams.mmif_utils.rst",
|
|
60
|
+
"documentation/autodoc/clams.appmetadata.rst",
|
|
61
|
+
"tests/test_promptable.py",
|
|
62
|
+
"tests/pillow-logo.png",
|
|
63
|
+
"tests/test_backends_hf.py",
|
|
64
|
+
"tests/test_envelope.py",
|
|
65
|
+
"tests/metadata.py",
|
|
66
|
+
"tests/test_clamsapp.py",
|
|
67
|
+
"container/tf2.containerfile",
|
|
68
|
+
"container/opencv4-hf.containerfile",
|
|
69
|
+
"container/torch2.containerfile",
|
|
70
|
+
"container/opencv4-hf4.containerfile",
|
|
71
|
+
"container/Containerfile",
|
|
72
|
+
"container/hf4.containerfile",
|
|
73
|
+
"container/ffmpeg-hf.containerfile",
|
|
74
|
+
"container/ffmpeg-tf2.containerfile",
|
|
75
|
+
"container/generate_containers_yml.py",
|
|
76
|
+
"container/opencv4-tf2.containerfile",
|
|
77
|
+
"container/opencv4-torch2.containerfile",
|
|
78
|
+
"container/hf.containerfile",
|
|
79
|
+
"container/jdk8.containerfile",
|
|
80
|
+
"container/hf5.containerfile",
|
|
81
|
+
"container/ffmpeg.containerfile",
|
|
82
|
+
"container/ffmpeg-hf5.containerfile",
|
|
83
|
+
"container/ffmpeg-hf4.containerfile",
|
|
84
|
+
"container/opencv4-hf5.containerfile",
|
|
85
|
+
"container/ffmpeg-torch2.containerfile",
|
|
86
|
+
"container/opencv4.containerfile",
|
|
87
|
+
".github/workflows/container.yml",
|
|
88
|
+
".github/workflows/containers.yml",
|
|
89
|
+
".github/workflows/issue-close.yml",
|
|
90
|
+
".github/workflows/codecov.yml",
|
|
91
|
+
".github/workflows/release-check.yml",
|
|
92
|
+
".github/workflows/issue-infra-project.yml",
|
|
93
|
+
".github/workflows/issue-assign.yml",
|
|
94
|
+
".github/workflows/publish.yml"
|
|
95
|
+
]
|
|
96
|
+
}
|
|
@@ -174,6 +174,17 @@ from :class:`~clams.app.ClamsApp`. These names are reserved; see
|
|
|
174
174
|
- Maximum number of new tokens generated per inference call. Larger values
|
|
175
175
|
grow the KV cache linearly and add to GPU memory usage; reduce if VRAM
|
|
176
176
|
is constrained.
|
|
177
|
+
* - ``useReasoning``
|
|
178
|
+
- boolean
|
|
179
|
+
- ``false``
|
|
180
|
+
- no
|
|
181
|
+
- Request the model's reasoning ("thinking") mode. Off by default;
|
|
182
|
+
honored only by apps whose model has a distinct reasoning mode
|
|
183
|
+
(others ignore it). Much slower and more token-hungry -- the trace is
|
|
184
|
+
generated before the answer and drawn from the ``maxNewTokens`` budget,
|
|
185
|
+
and small models can loop without terminating. When honored, the trace
|
|
186
|
+
is stored in the ``modelReasoningTrace`` property of the output
|
|
187
|
+
``TextDocument``. See :ref:`promptable-reasoning`.
|
|
177
188
|
* - ``temperature``
|
|
178
189
|
- number
|
|
179
190
|
- ``0.0``
|
|
@@ -320,6 +331,52 @@ inferences, final reply returned.
|
|
|
320
331
|
``turn-taking`` is the default because it costs a single inference call
|
|
321
332
|
and is the more common multi-element pattern.
|
|
322
333
|
|
|
334
|
+
.. _promptable-reasoning:
|
|
335
|
+
|
|
336
|
+
Reasoning traces (``useReasoning``)
|
|
337
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
338
|
+
|
|
339
|
+
Some models expose a distinct reasoning ("thinking") mode that emits an
|
|
340
|
+
intermediate reasoning trace before the answer. ``useReasoning`` is the
|
|
341
|
+
standard toggle for that mode. It is inert unless the app honors it, and
|
|
342
|
+
honoring is a per-app choice with three wiring points:
|
|
343
|
+
|
|
344
|
+
- map ``useReasoning`` onto the backend's reasoning switch (for HF chat
|
|
345
|
+
templates, typically ``enable_thinking``) by overriding
|
|
346
|
+
:meth:`~clams.app.ClamsHFPromptableApp.build_template_kwargs`;
|
|
347
|
+
- split the trace from the answer with
|
|
348
|
+
:meth:`~clams.app.ClamsPromptableApp.split_tagged_reasoning_trace`
|
|
349
|
+
(inline ``<think>...</think>``-style traces only; channel-delimited
|
|
350
|
+
formats need app-specific parsing). Many chat templates prefill the
|
|
351
|
+
opening ``<think>`` into the prompt, so only the closing tag appears in
|
|
352
|
+
the output; pass ``assume_open=True`` (wired to the reasoning toggle) so
|
|
353
|
+
the trace is still recovered and a missing close tag is read as
|
|
354
|
+
unterminated reasoning (empty answer) rather than a plain answer;
|
|
355
|
+
- pass the trace to
|
|
356
|
+
:meth:`~clams.app.ClamsPromptableApp.response_to_grounded_textdocument`
|
|
357
|
+
through its ``reasoning_trace`` argument, which stores it in the
|
|
358
|
+
``modelReasoningTrace`` property of the produced ``TextDocument``,
|
|
359
|
+
separate from the document text.
|
|
360
|
+
|
|
361
|
+
An app whose model has no reasoning mode leaves ``useReasoning`` unwired;
|
|
362
|
+
callers may still set it and it has no effect, like ``topK`` under greedy
|
|
363
|
+
decoding.
|
|
364
|
+
|
|
365
|
+
Cost and failure modes
|
|
366
|
+
""""""""""""""""""""""
|
|
367
|
+
|
|
368
|
+
Reasoning is much slower and far more token-hungry than a direct answer:
|
|
369
|
+
the entire trace is generated first, from the same budget capped by
|
|
370
|
+
``maxNewTokens``. Enabling ``useReasoning`` without raising ``maxNewTokens``
|
|
371
|
+
substantially (thousands of tokens, not hundreds) risks the trace consuming
|
|
372
|
+
the whole budget, leaving the answer truncated or empty. Small reasoning
|
|
373
|
+
models (as a rule of thumb, roughly 4B parameters and under) are especially
|
|
374
|
+
prone to non-terminating "thinking loops" that never reach an answer; budget
|
|
375
|
+
generously and validate termination per model. When the trace overruns the
|
|
376
|
+
budget before closing, ``assume_open=True`` yields an empty answer plus the
|
|
377
|
+
unterminated trace -- an app should surface that (e.g. a warning and an empty
|
|
378
|
+
``TextDocument``) rather than emitting the raw reasoning as the answer.
|
|
379
|
+
|
|
323
380
|
Helpers
|
|
324
381
|
^^^^^^^
|
|
325
382
|
|
|
@@ -512,7 +569,10 @@ supplies:
|
|
|
512
569
|
* a default
|
|
513
570
|
:py:meth:`~clams.app.ClamsHFPromptableApp.build_gen_kwargs` that
|
|
514
571
|
maps the SDK promptable parameters to HF ``model.generate()``
|
|
515
|
-
kwargs
|
|
572
|
+
kwargs;
|
|
573
|
+
* a :py:meth:`~clams.app.ClamsHFPromptableApp.build_template_kwargs`
|
|
574
|
+
hook for chat-template controls, the override point for honoring
|
|
575
|
+
``useReasoning`` (see :ref:`promptable-reasoning`).
|
|
516
576
|
|
|
517
577
|
See each method's docstring for full details.
|
|
518
578
|
|
|
@@ -26,7 +26,9 @@ sys.path.insert(0, str(proj_root_dir))
|
|
|
26
26
|
|
|
27
27
|
# -- Project information -----------------------------------------------------
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
# The canonical package name — NOT derived from the checkout directory,
|
|
30
|
+
# which is `clone-repo` when the shared docs-publish workflow builds here.
|
|
31
|
+
project = 'clams-python'
|
|
30
32
|
blob_base_url = f'https://github.com/clamsproject/{project}/blob'
|
|
31
33
|
copyright = f'{datetime.date.today().year}, Brandeis LLC'
|
|
32
34
|
author = 'Brandeis LLC'
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
"``clams-python`` version","``mmif-python`` version","Target MMIF Specification"
|
|
2
|
+
`1.7.3 <https://pypi.org/project/clams-python/1.7.3/>`__,`1.5.1 <https://pypi.org/project/mmif-python/1.5.1/>`__,`1.2.0 <https://mmif.clams.ai/1.2.0/>`__
|
|
3
|
+
`1.7.2 <https://pypi.org/project/clams-python/1.7.2/>`__,`1.5.1 <https://pypi.org/project/mmif-python/1.5.1/>`__,`1.2.0 <https://mmif.clams.ai/1.2.0/>`__
|
|
2
4
|
`1.7.1 <https://pypi.org/project/clams-python/1.7.1/>`__,`1.5.1 <https://pypi.org/project/mmif-python/1.5.1/>`__,`1.2.0 <https://mmif.clams.ai/1.2.0/>`__
|
|
3
5
|
`1.7.0 <https://pypi.org/project/clams-python/1.7.0/>`__,`1.5.0 <https://pypi.org/project/mmif-python/1.5.0/>`__,`1.2.0 <https://mmif.clams.ai/1.2.0/>`__
|
|
4
6
|
`1.6.0 <https://pypi.org/project/clams-python/1.6.0/>`__,`1.4.0 <https://pypi.org/project/mmif-python/1.4.0/>`__,`1.1.1 <https://mmif.clams.ai/1.1.1/>`__
|
|
@@ -10,7 +10,7 @@ single-turn / turn-taking / user-only modes, and the
|
|
|
10
10
|
"""
|
|
11
11
|
import unittest
|
|
12
12
|
|
|
13
|
-
from mmif import AnnotationTypes, DocumentTypes, Mmif
|
|
13
|
+
from mmif import AnnotationTypes, Document, DocumentTypes, Mmif
|
|
14
14
|
|
|
15
15
|
from clams import AppMetadata, ClamsPromptableApp
|
|
16
16
|
|
|
@@ -326,6 +326,19 @@ class TestStoreResponse(unittest.TestCase):
|
|
|
326
326
|
def setUp(self):
|
|
327
327
|
self.app = make_test_app(make_metadata(call_helper=True))
|
|
328
328
|
self.mmif = Mmif(validate=False)
|
|
329
|
+
# upstream: a media document and an annotation anchored to it, which
|
|
330
|
+
# serves as the response's `source`.
|
|
331
|
+
vdoc = Document()
|
|
332
|
+
vdoc.at_type = DocumentTypes.VideoDocument
|
|
333
|
+
vdoc.id = 'v1'
|
|
334
|
+
vdoc.location = 'file:///video.mp4'
|
|
335
|
+
self.mmif.add_document(vdoc)
|
|
336
|
+
self.doc_id = vdoc.id
|
|
337
|
+
src_view = self.mmif.new_view()
|
|
338
|
+
src_view.metadata.app = 'http://upstream/1'
|
|
339
|
+
self.src = src_view.new_annotation(
|
|
340
|
+
AnnotationTypes.TimeFrame, document=vdoc.id, label='scene')
|
|
341
|
+
# the current app's view
|
|
329
342
|
self.view = self.mmif.new_view()
|
|
330
343
|
self.app.sign_view(self.view, {})
|
|
331
344
|
self.view.new_contain(DocumentTypes.TextDocument)
|
|
@@ -333,22 +346,34 @@ class TestStoreResponse(unittest.TestCase):
|
|
|
333
346
|
|
|
334
347
|
def test_happy_path_creates_textdocument_and_alignment(self):
|
|
335
348
|
td, align = self.app.response_to_grounded_textdocument(
|
|
336
|
-
self.view, source=
|
|
349
|
+
self.view, source=self.src.id, response='generated text')
|
|
337
350
|
self.assertEqual(td.text_value, 'generated text')
|
|
338
|
-
|
|
351
|
+
# the TD inherits the source annotation's document
|
|
352
|
+
self.assertEqual(td.get_property('document'), self.doc_id)
|
|
353
|
+
self.assertEqual(align.get_property('source'), self.src.id)
|
|
339
354
|
self.assertEqual(align.get_property('target'), td.id)
|
|
340
355
|
|
|
341
|
-
def
|
|
342
|
-
# no
|
|
343
|
-
self.
|
|
344
|
-
|
|
356
|
+
def test_source_without_document_raises(self):
|
|
357
|
+
# a source annotation carrying no `document` means a malformed input
|
|
358
|
+
ungrounded = self.view.new_annotation(AnnotationTypes.TimeFrame)
|
|
359
|
+
with self.assertRaises(ValueError):
|
|
360
|
+
self.app.response_to_grounded_textdocument(
|
|
361
|
+
self.view, source=ungrounded.id, response='text')
|
|
362
|
+
|
|
363
|
+
def test_reasoning_trace_none_stores_no_property(self):
|
|
364
|
+
td, _ = self.app.response_to_grounded_textdocument(
|
|
365
|
+
self.view, source=self.src.id, response='text',
|
|
345
366
|
reasoning_trace=None)
|
|
367
|
+
self.assertNotIn('modelReasoningTrace', td.properties)
|
|
346
368
|
|
|
347
|
-
def
|
|
348
|
-
|
|
349
|
-
self.
|
|
350
|
-
|
|
351
|
-
|
|
369
|
+
def test_reasoning_trace_stored_on_textdocument(self):
|
|
370
|
+
td, _ = self.app.response_to_grounded_textdocument(
|
|
371
|
+
self.view, source=self.src.id, response='the answer',
|
|
372
|
+
reasoning_trace='step 1 ... step 2 ...')
|
|
373
|
+
# trace lives in the property; the TD text stays answer-only
|
|
374
|
+
self.assertEqual(
|
|
375
|
+
td.get_property('modelReasoningTrace'), 'step 1 ... step 2 ...')
|
|
376
|
+
self.assertEqual(td.text_value, 'the answer')
|
|
352
377
|
|
|
353
378
|
# TODO (krim @ 05/28/26): this test case belongs upstream in the
|
|
354
379
|
# vocabulary type definition (the `origins`/`origination` pairing
|
|
@@ -359,11 +384,11 @@ class TestStoreResponse(unittest.TestCase):
|
|
|
359
384
|
# underlying TD.
|
|
360
385
|
def test_origins_and_origination_written_together(self):
|
|
361
386
|
td, align = self.app.response_to_grounded_textdocument(
|
|
362
|
-
self.view, source=
|
|
387
|
+
self.view, source=self.src.id, response='caption text',
|
|
363
388
|
origins=['tp1'], origination='derived')
|
|
364
389
|
self.assertEqual(td.get_property('origins'), ['tp1'])
|
|
365
390
|
self.assertEqual(td.get_property('origination'), 'derived')
|
|
366
|
-
self.assertEqual(align.get_property('source'),
|
|
391
|
+
self.assertEqual(align.get_property('source'), self.src.id)
|
|
367
392
|
self.assertEqual(align.get_property('target'), td.id)
|
|
368
393
|
|
|
369
394
|
def test_unpaired_origins_or_origination_raises(self):
|
|
@@ -377,6 +402,73 @@ class TestStoreResponse(unittest.TestCase):
|
|
|
377
402
|
self.view, source='src1', response='text', **kwargs)
|
|
378
403
|
|
|
379
404
|
|
|
405
|
+
# ---------------------------------------------------------------------------
|
|
406
|
+
# Reasoning-trace split helper
|
|
407
|
+
# ---------------------------------------------------------------------------
|
|
408
|
+
|
|
409
|
+
class TestSplitReasoningTrace(unittest.TestCase):
|
|
410
|
+
""":meth:`ClamsPromptableApp.split_tagged_reasoning_trace` (static)."""
|
|
411
|
+
|
|
412
|
+
split = staticmethod(ClamsPromptableApp.split_tagged_reasoning_trace)
|
|
413
|
+
|
|
414
|
+
def test_closed_block_splits_answer_and_trace(self):
|
|
415
|
+
answer, trace = self.split('<think>reasoning</think>The answer.')
|
|
416
|
+
self.assertEqual(answer, 'The answer.')
|
|
417
|
+
self.assertEqual(trace, 'reasoning')
|
|
418
|
+
|
|
419
|
+
def test_no_tags_returns_text_as_answer_none_trace(self):
|
|
420
|
+
answer, trace = self.split('just an answer')
|
|
421
|
+
self.assertEqual(answer, 'just an answer')
|
|
422
|
+
self.assertIsNone(trace)
|
|
423
|
+
|
|
424
|
+
def test_unterminated_block_not_overstripped(self):
|
|
425
|
+
# No closing tag (e.g. ran out of tokens mid-thought): return the
|
|
426
|
+
# raw text rather than discard a possibly-real answer.
|
|
427
|
+
raw = '<think>thinking with no close'
|
|
428
|
+
answer, trace = self.split(raw)
|
|
429
|
+
self.assertEqual(answer, raw)
|
|
430
|
+
self.assertIsNone(trace)
|
|
431
|
+
|
|
432
|
+
def test_uses_final_close_tag(self):
|
|
433
|
+
answer, trace = self.split(
|
|
434
|
+
'<think>a</think>mid<think>b</think>final')
|
|
435
|
+
self.assertEqual(answer, 'final')
|
|
436
|
+
|
|
437
|
+
def test_custom_tags(self):
|
|
438
|
+
answer, trace = self.split(
|
|
439
|
+
'[R]why[/R]done', open_tag='[R]', close_tag='[/R]')
|
|
440
|
+
self.assertEqual(answer, 'done')
|
|
441
|
+
self.assertEqual(trace, 'why')
|
|
442
|
+
|
|
443
|
+
def test_non_thinking_output_safe_to_call(self):
|
|
444
|
+
# Safe to call unconditionally even when thinking is disabled.
|
|
445
|
+
answer, trace = self.split(' plain caption ')
|
|
446
|
+
self.assertEqual(answer, 'plain caption')
|
|
447
|
+
self.assertIsNone(trace)
|
|
448
|
+
|
|
449
|
+
def test_assume_open_close_present_open_absent(self):
|
|
450
|
+
# Prefilled opening tag (e.g. Qwen3.5): only the close tag is emitted.
|
|
451
|
+
answer, trace = self.split(
|
|
452
|
+
'reasoning body</think>The answer.', assume_open=True)
|
|
453
|
+
self.assertEqual(answer, 'The answer.')
|
|
454
|
+
self.assertEqual(trace, 'reasoning body')
|
|
455
|
+
|
|
456
|
+
def test_assume_open_no_close_is_unterminated_reasoning(self):
|
|
457
|
+
# Trace overran the budget before closing: empty answer, whole text is
|
|
458
|
+
# the unterminated trace.
|
|
459
|
+
raw = 'still thinking, never closed'
|
|
460
|
+
answer, trace = self.split(raw, assume_open=True)
|
|
461
|
+
self.assertEqual(answer, '')
|
|
462
|
+
self.assertEqual(trace, raw)
|
|
463
|
+
|
|
464
|
+
def test_open_absent_default_keeps_backward_compatible(self):
|
|
465
|
+
# Without assume_open, a lone close tag is not treated as a prefilled
|
|
466
|
+
# block: answer after the close, no trace.
|
|
467
|
+
answer, trace = self.split('reasoning body</think>The answer.')
|
|
468
|
+
self.assertEqual(answer, 'The answer.')
|
|
469
|
+
self.assertIsNone(trace)
|
|
470
|
+
|
|
471
|
+
|
|
380
472
|
# ---------------------------------------------------------------------------
|
|
381
473
|
# Transport-neutral parameter casting
|
|
382
474
|
# ---------------------------------------------------------------------------
|
|
@@ -580,6 +672,56 @@ class TestHFPromptableAppClassAttrs(unittest.TestCase):
|
|
|
580
672
|
finally:
|
|
581
673
|
restore()
|
|
582
674
|
|
|
675
|
+
def test_build_template_kwargs_default_empty(self):
|
|
676
|
+
restore, _ = self._patch_load()
|
|
677
|
+
try:
|
|
678
|
+
app = self._make_subclass(analyzer_versions=self.SINGLETON_AV)()
|
|
679
|
+
self.assertEqual(app.build_template_kwargs(), {})
|
|
680
|
+
finally:
|
|
681
|
+
restore()
|
|
682
|
+
|
|
683
|
+
def test_model_load_kwargs_default_is_model_kwargs_copy(self):
|
|
684
|
+
restore, calls = self._patch_load()
|
|
685
|
+
try:
|
|
686
|
+
app = self._make_subclass(
|
|
687
|
+
analyzer_versions=self.SINGLETON_AV,
|
|
688
|
+
MODEL_KWARGS={'trust_remote_code': True},
|
|
689
|
+
)()
|
|
690
|
+
# eager singleton load forwarded the class-level MODEL_KWARGS
|
|
691
|
+
self.assertEqual(
|
|
692
|
+
calls[0]['model_kwargs'], {'trust_remote_code': True})
|
|
693
|
+
# and it's a copy, not the same object
|
|
694
|
+
self.assertIsNot(
|
|
695
|
+
app.model_load_kwargs('org/fake-model', 'deadbee'),
|
|
696
|
+
app.MODEL_KWARGS)
|
|
697
|
+
finally:
|
|
698
|
+
restore()
|
|
699
|
+
|
|
700
|
+
def test_model_load_kwargs_override_is_per_variant(self):
|
|
701
|
+
restore, calls = self._patch_load()
|
|
702
|
+
try:
|
|
703
|
+
def per_variant(self, model_id, revision):
|
|
704
|
+
kw = {'base': True}
|
|
705
|
+
if model_id.endswith('small-model'):
|
|
706
|
+
kw['quantized'] = True
|
|
707
|
+
return kw
|
|
708
|
+
|
|
709
|
+
cls = self._make_subclass(
|
|
710
|
+
analyzer_versions=self.MULTI_AV,
|
|
711
|
+
model_load_kwargs=per_variant,
|
|
712
|
+
)
|
|
713
|
+
app = cls() # multi-member: no eager load
|
|
714
|
+
self.assertEqual(len(calls), 0)
|
|
715
|
+
app.load_model('org/large-model')
|
|
716
|
+
app.load_model('org/small-model')
|
|
717
|
+
large = next(c for c in calls if c['model_id'] == 'org/large-model')
|
|
718
|
+
small = next(c for c in calls if c['model_id'] == 'org/small-model')
|
|
719
|
+
self.assertEqual(large['model_kwargs'], {'base': True})
|
|
720
|
+
self.assertEqual(
|
|
721
|
+
small['model_kwargs'], {'base': True, 'quantized': True})
|
|
722
|
+
finally:
|
|
723
|
+
restore()
|
|
724
|
+
|
|
583
725
|
|
|
584
726
|
if __name__ == '__main__':
|
|
585
727
|
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{clams_python-1.7.1 → clams_python-1.7.3}/clams/develop/templates/app/.dockerignore.template
RENAMED
|
File without changes
|
|
File without changes
|
{clams_python-1.7.1 → clams_python-1.7.3}/clams/develop/templates/app/Containerfile.template
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{clams_python-1.7.1 → clams_python-1.7.3}/clams/develop/templates/app/requirements.txt.template
RENAMED
|
File without changes
|
{clams_python-1.7.1 → clams_python-1.7.3}/clams/develop/templates/gha/for-clams-team.md.template
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{clams_python-1.7.1 → clams_python-1.7.3}/clams/develop/templates/gha/workflows/publish.yml.template
RENAMED
|
File without changes
|
{clams_python-1.7.1 → clams_python-1.7.3}/clams/develop/templates/utl-tf/__init__.py.template
RENAMED
|
File without changes
|
{clams_python-1.7.1 → clams_python-1.7.3}/clams/develop/templates/utl-tf/timeframe.py.template
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|