licos-dev-sdk 0.4.8__tar.gz → 0.5.0__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 (54) hide show
  1. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/PKG-INFO +2 -2
  2. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/pyproject.toml +2 -2
  3. licos_dev_sdk-0.5.0/src/licos_dev_sdk/langchain_reasoning.py +32 -0
  4. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/src/licos_dev_sdk/model.py +13 -3
  5. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/tests/test_model.py +24 -0
  6. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/.gitignore +0 -0
  7. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/DOCX.md +0 -0
  8. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/src/licos_dev_sdk/__init__.py +0 -0
  9. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/src/licos_dev_sdk/_usage_identity.py +0 -0
  10. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/src/licos_dev_sdk/_utils.py +0 -0
  11. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/src/licos_dev_sdk/archive.py +0 -0
  12. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/src/licos_dev_sdk/chart.py +0 -0
  13. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/src/licos_dev_sdk/data.py +0 -0
  14. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/src/licos_dev_sdk/diagram.py +0 -0
  15. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/src/licos_dev_sdk/document.py +0 -0
  16. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/src/licos_dev_sdk/document_common.py +0 -0
  17. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/src/licos_dev_sdk/document_docx.py +0 -0
  18. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/src/licos_dev_sdk/document_docx_edit.py +0 -0
  19. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/src/licos_dev_sdk/document_docx_expectations.py +0 -0
  20. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/src/licos_dev_sdk/document_docx_inspect.py +0 -0
  21. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/src/licos_dev_sdk/document_docx_layout.py +0 -0
  22. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/src/licos_dev_sdk/document_docx_package.py +0 -0
  23. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/src/licos_dev_sdk/document_docx_preservation.py +0 -0
  24. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/src/licos_dev_sdk/document_docx_structure.py +0 -0
  25. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/src/licos_dev_sdk/document_docx_table.py +0 -0
  26. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/src/licos_dev_sdk/document_docx_table_actions.py +0 -0
  27. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/src/licos_dev_sdk/document_read.py +0 -0
  28. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/src/licos_dev_sdk/document_table_export.py +0 -0
  29. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/src/licos_dev_sdk/image.py +0 -0
  30. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/src/licos_dev_sdk/observability.py +0 -0
  31. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/src/licos_dev_sdk/pdf_inspect.py +0 -0
  32. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/src/licos_dev_sdk/ppt_master_runtime.py +0 -0
  33. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/src/licos_dev_sdk/presentation.py +0 -0
  34. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/src/licos_dev_sdk/presentation_canvas.py +0 -0
  35. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/src/licos_dev_sdk/presentation_inspect.py +0 -0
  36. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/src/licos_dev_sdk/spreadsheet.py +0 -0
  37. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/src/licos_dev_sdk/spreadsheet_edit.py +0 -0
  38. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/src/licos_dev_sdk/spreadsheet_html.py +0 -0
  39. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/src/licos_dev_sdk/spreadsheet_inspect.py +0 -0
  40. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/src/licos_dev_sdk/web.py +0 -0
  41. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/tests/test_document_read.py +0 -0
  42. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/tests/test_document_spreadsheet.py +0 -0
  43. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/tests/test_document_table_export.py +0 -0
  44. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/tests/test_docx_inspect.py +0 -0
  45. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/tests/test_docx_structure.py +0 -0
  46. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/tests/test_docx_table_edit.py +0 -0
  47. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/tests/test_html_docx_features.py +0 -0
  48. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/tests/test_langchain_protocol.py +0 -0
  49. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/tests/test_observability.py +0 -0
  50. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/tests/test_output_paths.py +0 -0
  51. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/tests/test_ppt_master_runtime.py +0 -0
  52. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/tests/test_presentation.py +0 -0
  53. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/tests/test_spreadsheet_html_edit.py +0 -0
  54. {licos_dev_sdk-0.4.8 → licos_dev_sdk-0.5.0}/tests/test_usage_identity.py +0 -0
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: licos-dev-sdk
3
- Version: 0.4.8
3
+ Version: 0.5.0
4
4
  Summary: LICOS Dev SDK - file generation and model capability clients
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: beautifulsoup4>=4.12
7
7
  Requires-Dist: docxtpl>=0.16
