lambda-erp 1.1.6__tar.gz → 1.1.8__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 (114) hide show
  1. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/PKG-INFO +2 -2
  2. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/README.md +1 -1
  3. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/chat.py +39 -26
  4. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/erp_context.py +11 -0
  5. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/providers.py +37 -7
  6. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/pyproject.toml +1 -1
  7. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/.gitignore +0 -0
  8. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/LICENSE +0 -0
  9. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/__init__.py +0 -0
  10. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/attachments.py +0 -0
  11. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/auth.py +0 -0
  12. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/bootstrap.py +0 -0
  13. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/chat_links.py +0 -0
  14. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/demo_limits.py +0 -0
  15. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/deps.py +0 -0
  16. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/errors.py +0 -0
  17. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/list_values.py +0 -0
  18. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/main.py +0 -0
  19. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/mcp_oauth.py +0 -0
  20. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/oauth.py +0 -0
  21. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/pdf.py +0 -0
  22. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/pdf_contract.py +0 -0
  23. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/pdf_exports.py +0 -0
  24. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/pdf_profiles.py +0 -0
  25. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/remarks_md.py +0 -0
  26. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/routers/__init__.py +0 -0
  27. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/routers/accounting.py +0 -0
  28. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/routers/actions.py +0 -0
  29. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/routers/admin.py +0 -0
  30. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/routers/analytics.py +0 -0
  31. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/routers/availability.py +0 -0
  32. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/routers/bank_reconciliation.py +0 -0
  33. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/routers/bank_statements.py +0 -0
  34. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/routers/chat_api.py +0 -0
  35. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/routers/documents.py +0 -0
  36. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/routers/masters.py +0 -0
  37. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/routers/mcp.py +0 -0
  38. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/routers/proposals.py +0 -0
  39. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/routers/reports.py +0 -0
  40. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/routers/setup.py +0 -0
  41. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/services.py +0 -0
  42. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/templates/_proposal_totals.html +0 -0
  43. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/templates/_record_base.html +0 -0
  44. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/templates/document.html +0 -0
  45. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/templates/proposal.html +0 -0
  46. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/templates/record.html +0 -0
  47. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/time_filters.py +0 -0
  48. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/api/tool_permissions.py +0 -0
  49. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/docs/agents/README.md +0 -0
  50. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/frontend/LICENSE +0 -0
  51. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/frontend/README.md +0 -0
  52. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/frontend/src/api/client.ts +0 -0
  53. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/__init__.py +0 -0
  54. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/accounting/__init__.py +0 -0
  55. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/accounting/bank_account.py +0 -0
  56. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/accounting/bank_reconciliation.py +0 -0
  57. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/accounting/bank_statement_import.py +0 -0
  58. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/accounting/bank_transaction.py +0 -0
  59. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/accounting/budget.py +0 -0
  60. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/accounting/camt.py +0 -0
  61. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/accounting/chart_of_accounts.py +0 -0
  62. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/accounting/general_ledger.py +0 -0
  63. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/accounting/journal_entry.py +0 -0
  64. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/accounting/payment_entry.py +0 -0
  65. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/accounting/pos_invoice.py +0 -0
  66. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/accounting/purchase_invoice.py +0 -0
  67. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/accounting/revaluation.py +0 -0
  68. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/accounting/sales_invoice.py +0 -0
  69. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/accounting/settlement.py +0 -0
  70. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/accounting/setup/__init__.py +0 -0
  71. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/accounting/setup/engine.py +0 -0
  72. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/accounting/setup/pack.py +0 -0
  73. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/accounting/setup/packs/__init__.py +0 -0
  74. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/accounting/setup/packs/ch.py +0 -0
  75. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/accounting/setup/packs/de_common.py +0 -0
  76. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/accounting/setup/packs/de_skr03.py +0 -0
  77. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/accounting/setup/packs/de_skr04.py +0 -0
  78. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/accounting/setup/packs/generic.py +0 -0
  79. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/accounting/setup/profiles.py +0 -0
  80. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/accounting/setup/spine.py +0 -0
  81. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/accounting/subscription.py +0 -0
  82. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/assets/__init__.py +0 -0
  83. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/assets/asset.py +0 -0
  84. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/assets/lifecycle.py +0 -0
  85. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/assets/reservation.py +0 -0
  86. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/buying/__init__.py +0 -0
  87. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/buying/purchase_order.py +0 -0
  88. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/controllers/__init__.py +0 -0
  89. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/controllers/currency.py +0 -0
  90. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/controllers/defaults.py +0 -0
  91. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/controllers/item_price.py +0 -0
  92. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/controllers/item_prices.py +0 -0
  93. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/controllers/pricing_rule.py +0 -0
  94. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/controllers/taxes_and_totals.py +0 -0
  95. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/database.py +0 -0
  96. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/exceptions.py +0 -0
  97. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/hooks.py +0 -0
  98. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/master_integrity.py +0 -0
  99. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/model.py +0 -0
  100. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/selling/__init__.py +0 -0
  101. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/selling/proposal.py +0 -0
  102. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/selling/quotation.py +0 -0
  103. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/selling/sales_order.py +0 -0
  104. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/simulation.py +0 -0
  105. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/stock/__init__.py +0 -0
  106. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/stock/delivery_note.py +0 -0
  107. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/stock/purchase_receipt.py +0 -0
  108. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/stock/stock_entry.py +0 -0
  109. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/stock/stock_ledger.py +0 -0
  110. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/timestamps.py +0 -0
  111. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/utils.py +0 -0
  112. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/validation.py +0 -0
  113. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/lambda_erp/workflow.py +0 -0
  114. {lambda_erp-1.1.6 → lambda_erp-1.1.8}/terraform/README.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: lambda-erp
