clams-python 1.7.1__tar.gz → 1.7.2__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.
Files changed (102) hide show
  1. {clams_python-1.7.1 → clams_python-1.7.2}/CHANGELOG.md +5 -0
  2. {clams_python-1.7.1/clams_python.egg-info → clams_python-1.7.2}/PKG-INFO +1 -1
  3. {clams_python-1.7.1 → clams_python-1.7.2}/clams/app/__init__.py +134 -15
  4. {clams_python-1.7.1 → clams_python-1.7.2/clams_python.egg-info}/PKG-INFO +1 -1
  5. {clams_python-1.7.1 → clams_python-1.7.2}/clams_python.egg-info/SOURCES.txt +3 -0
  6. clams_python-1.7.2/clams_python.egg-info/scm_file_list.json +96 -0
  7. clams_python-1.7.2/clams_python.egg-info/scm_version.json +8 -0
  8. clams_python-1.7.2/documentation/autodoc/clams.backends.rst +15 -0
  9. {clams_python-1.7.1 → clams_python-1.7.2}/documentation/target-versions.csv +1 -0
  10. {clams_python-1.7.1 → clams_python-1.7.2}/tests/test_promptable.py +134 -14
  11. {clams_python-1.7.1 → clams_python-1.7.2}/.github/workflows/codecov.yml +0 -0
  12. {clams_python-1.7.1 → clams_python-1.7.2}/.github/workflows/container.yml +0 -0
  13. {clams_python-1.7.1 → clams_python-1.7.2}/.github/workflows/containers.yml +0 -0
  14. {clams_python-1.7.1 → clams_python-1.7.2}/.github/workflows/issue-assign.yml +0 -0
  15. {clams_python-1.7.1 → clams_python-1.7.2}/.github/workflows/issue-close.yml +0 -0
  16. {clams_python-1.7.1 → clams_python-1.7.2}/.github/workflows/issue-infra-project.yml +0 -0
  17. {clams_python-1.7.1 → clams_python-1.7.2}/.github/workflows/publish.yml +0 -0
  18. {clams_python-1.7.1 → clams_python-1.7.2}/.github/workflows/release-check.yml +0 -0
  19. {clams_python-1.7.1 → clams_python-1.7.2}/.gitignore +0 -0
  20. {clams_python-1.7.1 → clams_python-1.7.2}/.pytype.cfg +0 -0
  21. {clams_python-1.7.1 → clams_python-1.7.2}/CONTRIBUTING.md +0 -0
  22. {clams_python-1.7.1 → clams_python-1.7.2}/LICENSE +0 -0
  23. {clams_python-1.7.1 → clams_python-1.7.2}/README.md +0 -0
  24. {clams_python-1.7.1 → clams_python-1.7.2}/build-tools/build.py +0 -0
  25. {clams_python-1.7.1 → clams_python-1.7.2}/build-tools/clean.py +0 -0
  26. {clams_python-1.7.1 → clams_python-1.7.2}/build-tools/docs.py +0 -0
  27. {clams_python-1.7.1 → clams_python-1.7.2}/build-tools/prep_release.py +0 -0
  28. {clams_python-1.7.1 → clams_python-1.7.2}/build-tools/publish.py +0 -0
  29. {clams_python-1.7.1 → clams_python-1.7.2}/build-tools/test.py +0 -0
  30. {clams_python-1.7.1 → clams_python-1.7.2}/clams/__init__.py +0 -0
  31. {clams_python-1.7.1 → clams_python-1.7.2}/clams/appmetadata/__init__.py +0 -0
  32. {clams_python-1.7.1 → clams_python-1.7.2}/clams/backends/__init__.py +0 -0
  33. {clams_python-1.7.1 → clams_python-1.7.2}/clams/backends/hf.py +0 -0
  34. {clams_python-1.7.1 → clams_python-1.7.2}/clams/develop/__init__.py +0 -0
  35. {clams_python-1.7.1 → clams_python-1.7.2}/clams/develop/templates/app/.dockerignore.template +0 -0
  36. {clams_python-1.7.1 → clams_python-1.7.2}/clams/develop/templates/app/.gitignore.template +0 -0
  37. {clams_python-1.7.1 → clams_python-1.7.2}/clams/develop/templates/app/Containerfile.template +0 -0
  38. {clams_python-1.7.1 → clams_python-1.7.2}/clams/develop/templates/app/LICENSE.template +0 -0
  39. {clams_python-1.7.1 → clams_python-1.7.2}/clams/develop/templates/app/README.md.template +0 -0
  40. {clams_python-1.7.1 → clams_python-1.7.2}/clams/develop/templates/app/app.py.template +0 -0
  41. {clams_python-1.7.1 → clams_python-1.7.2}/clams/develop/templates/app/cli.py.template +0 -0
  42. {clams_python-1.7.1 → clams_python-1.7.2}/clams/develop/templates/app/metadata.py.template +0 -0
  43. {clams_python-1.7.1 → clams_python-1.7.2}/clams/develop/templates/app/requirements.txt.template +0 -0
  44. {clams_python-1.7.1 → clams_python-1.7.2}/clams/develop/templates/gha/for-clams-team.md.template +0 -0
  45. {clams_python-1.7.1 → clams_python-1.7.2}/clams/develop/templates/gha/workflows/issue-apps-project.yml.template +0 -0
  46. {clams_python-1.7.1 → clams_python-1.7.2}/clams/develop/templates/gha/workflows/issue-assign.yml.template +0 -0
  47. {clams_python-1.7.1 → clams_python-1.7.2}/clams/develop/templates/gha/workflows/issue-close.yml.template +0 -0
  48. {clams_python-1.7.1 → clams_python-1.7.2}/clams/develop/templates/gha/workflows/publish.yml.template +0 -0
  49. {clams_python-1.7.1 → clams_python-1.7.2}/clams/develop/templates/utl-tf/__init__.py.template +0 -0
  50. {clams_python-1.7.1 → clams_python-1.7.2}/clams/develop/templates/utl-tf/timeframe.py.template +0 -0
  51. {clams_python-1.7.1 → clams_python-1.7.2}/clams/envelop/__init__.py +0 -0
  52. {clams_python-1.7.1 → clams_python-1.7.2}/clams/restify/__init__.py +0 -0
  53. {clams_python-1.7.1 → clams_python-1.7.2}/clams/serve/__init__.py +0 -0
  54. {clams_python-1.7.1 → clams_python-1.7.2}/clams/ver/__init__.py +0 -0
  55. {clams_python-1.7.1 → clams_python-1.7.2}/clams_python.egg-info/dependency_links.txt +0 -0
  56. {clams_python-1.7.1 → clams_python-1.7.2}/clams_python.egg-info/entry_points.txt +0 -0
  57. {clams_python-1.7.1 → clams_python-1.7.2}/clams_python.egg-info/requires.txt +0 -0
  58. {clams_python-1.7.1 → clams_python-1.7.2}/clams_python.egg-info/top_level.txt +0 -0
  59. {clams_python-1.7.1 → clams_python-1.7.2}/container/Containerfile +0 -0
  60. {clams_python-1.7.1 → clams_python-1.7.2}/container/ffmpeg-hf.containerfile +0 -0
  61. {clams_python-1.7.1 → clams_python-1.7.2}/container/ffmpeg-hf4.containerfile +0 -0
  62. {clams_python-1.7.1 → clams_python-1.7.2}/container/ffmpeg-hf5.containerfile +0 -0
  63. {clams_python-1.7.1 → clams_python-1.7.2}/container/ffmpeg-tf2.containerfile +0 -0
  64. {clams_python-1.7.1 → clams_python-1.7.2}/container/ffmpeg-torch2.containerfile +0 -0
  65. {clams_python-1.7.1 → clams_python-1.7.2}/container/ffmpeg.containerfile +0 -0
  66. {clams_python-1.7.1 → clams_python-1.7.2}/container/generate_containers_yml.py +0 -0
  67. {clams_python-1.7.1 → clams_python-1.7.2}/container/hf.containerfile +0 -0
  68. {clams_python-1.7.1 → clams_python-1.7.2}/container/hf4.containerfile +0 -0
  69. {clams_python-1.7.1 → clams_python-1.7.2}/container/hf5.containerfile +0 -0
  70. {clams_python-1.7.1 → clams_python-1.7.2}/container/jdk8.containerfile +0 -0
  71. {clams_python-1.7.1 → clams_python-1.7.2}/container/opencv4-hf.containerfile +0 -0
  72. {clams_python-1.7.1 → clams_python-1.7.2}/container/opencv4-hf4.containerfile +0 -0
  73. {clams_python-1.7.1 → clams_python-1.7.2}/container/opencv4-hf5.containerfile +0 -0
  74. {clams_python-1.7.1 → clams_python-1.7.2}/container/opencv4-tf2.containerfile +0 -0
  75. {clams_python-1.7.1 → clams_python-1.7.2}/container/opencv4-torch2.containerfile +0 -0
  76. {clams_python-1.7.1 → clams_python-1.7.2}/container/opencv4.containerfile +0 -0
  77. {clams_python-1.7.1 → clams_python-1.7.2}/container/tf2.containerfile +0 -0
  78. {clams_python-1.7.1 → clams_python-1.7.2}/container/torch2.containerfile +0 -0
  79. {clams_python-1.7.1 → clams_python-1.7.2}/documentation/app-baseclasses.rst +0 -0
  80. {clams_python-1.7.1 → clams_python-1.7.2}/documentation/appdirectory.rst +0 -0
  81. {clams_python-1.7.1 → clams_python-1.7.2}/documentation/appmetadata.rst +0 -0
  82. {clams_python-1.7.1 → clams_python-1.7.2}/documentation/autodoc/clams.app.rst +0 -0
  83. {clams_python-1.7.1 → clams_python-1.7.2}/documentation/autodoc/clams.appmetadata.rst +0 -0
  84. {clams_python-1.7.1 → clams_python-1.7.2}/documentation/autodoc/clams.mmif_utils.rst +0 -0
  85. {clams_python-1.7.1 → clams_python-1.7.2}/documentation/autodoc/clams.restify.rst +0 -0
  86. {clams_python-1.7.1 → clams_python-1.7.2}/documentation/clamsapp.rst +0 -0
  87. {clams_python-1.7.1 → clams_python-1.7.2}/documentation/cli.rst +0 -0
  88. {clams_python-1.7.1 → clams_python-1.7.2}/documentation/conf.py +0 -0
  89. {clams_python-1.7.1 → clams_python-1.7.2}/documentation/gpu-apps.rst +0 -0
  90. {clams_python-1.7.1 → clams_python-1.7.2}/documentation/index.rst +0 -0
  91. {clams_python-1.7.1 → clams_python-1.7.2}/documentation/input-output.rst +0 -0
  92. {clams_python-1.7.1 → clams_python-1.7.2}/documentation/introduction.rst +0 -0
  93. {clams_python-1.7.1 → clams_python-1.7.2}/documentation/modules.rst +0 -0
  94. {clams_python-1.7.1 → clams_python-1.7.2}/documentation/runtime-params.rst +0 -0
  95. {clams_python-1.7.1 → clams_python-1.7.2}/documentation/tutorial.md +0 -0
  96. {clams_python-1.7.1 → clams_python-1.7.2}/pyproject.toml +0 -0
  97. {clams_python-1.7.1 → clams_python-1.7.2}/setup.cfg +0 -0
  98. {clams_python-1.7.1 → clams_python-1.7.2}/tests/metadata.py +0 -0
  99. {clams_python-1.7.1 → clams_python-1.7.2}/tests/pillow-logo.png +0 -0
  100. {clams_python-1.7.1 → clams_python-1.7.2}/tests/test_backends_hf.py +0 -0
  101. {clams_python-1.7.1 → clams_python-1.7.2}/tests/test_clamsapp.py +0 -0
  102. {clams_python-1.7.1 → clams_python-1.7.2}/tests/test_envelope.py +0 -0