8
8
  Requires-Dist: graphviz>=0.20
9
9
  Requires-Dist: jinja2>=3.1
10
- Requires-Dist: licos-platform-sdk>=0.3.2
10
+ Requires-Dist: licos-platform-sdk>=0.6.1
11
11
  Requires-Dist: matplotlib>=3.9
12
12
  Requires-Dist: mistune>=3.0
13
13
  Requires-Dist: openpyxl>=3.1
@@ -4,11 +4,11 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "licos-dev-sdk"
7
- version = "0.4.8"
7
+ version = "0.5.0"
8
8
  description = "LICOS Dev SDK - file generation and model capability clients"
9
9
  requires-python = ">=3.10"
10
10
  dependencies = [
11
- "licos-platform-sdk>=0.3.2",
11
+ "licos-platform-sdk>=0.6.1",
12
12
  "weasyprint>=62.0",
13
13
  "python-docx>=1.1",
14
14
  "docxtpl>=0.16",
@@ -0,0 +1,32 @@
1
+ """Preserve provider reasoning fields that ChatOpenAI intentionally omits."""
2
+ from __future__ import annotations
3
+
4
+ from typing import Any
5
+
6
+
7
+ def reasoning_chat_model_class(base_class: type) -> type:
8
+ class ReasoningChatOpenAI(base_class):
9
+ def _convert_chunk_to_generation_chunk(
10
+ self, chunk: dict[str, Any], default_chunk_class: type, base_generation_info: dict[str, Any] | None,
11
+ ) -> Any:
12
+ generation = super()._convert_chunk_to_generation_chunk(
13
+ chunk, default_chunk_class, base_generation_info,
14
+ )
15
+ choices = chunk.get("choices") or chunk.get("chunk", {}).get("choices", [])
16
+ if generation is not None and choices:
17
+ delta = choices[0].get("delta") or {}
18
+ reasoning = delta.get("reasoning_content")
19
+ if isinstance(reasoning, str):
20
+ generation.message.additional_kwargs["reasoning_content"] = reasoning
21
+ return generation
22
+
23
+ def _create_chat_result(self, response: Any, generation_info: dict[str, Any] | None = None) -> Any:
24
+ result = super()._create_chat_result(response, generation_info)
25
+ raw = response if isinstance(response, dict) else response.model_dump()
26
+ for choice, generation in zip(raw["choices"], result.generations):
27
+ reasoning = choice["message"].get("reasoning_content")
28
+ if isinstance(reasoning, str):
29
+ generation.message.additional_kwargs["reasoning_content"] = reasoning
30
+ return result
31
+
32
+ return ReasoningChatOpenAI
@@ -7,11 +7,13 @@ import json
7
7
  import logging
8
8
  import mimetypes
9
9
  import queue
10
+ import re
10
11
  import threading
11
12
  import time
12
13
  import uuid
13
14
  from contextvars import ContextVar
14
15
  from dataclasses import dataclass, field, replace
16
+ from decimal import Decimal
15
17
  from pathlib import Path
16
18
  from typing import Any, Callable, Iterator, Sequence
17
19
  from urllib import error as urlerror
@@ -486,6 +488,7 @@ def create_chat_openai(
486
488
  timeout: int | None = None,
487
489
  streaming: bool = True,
488
490
  use_responses_api: bool | None = None,
491
+ preserve_reasoning: bool = False,
489
492
  default_headers: dict[str, str] | None = None,
490
493
  **kwargs: Any,
491
494
  ) -> Any:
@@ -496,6 +499,8 @@ def create_chat_openai(
496
499
  key, so deployed projects do not need LLM_MODEL/OPENAI_MODEL env injection.
497
500
  A catalog `response_url` selects Responses by default. Set
498
501
  `use_responses_api=False` to use Chat Completions explicitly.
502
+ `preserve_reasoning=True` keeps Chat Completions providers' `reasoning_content`
503
+ in LangChain message metadata for a caller that explicitly forwards it.
499
504
  """
500
505
  try:
501
506
  from langchain_core.callbacks import BaseCallbackHandler
@@ -569,6 +574,10 @@ def create_chat_openai(
569
574
  ),
570
575
  **kwargs,
571
576
  }
577
+ if preserve_reasoning and not use_responses_api:
578
+ from .langchain_reasoning import reasoning_chat_model_class
579
+
580
+ return reasoning_chat_model_class(ChatOpenAI)(**chat_kwargs)
572
581
  return ChatOpenAI(**chat_kwargs)
573
582
 
574
583
 
@@ -2511,10 +2520,11 @@ def _int_field(data: dict[str, Any], names: Sequence[str]) -> int | None:
2511
2520
  parsed = int(value)
2512
2521
  return parsed if parsed > 0 else None
2513
2522
  if isinstance(value, str):
2514
- try:
2515
- parsed = int(value.strip())
2516
- except ValueError:
2523
+ match = re.fullmatch(r"\s*(\d+(?:\.\d+)?)\s*([KMG]?)\s*", value, flags=re.IGNORECASE)
2524
+ if match is None:
2517
2525
  continue
2526
+ multiplier = {"": 1, "K": 1000, "M": 1000000, "G": 1000000000}[match.group(2).upper()]
2527
+ parsed = int(Decimal(match.group(1)) * multiplier)
2518
2528
  return parsed if parsed > 0 else None
2519
2529
  return None
2520
2530
 
@@ -821,6 +821,25 @@ class ModelSdkTests(unittest.TestCase):
821
821
  self.assertEqual(endpoint.max_input_length, 128000)
822
822
  self.assertEqual(endpoint.max_output_length, 64000)
823
823
 
824
+ def test_resolve_llm_endpoint_parses_catalog_limit_suffixes(self) -> None:
825
+ def fake_urlopen(req: Any, timeout: int = 0) -> _FakeResponse:
826
+ if req.full_url == "http://platform.example/api/v1/internal/auth/ai-user-token":
827
+ return _FakeResponse({"code": 0, "success": True, "data": {"accessToken": "user-token"}})
828
+ if _is_available_models_request(req):
829
+ return _available_models_response(req)
830
+ if req.full_url.startswith("http://platform.example/api/v1/admin/workspaces/models/detail?"):
831
+ detail = _model_detail_payload()
832
+ detail["data"].update(contextLength="1M", maxInputLength="991K", maxOutputLength="128K")
833
+ return _FakeResponse(detail)
834
+ raise AssertionError(req.full_url)
835
+
836
+ with mock.patch.object(model.request, "urlopen", fake_urlopen):
837
+ endpoint = model.resolve_llm_endpoint()
838
+
839
+ self.assertEqual(endpoint.context_length, 1000000)
840
+ self.assertEqual(endpoint.max_input_length, 991000)
841
+ self.assertEqual(endpoint.max_output_length, 128000)
842
+
824
843
  def test_resolve_llm_endpoint_accepts_explicit_model(self) -> None:
825
844
  def fake_urlopen(req: Any, timeout: int = 0) -> _FakeResponse:
826
845
  if req.full_url == "http://platform.example/api/v1/internal/auth/ai-user-token":
@@ -862,9 +881,13 @@ class ModelSdkTests(unittest.TestCase):
862
881
  with (
863
882
  mock.patch.object(model.request, "urlopen", fake_urlopen),
864
883
  mock.patch.dict(sys.modules, {"langchain_openai": fake_langchain_openai}),
884
+ mock.patch("httpx.Client", return_value=object()),
885
+ mock.patch("httpx.AsyncClient", return_value=object()),
865
886
  ):
866
887
  chat_model = model.create_chat_openai(
867
888
  model="auto",
889
+ preserve_reasoning=True,
890
+ use_responses_api=False,
868
891
  temperature=0.2,
869
892
  max_completion_tokens=100000,
870
893
  timeout=30,
@@ -875,6 +898,7 @@ class ModelSdkTests(unittest.TestCase):
875
898
  )
876
899
 
877
900
  self.assertIsInstance(chat_model, FakeChatOpenAI)
901
+ self.assertIsNot(type(chat_model), FakeChatOpenAI)
878
902
  self.assertEqual(captured["exchange_headers"]["Authorization"], "Bearer ai-agent-token")
879
903
  self.assertEqual(captured["catalog_headers"]["Authorization"], "Bearer user-token")
880
904
  self.assertEqual(captured["detail_headers"]["Authorization"], "Bearer user-token")
File without changes
File without changes