3
- Version: 1.1.6
3
+ Version: 1.1.8
4
4
  Summary: Core ERP logic - accounting, sales, purchasing, inventory
5
5
  Author: TORUS INVESTMENTS AG
6
6
  License-Expression: Apache-2.0
@@ -261,7 +261,7 @@ Open `http://localhost:5173`. Vite proxies `/api/*` to the backend.
261
261
 
262
262
  ```
263
263
  OPENAI_API_KEY=sk-...
264
- LAMBDA_ERP_REPORT_MODEL=gpt-5.6-terra # optional specialist override; default shown
264
+ LAMBDA_ERP_REPORT_MODEL=gpt-6.1-sol # optional specialist override; default shown
265
265
  LAMBDA_ERP_ADMIN_EMAIL=admin@example.com # optional, seeds the admin at boot
266
266
  LAMBDA_ERP_ADMIN_PASSWORD=... # optional, required with the line above
267
267
  LAMBDA_ERP_ADMIN_NAME=Administrator # optional, display name for the seeded admin
@@ -233,7 +233,7 @@ Open `http://localhost:5173`. Vite proxies `/api/*` to the backend.
233
233
 
234
234
  ```
235
235
  OPENAI_API_KEY=sk-...
236
- LAMBDA_ERP_REPORT_MODEL=gpt-5.6-terra # optional specialist override; default shown
236
+ LAMBDA_ERP_REPORT_MODEL=gpt-6.1-sol # optional specialist override; default shown
237
237
  LAMBDA_ERP_ADMIN_EMAIL=admin@example.com # optional, seeds the admin at boot
238
238
  LAMBDA_ERP_ADMIN_PASSWORD=... # optional, required with the line above
239
239
  LAMBDA_ERP_ADMIN_NAME=Administrator # optional, display name for the seeded admin
@@ -2860,14 +2860,14 @@ Users can attach PDFs and images (receipts, bills, contracts, screenshots) to th
2860
2860
  # ---------------------------------------------------------------------------
2861
2861
  # OpenAI report-spec specialist
2862
2862
  #
2863
- # GPT-5.6 Terra stays as the planner/orchestrator. When it decides a report needs
2863
+ # GPT-6.1 Sol acts as the planner/orchestrator. When it decides a report needs
2864
2864
  # a custom analytics view it calls the create/update custom analytics tools.
2865
- # Those handlers make a separate, narrowly prompted Terra call for the bounded
2865
+ # Those handlers make a separate, narrowly prompted GPT-6.1 Sol call for the bounded
2866
2866
  # declarative report spec. Both roles use the existing OPENAI_API_KEY.
2867
2867
  # ---------------------------------------------------------------------------
2868
2868
 
2869
2869
 
2870
- REPORT_SPECIALIST_MODEL = "gpt-5.6-terra"
2870
+ REPORT_SPECIALIST_MODEL = "gpt-6.1-sol"
2871
2871
 
2872
2872
 
2873
2873
  def _report_specialist_model() -> str:
@@ -3148,7 +3148,7 @@ def _generate_report_spec_via_openai(
3148
3148
  instructions=_REPORT_CODE_SYSTEM_PROMPT,
3149
3149
  input=attempt_input,
3150
3150
  max_output_tokens=4096,
3151
- reasoning={"effort": "low"},
3151
+ reasoning={"effort": "medium"},
3152
3152
  )
3153
3153
  # Log every call for the admin dashboard. Only public_manager rows
3154
3154
  # count against the demo cap — other roles are logged for
@@ -3275,18 +3275,18 @@ async def generate_title(
3275
3275
  # Reasoning loop
3276
3276
  # ---------------------------------------------------------------------------
3277
3277
 
3278
- # The agentic orchestrator model. Function tools on /v1/chat/completions
3279
- # require reasoning_effort="none" for the gpt-5.6 family (400 otherwise).
3280
- ORCHESTRATOR_MODEL = "gpt-5.6-terra"
3278
+ # GPT-6.1 Sol requires Responses for function tools and does not support
3279
+ # reasoning effort "none", including tool-free wrap-ups after the step limit.
3280
+ ORCHESTRATOR_MODEL = "gpt-6.1-sol"
3281
3281
 
3282
3282
 
3283
3283
  # ---------------------------------------------------------------------------
3284
- # LLM API selector: Chat Completions (default) vs Responses API
3284
+ # LLM API selector: Responses API (default) vs Chat Completions
3285
3285
  #
3286
3286
  # The Responses API (/v1/responses) is what accepts non-PDF file inputs
3287
3287
  # (Office documents via input_file), and it does reasoning natively (no
3288
3288
  # reasoning_effort="none" hack). Migrating the whole loop is risky, so it's
3289
- # behind a flag and OFF by default. To keep the blast radius tiny, ALL
3289
+ # controlled by ERP_CHAT_API. To keep the blast radius tiny, ALL
3290
3290
  # conversation state stays Chat-Completions-shaped; we translate to/from the
3291
3291
  # Responses shape only at the single orchestrator call boundary
3292
3292
  # (_orchestrator_turn), and shim the Responses output back into the
@@ -3294,6 +3294,9 @@ ORCHESTRATOR_MODEL = "gpt-5.6-terra"
3294
3294
  # ---------------------------------------------------------------------------
3295
3295
 
3296
3296
  def _use_responses_api() -> bool:
3297
+ # A legacy deployment flag must not route Sol 6.1 into an unsupported API.
3298
+ if ORCHESTRATOR_MODEL == "gpt-6.1-sol":
3299
+ return True
3297
3300
  # Default: the Responses API (needed for Office attachments + native
3298
3301
  # reasoning). Set ERP_CHAT_API=chat to fall back to Chat Completions.
3299
3302
  return os.environ.get("ERP_CHAT_API", "responses").strip().lower() != "chat"
@@ -3451,9 +3454,9 @@ def _shim_usage_from_responses(usage):
3451
3454
  return _ShimUsage(inp, out, cached)
3452
3455
 
3453
3456
 
3454
- def _orchestrator_turn(client, messages, tools, max_tokens):
3457
+ def _orchestrator_turn(client, messages, tools, max_tokens, *, tool_choice="auto"):
3455
3458
  """One orchestrator LLM turn. Returns (message, usage) shaped like Chat
3456
- Completions, using either the Chat or Responses API per ERP_CHAT_API.
3459
+ Completions. Sol 6.1 always uses Responses; compatible models honor ERP_CHAT_API.
3457
3460
  Blocking — call via asyncio.to_thread."""
3458
3461
  if _use_responses_api():
3459
3462
  instructions, input_items = _responses_request_from_chat(messages)
@@ -3462,7 +3465,7 @@ def _orchestrator_turn(client, messages, tools, max_tokens):
3462
3465
  instructions=instructions,
3463
3466
  input=input_items,
3464
3467
  tools=_responses_tools_from_chat(tools),
3465
- tool_choice="auto",
3468
+ tool_choice=tool_choice,
3466
3469
  max_output_tokens=max_tokens,
3467
3470
  reasoning={"effort": _RESPONSES_REASONING_EFFORT},
3468
3471
  )
@@ -3471,9 +3474,9 @@ def _orchestrator_turn(client, messages, tools, max_tokens):
3471
3474
  model=ORCHESTRATOR_MODEL,
3472
3475
  messages=messages,
3473
3476
  tools=tools,
3474
- tool_choice="auto",
3477
+ tool_choice=tool_choice,
3475
3478
  max_completion_tokens=max_tokens,
3476
- # gpt-5.6-terra rejects function tools on /v1/chat/completions unless
3479
+ # gpt-6-luna rejects function tools on /v1/chat/completions unless
3477
3480
  # reasoning_effort is "none" (400 otherwise).
3478
3481
  reasoning_effort="none",
3479
3482
  )
@@ -3490,10 +3493,11 @@ async def run_thinking_loop(
3490
3493
  ):
3491
3494
  """Run the agentic reasoning loop.
3492
3495
 
3493
- The orchestrator and report specialist both use OpenAI gpt-5.6-terra. When GPT decides to call
3496
+ The orchestrator and the report specialist use GPT-6.1 Sol.
3497
+ When GPT decides to call
3494
3498
  `create_custom_analytics_report` or `update_custom_analytics_report`
3495
3499
  with an intent/feedback hint, the tool handler itself delegates the
3496
- declarative-spec step to a separate narrowly prompted Terra call. We emit an
3500
+ declarative-spec step to a separate narrowly prompted GPT-6.1 Sol call. We emit an
3497
3501
  `llm_provider` event around that delegation so the UI can surface it.
3498
3502
  """
3499
3503
  openai_api_key = os.environ.get("OPENAI_API_KEY", "")
@@ -3637,8 +3641,8 @@ async def run_thinking_loop(
3637
3641
  print(f"[chat_llm] provider=openai model={model_name} session_id={session_id or '-'} iter={iteration + 1}", flush=True)
3638
3642
  await on_event({"type": "llm_provider", "provider": "openai", "model": model_name})
3639
3643
  try:
3640
- # One orchestrator turn via Chat Completions (default) or the
3641
- # Responses API (ERP_CHAT_API=responses). Returns Chat-shaped
3644
+ # One orchestrator turn via Responses (required for Sol 6.1)
3645
+ # or the legacy API for compatible models. Returns Chat-shaped
3642
3646
  # (message, usage) either way, so everything below is unchanged.
3643
3647
  message, usage = await asyncio.to_thread(
3644
3648
  _orchestrator_turn, openai_client, messages, build_tools(user_info), max_completion,
@@ -3702,7 +3706,7 @@ async def run_thinking_loop(
3702
3706
  principal_ref[0] = refresh_auth_principal(principal_ref[0])
3703
3707
  live_role = principal_ref[0].get("role") if principal_ref[0] else None
3704
3708
 
3705
- # If the orchestrator is delegating report-spec generation to Terra,
3709
+ # If the orchestrator is delegating report-spec generation to GPT-6.1 Sol,
3706
3710
  # surface the handoff in the UI.
3707
3711
  will_delegate_to_report_specialist = (
3708
3712
  tool_allowed(fn_name, live_role)
@@ -3800,10 +3804,10 @@ async def run_thinking_loop(
3800
3804
  content = "I've reached the maximum number of reasoning steps. Here's what I've done so far — please check the results above."
3801
3805
  if not demo_mode:
3802
3806
  try:
3803
- wrap = await asyncio.to_thread(
3804
- openai_client.chat.completions.create,
3805
- model=ORCHESTRATOR_MODEL,
3806
- messages=messages + [{
3807
+ wrap, usage = await asyncio.to_thread(
3808
+ _orchestrator_turn,
3809
+ openai_client,
3810
+ messages + [{
3807
3811
  "role": "user",
3808
3812
  "content": (
3809
3813
  "You hit the step limit before finishing. In the user's language, state "
@@ -3813,10 +3817,19 @@ async def run_thinking_loop(
3813
3817
  "stands alone: do NOT refer to 'the results above' or to earlier messages."
3814
3818
  ),
3815
3819
  }],
3816
- max_completion_tokens=600,
3817
- reasoning_effort="none",
3820
+ build_tools(user_info),
3821
+ 2048,
3822
+ tool_choice="none",
3823
+ )
3824
+ demo_limiter.settle(
3825
+ None, actual_cost_usd=cost_of_openai_call(ORCHESTRATOR_MODEL, usage),
3826
+ ip=client_ip or "unknown", role=user_role, provider="openai",
3827
+ model=ORCHESTRATOR_MODEL,
3828
+ prompt_tokens=int(getattr(usage, "prompt_tokens", 0) or 0) if usage else 0,
3829
+ completion_tokens=int(getattr(usage, "completion_tokens", 0) or 0) if usage else 0,
3830
+ session_id=session_id,
3818
3831
  )
3819
- wrap_text = (wrap.choices[0].message.content or "").strip()
3832
+ wrap_text = (wrap.content or "").strip()
3820
3833
  if wrap_text:
3821
3834
  content = wrap_text
3822
3835
  except Exception:
@@ -8,6 +8,17 @@ WORKFLOW_GUIDANCE = """Discover record types and inspect get_document_fields/get
8
8
  writes; use their canonical field names, required fields, link targets and PDF requirements.
9
9
  Resolve existing records by their returned name; never invent identifiers or silently choose
10
10
  between ambiguous matches. Custom document records use document tools, not master tools.
11
+ Ground every written fact in the user's message or a retrieved record for the specific
12
+ target entity. This applies to free-text notes, descriptions and activity bodies as well
13
+ as structured fields. When the user switches company, lead or document, re-establish the
14
+ target and its relationships; do not carry a person's name, employer, sender identity,
15
+ contact details or other attributes over from the previous topic without explicit evidence.
16
+ A known contact at a company is not proof that they sent an unsigned message. If the source
17
+ only identifies a team/company, attribute it to that team/company, leave the person/contact
18
+ unset and preserve the source wording. Ask only if identifying the person is necessary to
19
+ perform the requested operation; never invent a sender to fill the gap.
20
+ Keep dates, times and time zones consistent between structured fields, notes and the final
21
+ answer. Convert the source time once; if the source zone is ambiguous, ask before writing.
11
22
  Create/update, submit, cancel and convert are separate business operations. A successful
12
23
  draft creation does not imply submission, stock movement or ledger posting. Check returned
13
24
  status and warnings. Correct validation errors using the field metadata and user information.
@@ -1,8 +1,8 @@
1
1
  """Pricing data for external LLM APIs the chat orchestrator uses.
2
2
 
3
- Mirrors the shape of `lambda-web/backend/providers.py`. Only the models
4
- this app actually calls are listed — refresh this dict when model
5
- choices change. Prices are USD per 1M tokens.
3
+ Mirrors the shape of `lambda-web/backend/providers.py`. Includes active models
4
+ and GPT-6 alternatives for comparison. Prices are Standard USD per 1M tokens;
5
+ see docs/llm-pricing.md for sources and billing scope.
6
6
 
7
7
  Consumers import `cost_of_openai_call` to turn an SDK `response.usage` object
8
8
  into a USD amount. Both Chat Completions and Responses usage shapes are
@@ -19,6 +19,36 @@ from typing import Any, Optional
19
19
  # ---------------------------------------------------------------------------
20
20
 
21
21
  OPENAI_PRICING: dict[str, dict[str, Any]] = {
22
+ # Official model pages, verified 2026-09-29. Above 272K input tokens,
23
+ # the higher rates apply to the entire request (including cache hits).
24
+ "gpt-6-astra": {
25
+ "tiered": True,
26
+ "tiers": [
27
+ {"max_input_tokens": 272_000, "input": 10.00, "cached_input": 1.00, "cache_write": 12.50, "output": 50.00},
28
+ {"max_input_tokens": None, "input": 20.00, "cached_input": 2.00, "cache_write": 25.00, "output": 75.00},
29
+ ],
30
+ },
31
+ "gpt-6-sol": {
32
+ "tiered": True,
33
+ "tiers": [
34
+ {"max_input_tokens": 272_000, "input": 2.00, "cached_input": 0.20, "cache_write": 2.50, "output": 10.00},
35
+ {"max_input_tokens": None, "input": 4.00, "cached_input": 0.40, "cache_write": 5.00, "output": 15.00},
36
+ ],
37
+ },
38
+ "gpt-6-luna": {
39
+ "tiered": True,
40
+ "tiers": [
41
+ {"max_input_tokens": 272_000, "input": 0.10, "cached_input": 0.01, "cache_write": 0.125, "output": 0.50},
42
+ {"max_input_tokens": None, "input": 0.20, "cached_input": 0.02, "cache_write": 0.25, "output": 0.75},
43
+ ],
44
+ },
45
+ "gpt-6.1-sol": {
46
+ "tiered": True,
47
+ "tiers": [
48
+ {"max_input_tokens": 272_000, "input": 2.00, "cached_input": 0.10, "cache_write": 2.50, "output": 10.00},
49
+ {"max_input_tokens": None, "input": 4.00, "cached_input": 0.20, "cache_write": 5.00, "output": 15.00},
50
+ ],
51
+ },
22
52
  "gpt-5.4": {
23
53
  "tiered": True,
24
54
  "tiers": [
@@ -31,8 +61,8 @@ OPENAI_PRICING: dict[str, dict[str, Any]] = {
31
61
  "gpt-5.6-terra": {
32
62
  "tiered": True,
33
63
  "tiers": [
34
- {"max_input_tokens": 272_000, "input": 2.50, "cached_input": 0.25, "cache_write": 3.125, "output": 15.00},
35
- {"max_input_tokens": None, "input": 5.00, "cached_input": 0.50, "cache_write": 6.25, "output": 22.50},
64
+ {"max_input_tokens": 272_000, "input": 2.00, "cached_input": 0.20, "cache_write": 2.50, "output": 12.00},
65
+ {"max_input_tokens": None, "input": 4.00, "cached_input": 0.40, "cache_write": 5.00, "output": 18.00},
36
66
  ],
37
67
  },
38
68
  "gpt-4.1-nano": {"input": 0.10, "cached_input": 0.025, "output": 0.40},
@@ -54,8 +84,8 @@ TRANSCRIBE_PRICING: dict[str, dict[str, float]] = {
54
84
 
55
85
 
56
86
  # ---------------------------------------------------------------------------
57
- # Rate lookup — unknown models fall back to the most expensive model in each
58
- # family so we never under-count a call we forgot to register.
87
+ # Rate lookup — preserve the legacy gpt-5.4 estimate for unknown model IDs.
88
+ # Register a model explicitly before using it; this fallback is not a price cap.
59
89
  # ---------------------------------------------------------------------------
60
90
 
61
91
  def get_openai_rates(model: str, input_tokens: int = 0) -> dict[str, float]:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "lambda-erp"
3
- version = "1.1.6"
3
+ version = "1.1.8"
4
4
  description = "Core ERP logic - accounting, sales, purchasing, inventory"
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
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