@@ -1,4 +1,9 @@
1
1
 
2
+ ## releasing 1.7.1 (2026-06-19)
3
+ ### Overview
4
+ Updates the `mmif-python` SDK dependency to 1.5.1 to incorporate a bugfix in VDH, with some other minor documentation generation improvements.
5
+
6
+
2
7
  ## releasing 1.7.0 (2026-05-29)
3
8
  ### Overview
4
9
  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.1
3
+ Version: 1.7.2
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
@@ -1005,8 +1005,10 @@ class ClamsPromptableApp(ClamsApp):
1005
1005
  """
1006
1006
  Persist a single LLM text response into a view. Writes one
1007
1007
  ``TextDocument`` (containing the response) plus possible
1008
- grounding via an ``Alignment`` annotation and ``origins`` /
1009
- ``origination`` properties on the TD.
1008
+ grounding via an ``Alignment`` annotation and ``origins`` /
1009
+ ``origination`` properties on the TD. The TD also inherits the
1010
+ ``document`` of its ``source`` annotation, so it records which
1011
+ medium the response describes.
1010
1012
 
1011
1013
  The two grounding link kinds are semantically distinct:
1012
1014
 
@@ -1028,7 +1030,12 @@ class ClamsPromptableApp(ClamsApp):
1028
1030
  ``Alignment`` first if needed.
1029
1031
  :param source: ``id`` of the annotation to record as the
1030
1032
  cross-modal anchor of the response (see above).
1031
- :param response: the text generated by the model.
1033
+ :param response: the text to store as the TextDocument's
1034
+ value. Typically the model's generated answer, but the
1035
+ caller MAY pass a post-processed form (e.g. with a
1036
+ reasoning block stripped, whitespace trimmed, or otherwise
1037
+ reformatted); this method stores whatever it is given,
1038
+ verbatim, as the ``text``.
1032
1039
  :param origins: optional list of ``id``\\s of annotations the
1033
1040
  response was *derived* from. Must be paired with
1034
1041
  ``origination``.
@@ -1039,15 +1046,24 @@ class ClamsPromptableApp(ClamsApp):
1039
1046
  ``origins``.
1040
1047
  :param reasoning_trace: optional model-side reasoning trace
1041
1048
  (a chain-of-thought / scratchpad string, NOT a Python
1042
- traceback). NOT YET SUPPORTED -- passing a non-``None``
1043
- value raises :py:class:`NotImplementedError`. Storage
1044
- convention is still being decided at
1045
- clamsproject/clams-python#263.
1049
+ traceback). When non-``None``, it is stored on the
1050
+ returned TextDocument as the ``modelReasoningTrace``
1051
+ property, kept OUT of the ``text`` (which holds whatever
1052
+ ``response`` the caller passed). The point is separation,
1053
+ not any claim about the ``text`` being verbatim: storing a
1054
+ trace adds no annotation and no new type, so the type-level
1055
+ output shape is identical whether or not a trace is stored.
1056
+ Pass ``None`` (the default) to store no trace. The property
1057
+ name is provisional pending the
1058
+ vocabulary convention (clamsproject/clams-vocabulary#11).
1046
1059
  :return: ``(TextDocument, Alignment)`` tuple of the new
1047
1060
  annotations.
1048
1061
  :raises ValueError: if exactly one of ``origins`` /
1049
- ``origination`` is set; they must be supplied together
1050
- or both omitted.
1062
+ ``origination`` is set (they must be supplied together or
1063
+ both omitted), or if the ``source`` annotation carries no
1064
+ ``document`` to ground the response to (a malformed input).
1065
+ :raises KeyError: if ``source`` does not resolve to an
1066
+ annotation in the MMIF.
1051
1067
  """
1052
1068
  if bool(origins) != bool(origination):
1053
1069
  raise ValueError(
@@ -1056,6 +1072,20 @@ class ClamsPromptableApp(ClamsApp):
1056
1072
  f"origins={origins!r}, origination={origination!r}."
1057
1073
  )
1058
1074
  td = view.new_textdocument(text=response)
1075
+ # Ground the TextDocument to the medium it describes: copy `document`
1076
+ # from the source annotation (e.g. the parent TimeFrame's video) onto
1077
+ # the TD, so it records which document the response pertains to. A
1078
+ # grounded response must be traceable to a document, so a source that
1079
+ # carries no `document` means the upstream MMIF is malformed -- raise
1080
+ # rather than silently drop the grounding.
1081
+ source_ann = view._parent_mmif[source]
1082
+ source_doc = source_ann.get_property('document')
1083
+ if not source_doc:
1084
+ raise ValueError(
1085
+ f"source annotation {source!r} has no `document` property to "
1086
+ f"ground the response to; the input MMIF is malformed."
1087
+ )
1088
+ td.add_property('document', source_doc)
1059
1089
  if origins:
1060
1090
  td.add_property('origins', origins)
1061
1091
  td.add_property('origination', origination)
@@ -1065,13 +1095,52 @@ class ClamsPromptableApp(ClamsApp):
1065
1095
  target=td.id,
1066
1096
  )
1067
1097
  if reasoning_trace is not None:
1068
- raise NotImplementedError(
1069
- "Reasoning-trace storage convention is not yet defined; "
1070
- "tracked at clamsproject/clams-python#263."
1071
- )
1098
+ # Store the trace as a property on the TD rather than a separate
1099
+ # document: keeps the type-level output shape invariant (same
1100
+ # annotation, same `contains`, same graph) whether or not a trace is
1101
+ # present -- a strict enrichment, independent of whatever `response`
1102
+ # the caller put in `text`. Property name provisional pending
1103
+ # clamsproject/clams-vocabulary#11.
1104
+ td.add_property('modelReasoningTrace', reasoning_trace)
1072
1105
  return td, align
1073
1106
 
1074
-
1107
+ @staticmethod
1108
+ def split_tagged_reasoning_trace(
1109
+ text: str,
1110
+ open_tag: str = '<think>',
1111
+ close_tag: str = '</think>',
1112
+ ) -> Tuple[str, Optional[str]]:
1113
+ """
1114
+ Split a reasoning model's output into ``(answer, trace)`` for the
1115
+ inline XML-tag style: a single reasoning block delimited by
1116
+ ``open_tag`` / ``close_tag`` (default ``<think>`` / ``</think>``)
1117
+ embedded in the decoded text, e.g. as emitted by DeepSeek-R1 or
1118
+ Qwen3.
1119
+
1120
+ It does NOT handle models that isolate reasoning in a separate
1121
+ token-delimited channel rather than an inline tag block (e.g.
1122
+ gpt-oss, Gemma 4); those need their own parsing in the app. Trace
1123
+ handling is the app's responsibility either way -- call this when
1124
+ the model fits the inline-tag style, or parse it yourself.
1125
+
1126
+ ``answer`` is everything after the final ``close_tag`` (stripped);
1127
+ ``trace`` is the text between the tags, or ``None`` when no closed
1128
+ block is present -- so it is safe to call on non-reasoning output,
1129
+ which returns ``(text.strip(), None)``.
1130
+
1131
+ :param text: raw decoded model output.
1132
+ :param open_tag: opening marker of the inline reasoning block.
1133
+ :param close_tag: closing marker of the inline reasoning block.
1134
+ :return: ``(answer, trace_or_None)``.
1135
+ """
1136
+ ci = text.rfind(close_tag)
1137
+ if ci == -1:
1138
+ return text.strip(), None
1139
+ answer = text[ci + len(close_tag):].strip()
1140
+ oi = text.find(open_tag)
1141
+ trace = (text[oi + len(open_tag):ci].strip()
1142
+ if oi != -1 and oi < ci else None)
1143
+ return answer, trace
1075
1144
  class ClamsHFPromptableApp(ClamsPromptableApp):
1076
1145
  """
1077
1146
  Base class for promptable CLAMS apps backed by a local
@@ -1289,7 +1358,7 @@ class ClamsHFPromptableApp(ClamsPromptableApp):
1289
1358
  dtype=self.DTYPE,
1290
1359
  padding_side=self.PADDING_SIDE,
1291
1360
  revision=revision,
1292
- model_kwargs=self.MODEL_KWARGS,
1361
+ model_kwargs=self.model_load_kwargs(model_id, revision),
1293
1362
  processor_kwargs=self.PROCESSOR_KWARGS,
1294
1363
  )
1295
1364
  self.logger.info(f"HF model loaded on {triple[2]}")
@@ -1297,6 +1366,32 @@ class ClamsHFPromptableApp(ClamsPromptableApp):
1297
1366
  self.processor, self.model, self.device = triple
1298
1367
  return triple
1299
1368
 
1369
+ def model_load_kwargs(self, model_id: str, revision: str) -> dict:
1370
+ """
1371
+ The ``model_kwargs`` forwarded to
1372
+ :func:`~clams.backends.hf.load_hf_model` (and on to
1373
+ ``from_pretrained``) for a SPECIFIC family member. The base
1374
+ implementation returns a copy of the class-level
1375
+ :py:attr:`MODEL_KWARGS`, applied uniformly to every variant.
1376
+
1377
+ Override when a family mixes per-variant load requirements that
1378
+ a single class-level ``MODEL_KWARGS`` cannot express -- e.g. a
1379
+ ``analyzer_versions`` family whose members span full-precision,
1380
+ FP8, and GPTQ-Int4 builds, where only the quantized members need
1381
+ a ``quantization_config``::
1382
+
1383
+ def model_load_kwargs(self, model_id, revision):
1384
+ kw = super().model_load_kwargs(model_id, revision)
1385
+ if model_id.endswith('-GPTQ-Int4'):
1386
+ kw['quantization_config'] = ... # gptq config
1387
+ return kw
1388
+
1389
+ :param model_id: resolved HF model id (no ``@revision`` suffix).
1390
+ :param revision: resolved commit/revision being loaded.
1391
+ :return: ``model_kwargs`` dict for this variant.
1392
+ """
1393
+ return dict(self.MODEL_KWARGS or {})
1394
+
1300
1395
  def generate(
1301
1396
  self,
1302
1397
  prompt: List[str],
@@ -1341,6 +1436,7 @@ class ClamsHFPromptableApp(ClamsPromptableApp):
1341
1436
  if n == 0:
1342
1437
  return []
1343
1438
  gen_kwargs = self.build_gen_kwargs(**generation_params)
1439
+ template_kwargs = self.build_template_kwargs(**generation_params)
1344
1440
  try:
1345
1441
  conversations = [
1346
1442
  self.build_conversation(
@@ -1357,6 +1453,7 @@ class ClamsHFPromptableApp(ClamsPromptableApp):
1357
1453
  return_dict=True,
1358
1454
  padding=True,
1359
1455
  return_tensors="pt",
1456
+ **template_kwargs,
1360
1457
  )
1361
1458
  inputs = inputs.to(self.device)
1362
1459
  if (self.DTYPE is not None
@@ -1405,6 +1502,28 @@ class ClamsHFPromptableApp(ClamsPromptableApp):
1405
1502
  })
1406
1503
  return gen_kwargs
1407
1504
 
1505
+ def build_template_kwargs(self, **generation_params) -> dict:
1506
+ """
1507
+ Extra keyword arguments to pass to
1508
+ ``processor.apply_chat_template`` in :py:meth:`generate`, beyond
1509
+ the SDK-fixed ones (``add_generation_prompt``, ``tokenize``,
1510
+ ``return_dict``, ``padding``, ``return_tensors``). The base
1511
+ implementation returns ``{}``.
1512
+
1513
+ Override to inject model-specific chat-template controls without
1514
+ having to reimplement :py:meth:`generate`. Common cases:
1515
+
1516
+ * ``{'enable_thinking': False}`` to run a reasoning ("thinking")
1517
+ model in non-thinking mode;
1518
+ * ``{'tools': [...]}`` or ``{'documents': [...]}`` for tool-use /
1519
+ RAG chat templates.
1520
+
1521
+ Keys MUST NOT collide with the SDK-fixed kwargs above. Unknown
1522
+ keys are passed through to the template's render context, where
1523
+ a template that does not reference them simply ignores them.
1524
+ """
1525
+ return {}
1526
+
1408
1527
 
1409
1528
  class ParameterCaster(object):
1410
1529
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clams-python
3
- Version: 1.7.1
3
+ Version: 1.7.2
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
@@ -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
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "1.7.2",
3
+ "distance": 0,
4
+ "node": "g874f55164c482ad4c7896c6bfd8d6a69649eb66b",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-07-07"
8
+ }
@@ -0,0 +1,15 @@
1
+ clams.backends package
2
+ ======================
3
+
4
+ .. automodule:: clams.backends
5
+
6
+ Submodules
7
+ ----------
8
+
9
+ clams.backends.hf
10
+ ^^^^^^^^^^^^^^^^^
11
+
12
+ .. automodule:: clams.backends.hf
13
+ :members:
14
+ :undoc-members:
15
+ :show-inheritance:
@@ -1,4 +1,5 @@
1
1
  "``clams-python`` version","``mmif-python`` version","Target MMIF Specification"
2
+ `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
3
  `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
4
  `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
5
  `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='src1', response='generated text')
349
+ self.view, source=self.src.id, response='generated text')
337
350
  self.assertEqual(td.text_value, 'generated text')
338
- self.assertEqual(align.get_property('source'), 'src1')
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 test_reasoning_trace_none_does_not_raise(self):
342
- # no exception
343
- self.app.response_to_grounded_textdocument(
344
- self.view, source='src1', response='text',
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 test_reasoning_trace_not_none_raises_not_implemented(self):
348
- with self.assertRaises(NotImplementedError):
349
- self.app.response_to_grounded_textdocument(
350
- self.view, source='src1', response='text',
351
- reasoning_trace='intermediate reasoning')
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='tf1', response='caption text',
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'), 'tf1')
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,51 @@ 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
+
380
450
  # ---------------------------------------------------------------------------
381
451
  # Transport-neutral parameter casting
382
452
  # ---------------------------------------------------------------------------
@@ -580,6 +650,56 @@ class TestHFPromptableAppClassAttrs(unittest.TestCase):
580
650
  finally:
581
651
  restore()
582
652
 
653
+ def test_build_template_kwargs_default_empty(self):
654
+ restore, _ = self._patch_load()
655
+ try:
656
+ app = self._make_subclass(analyzer_versions=self.SINGLETON_AV)()
657
+ self.assertEqual(app.build_template_kwargs(), {})
658
+ finally:
659
+ restore()
660
+
661
+ def test_model_load_kwargs_default_is_model_kwargs_copy(self):
662
+ restore, calls = self._patch_load()
663
+ try:
664
+ app = self._make_subclass(
665
+ analyzer_versions=self.SINGLETON_AV,
666
+ MODEL_KWARGS={'trust_remote_code': True},
667
+ )()
668
+ # eager singleton load forwarded the class-level MODEL_KWARGS
669
+ self.assertEqual(
670
+ calls[0]['model_kwargs'], {'trust_remote_code': True})
671
+ # and it's a copy, not the same object
672
+ self.assertIsNot(
673
+ app.model_load_kwargs('org/fake-model', 'deadbee'),
674
+ app.MODEL_KWARGS)
675
+ finally:
676
+ restore()
677
+
678
+ def test_model_load_kwargs_override_is_per_variant(self):
679
+ restore, calls = self._patch_load()
680
+ try:
681
+ def per_variant(self, model_id, revision):
682
+ kw = {'base': True}
683
+ if model_id.endswith('small-model'):
684
+ kw['quantized'] = True
685
+ return kw
686
+
687
+ cls = self._make_subclass(
688
+ analyzer_versions=self.MULTI_AV,
689
+ model_load_kwargs=per_variant,
690
+ )
691
+ app = cls() # multi-member: no eager load
692
+ self.assertEqual(len(calls), 0)
693
+ app.load_model('org/large-model')
694
+ app.load_model('org/small-model')
695
+ large = next(c for c in calls if c['model_id'] == 'org/large-model')
696
+ small = next(c for c in calls if c['model_id'] == 'org/small-model')
697
+ self.assertEqual(large['model_kwargs'], {'base': True})
698
+ self.assertEqual(
699
+ small['model_kwargs'], {'base': True, 'quantized': True})
700
+ finally:
701
+ restore()
702
+
583
703
 
584
704
  if __name__ == '__main__':
585
705
  unittest.main()
File without changes
File without changes
File without changes
File without changes
File without changes