lambda-erp 1.1.5__tar.gz → 1.1.7__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.5 → lambda_erp-1.1.7}/PKG-INFO +1 -1
  2. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/chat.py +21 -11
  3. lambda_erp-1.1.7/api/erp_context.py +61 -0
  4. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/providers.py +37 -7
  5. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/routers/mcp.py +2 -0
  6. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/tool_permissions.py +1 -1
  7. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/pyproject.toml +1 -1
  8. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/.gitignore +0 -0
  9. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/LICENSE +0 -0
  10. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/README.md +0 -0
  11. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/__init__.py +0 -0
  12. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/attachments.py +0 -0
  13. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/auth.py +0 -0
  14. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/bootstrap.py +0 -0
  15. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/chat_links.py +0 -0
  16. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/demo_limits.py +0 -0
  17. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/deps.py +0 -0
  18. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/errors.py +0 -0
  19. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/list_values.py +0 -0
  20. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/main.py +0 -0
  21. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/mcp_oauth.py +0 -0
  22. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/oauth.py +0 -0
  23. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/pdf.py +0 -0
  24. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/pdf_contract.py +0 -0
  25. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/pdf_exports.py +0 -0
  26. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/pdf_profiles.py +0 -0
  27. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/remarks_md.py +0 -0
  28. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/routers/__init__.py +0 -0
  29. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/routers/accounting.py +0 -0
  30. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/routers/actions.py +0 -0
  31. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/routers/admin.py +0 -0
  32. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/routers/analytics.py +0 -0
  33. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/routers/availability.py +0 -0
  34. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/routers/bank_reconciliation.py +0 -0
  35. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/routers/bank_statements.py +0 -0
  36. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/routers/chat_api.py +0 -0
  37. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/routers/documents.py +0 -0
  38. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/routers/masters.py +0 -0
  39. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/routers/proposals.py +0 -0
  40. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/routers/reports.py +0 -0
  41. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/routers/setup.py +0 -0
  42. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/services.py +0 -0
  43. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/templates/_proposal_totals.html +0 -0
  44. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/templates/_record_base.html +0 -0
  45. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/templates/document.html +0 -0
  46. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/templates/proposal.html +0 -0
  47. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/templates/record.html +0 -0
  48. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/time_filters.py +0 -0
  49. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/docs/agents/README.md +0 -0
  50. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/frontend/LICENSE +0 -0
  51. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/frontend/README.md +0 -0
  52. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/frontend/src/api/client.ts +0 -0
  53. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/__init__.py +0 -0
  54. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/__init__.py +0 -0
  55. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/bank_account.py +0 -0
  56. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/bank_reconciliation.py +0 -0
  57. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/bank_statement_import.py +0 -0
  58. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/bank_transaction.py +0 -0
  59. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/budget.py +0 -0
  60. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/camt.py +0 -0
  61. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/chart_of_accounts.py +0 -0
  62. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/general_ledger.py +0 -0
  63. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/journal_entry.py +0 -0
  64. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/payment_entry.py +0 -0
  65. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/pos_invoice.py +0 -0
  66. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/purchase_invoice.py +0 -0
  67. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/revaluation.py +0 -0
  68. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/sales_invoice.py +0 -0
  69. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/settlement.py +0 -0
  70. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/setup/__init__.py +0 -0
  71. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/setup/engine.py +0 -0
  72. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/setup/pack.py +0 -0
  73. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/setup/packs/__init__.py +0 -0
  74. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/setup/packs/ch.py +0 -0
  75. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/setup/packs/de_common.py +0 -0
  76. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/setup/packs/de_skr03.py +0 -0
  77. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/setup/packs/de_skr04.py +0 -0
  78. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/setup/packs/generic.py +0 -0
  79. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/setup/profiles.py +0 -0
  80. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/setup/spine.py +0 -0
  81. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/subscription.py +0 -0
  82. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/assets/__init__.py +0 -0
  83. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/assets/asset.py +0 -0
  84. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/assets/lifecycle.py +0 -0
  85. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/assets/reservation.py +0 -0
  86. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/buying/__init__.py +0 -0
  87. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/buying/purchase_order.py +0 -0
  88. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/controllers/__init__.py +0 -0
  89. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/controllers/currency.py +0 -0
  90. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/controllers/defaults.py +0 -0
  91. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/controllers/item_price.py +0 -0
  92. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/controllers/item_prices.py +0 -0
  93. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/controllers/pricing_rule.py +0 -0
  94. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/controllers/taxes_and_totals.py +0 -0
  95. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/database.py +0 -0
  96. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/exceptions.py +0 -0
  97. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/hooks.py +0 -0
  98. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/master_integrity.py +0 -0
  99. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/model.py +0 -0
  100. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/selling/__init__.py +0 -0
  101. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/selling/proposal.py +0 -0
  102. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/selling/quotation.py +0 -0
  103. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/selling/sales_order.py +0 -0
  104. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/simulation.py +0 -0
  105. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/stock/__init__.py +0 -0
  106. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/stock/delivery_note.py +0 -0
  107. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/stock/purchase_receipt.py +0 -0
  108. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/stock/stock_entry.py +0 -0
  109. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/stock/stock_ledger.py +0 -0
  110. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/timestamps.py +0 -0
  111. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/utils.py +0 -0
  112. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/validation.py +0 -0
  113. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/workflow.py +0 -0
  114. {lambda_erp-1.1.5 → lambda_erp-1.1.7}/terraform/README.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: lambda-erp
3
- Version: 1.1.5
3
+ Version: 1.1.7
4
4
  Summary: Core ERP logic - accounting, sales, purchasing, inventory
5
5
  Author: TORUS INVESTMENTS AG
6
6
  License-Expression: Apache-2.0
@@ -28,6 +28,7 @@ from openai import OpenAI
28
28
 
29
29
  from api.time_filters import TIME_FILTER_SCHEMA, time_filter_fields
30
30
  from api import services
31
+ from api.erp_context import WORKFLOW_GUIDANCE, get_erp_context
31
32
  from api.chat_links import with_record_view_urls
32
33
  from api.tool_permissions import tool_allowed, tool_permission_error
33
34
  from api.demo_limits import (
@@ -473,6 +474,11 @@ DOCUMENT_SLUGS = [
473
474
  MASTER_TYPES = ["customer", "supplier", "item", "warehouse", "account", "company", "cost-center"]
474
475
 
475
476
  TOOLS = [
477
+ {"type": "function", "function": {
478
+ "name": "get_erp_context",
479
+ "description": "Discover this ERP's live document/master types, relationships, PDF capabilities and workflow guidance. Read this before using an unfamiliar ERP. No LLM is invoked. Use field tools for detailed schemas and tools/list for permitted operations.",
480
+ "parameters": {"type": "object", "properties": {}, "additionalProperties": False},
481
+ }},
476
482
  {
477
483
  "type": "function",
478
484
  "function": {
@@ -2244,6 +2250,7 @@ def _handle_apply_company_setup(args):
2244
2250
 
2245
2251
 
2246
2252
  TOOL_HANDLERS = {
2253
+ "get_erp_context": get_erp_context,
2247
2254
  "list_documents": _handle_list_documents,
2248
2255
  "get_document_fields": _handle_get_document_fields,
2249
2256
  "generate_document_pdf": _handle_generate_document_pdf,
@@ -2478,6 +2485,8 @@ If `jurisdiction.is_fallback` is true, tell the user their country isn't localiz
2478
2485
 
2479
2486
  return f"""You are an ERP assistant for Lambda ERP. Today's date is {date.today().isoformat()}.
2480
2487
 
2488
+ {WORKFLOW_GUIDANCE}
2489
+
2481
2490
  You help users manage their business by creating documents, looking up data, and running reports — all through natural conversation.
2482
2491
 
2483
2492
  ## Enforced document requirements (generated from backend rule metadata)
@@ -2851,14 +2860,14 @@ Users can attach PDFs and images (receipts, bills, contracts, screenshots) to th
2851
2860
  # ---------------------------------------------------------------------------
2852
2861
  # OpenAI report-spec specialist
2853
2862
  #
2854
- # GPT-5.6 Terra stays as the planner/orchestrator. When it decides a report needs
2863
+ # GPT-6 Luna acts as the planner/orchestrator. When it decides a report needs
2855
2864
  # a custom analytics view it calls the create/update custom analytics tools.
2856
- # 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
2857
2866
  # declarative report spec. Both roles use the existing OPENAI_API_KEY.
2858
2867
  # ---------------------------------------------------------------------------
2859
2868
 
2860
2869
 
2861
- REPORT_SPECIALIST_MODEL = "gpt-5.6-terra"
2870
+ REPORT_SPECIALIST_MODEL = "gpt-6.1-sol"
2862
2871
 
2863
2872
 
2864
2873
  def _report_specialist_model() -> str:
@@ -3267,17 +3276,17 @@ async def generate_title(
3267
3276
  # ---------------------------------------------------------------------------
3268
3277
 
3269
3278
  # The agentic orchestrator model. Function tools on /v1/chat/completions
3270
- # require reasoning_effort="none" for the gpt-5.6 family (400 otherwise).
3271
- ORCHESTRATOR_MODEL = "gpt-5.6-terra"
3279
+ # require reasoning_effort="none" for GPT-6 Luna (400 otherwise).
3280
+ ORCHESTRATOR_MODEL = "gpt-6-luna"
3272
3281
 
3273
3282
 
3274
3283
  # ---------------------------------------------------------------------------
3275
- # LLM API selector: Chat Completions (default) vs Responses API
3284
+ # LLM API selector: Responses API (default) vs Chat Completions
3276
3285
  #
3277
3286
  # The Responses API (/v1/responses) is what accepts non-PDF file inputs
3278
3287
  # (Office documents via input_file), and it does reasoning natively (no
3279
3288
  # reasoning_effort="none" hack). Migrating the whole loop is risky, so it's
3280
- # 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
3281
3290
  # conversation state stays Chat-Completions-shaped; we translate to/from the
3282
3291
  # Responses shape only at the single orchestrator call boundary
3283
3292
  # (_orchestrator_turn), and shim the Responses output back into the
@@ -3464,7 +3473,7 @@ def _orchestrator_turn(client, messages, tools, max_tokens):
3464
3473
  tools=tools,
3465
3474
  tool_choice="auto",
3466
3475
  max_completion_tokens=max_tokens,
3467
- # gpt-5.6-terra rejects function tools on /v1/chat/completions unless
3476
+ # gpt-6-luna rejects function tools on /v1/chat/completions unless
3468
3477
  # reasoning_effort is "none" (400 otherwise).
3469
3478
  reasoning_effort="none",
3470
3479
  )
@@ -3481,10 +3490,11 @@ async def run_thinking_loop(
3481
3490
  ):
3482
3491
  """Run the agentic reasoning loop.
3483
3492
 
3484
- The orchestrator and report specialist both use OpenAI gpt-5.6-terra. When GPT decides to call
3493
+ The orchestrator uses GPT-6 Luna; the report specialist uses GPT-6.1 Sol.
3494
+ When GPT decides to call
3485
3495
  `create_custom_analytics_report` or `update_custom_analytics_report`
3486
3496
  with an intent/feedback hint, the tool handler itself delegates the
3487
- declarative-spec step to a separate narrowly prompted Terra call. We emit an
3497
+ declarative-spec step to a separate narrowly prompted GPT-6.1 Sol call. We emit an
3488
3498
  `llm_provider` event around that delegation so the UI can surface it.
3489
3499
  """
3490
3500
  openai_api_key = os.environ.get("OPENAI_API_KEY", "")
@@ -3693,7 +3703,7 @@ async def run_thinking_loop(
3693
3703
  principal_ref[0] = refresh_auth_principal(principal_ref[0])
3694
3704
  live_role = principal_ref[0].get("role") if principal_ref[0] else None
3695
3705
 
3696
- # If the orchestrator is delegating report-spec generation to Terra,
3706
+ # If the orchestrator is delegating report-spec generation to GPT-6.1 Sol,
3697
3707
  # surface the handoff in the UI.
3698
3708
  will_delegate_to_report_specialist = (
3699
3709
  tool_allowed(fn_name, live_role)
@@ -0,0 +1,61 @@
1
+ """Shared, deterministic guidance for the native chat and direct ERP clients."""
2
+
3
+ from api import services
4
+ from api.pdf_profiles import pdf_metadata
5
+
6
+
7
+ WORKFLOW_GUIDANCE = """Discover record types and inspect get_document_fields/get_master_fields before
8
+ writes; use their canonical field names, required fields, link targets and PDF requirements.
9
+ Resolve existing records by their returned name; never invent identifiers or silently choose
10
+ between ambiguous matches. Custom document records use document tools, not master tools.
11
+ Create/update, submit, cancel and convert are separate business operations. A successful
12
+ draft creation does not imply submission, stock movement or ledger posting. Check returned
13
+ status and warnings. Correct validation errors using the field metadata and user information.
14
+ Use list/search filters and pagination; an empty or partial search is not proof of zero
15
+ stock. Use query_dataset/get_report for totals and stock, with the requested scope and dates.
16
+ Generate a fresh PDF after edits. Only a successful artifact descriptor proves generation;
17
+ download that artifact, check its SHA-256 and deliver it through the calling application's
18
+ attachment mechanism. Do not invent download URLs or reuse a stale document attachment.
19
+ After a write timeout the outcome is unknown: inspect the affected records before retrying.
20
+ Do not automatically replay the operation through another tool or the chat API.
21
+ """
22
+
23
+
24
+ def get_erp_context(args=None):
25
+ """No LLM, secrets, session state or business-record snapshots are involved."""
26
+ documents = []
27
+ for slug, doctype in sorted(services.SLUG_TO_DOCTYPE.items()):
28
+ cls = services.DOCUMENT_CLASSES.get(doctype)
29
+ meta = services.CHAT_DOCTYPES.get(slug, {})
30
+ documents.append({
31
+ "type": slug, "doctype": doctype,
32
+ "description": meta.get("description", doctype),
33
+ "key_fields": meta.get("fields", []),
34
+ "links": getattr(cls, "LINK_FIELDS", None) or {},
35
+ "pdf": {k: v for k, v in pdf_metadata(doctype).items()
36
+ if k in {"supported", "reason", "kind", "required_fields", "required_any"}},
37
+ "describe_tool": "get_document_fields",
38
+ })
39
+ masters = []
40
+ for slug, (doctype, display_field) in sorted(services.MASTER_TABLES.items()):
41
+ meta = services.MASTER_METADATA.get(slug, {})
42
+ masters.append({
43
+ "type": slug, "doctype": doctype, "key_field": "name",
44
+ "display_field": display_field,
45
+ "description": meta.get("description", doctype),
46
+ "key_fields": meta.get("fields", []),
47
+ "describe_tool": "get_master_fields",
48
+ })
49
+ return {
50
+ "contract_version": 1,
51
+ "instructions": WORKFLOW_GUIDANCE,
52
+ "documents": documents,
53
+ "masters": masters,
54
+ "capability_source": "tools/list is the caller's permitted operation catalogue; never infer write permission from this type list.",
55
+ "limitations": [
56
+ "Native ERP chat history and attachments are not the calling application's history or attachments.",
57
+ "File-ID operations require files uploaded to this ERP; external application attachment IDs cannot be substituted.",
58
+ "Chat-based custom analytics generation and guided company setup are not exposed through MCP. Use an explicit chat fallback if enabled.",
59
+ "Registered extension actions may call external services or models; inspect each action's description.",
60
+ ],
61
+ }
@@ -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]:
@@ -49,6 +49,7 @@ _EXCLUDE = {
49
49
  }
50
50
 
51
51
  _READ_ONLY = {
52
+ 'get_erp_context',
52
53
  'list_documents', 'get_document_fields', 'get_document', 'get_master_fields',
53
54
  'search_masters', 'get_report', 'get_current_time', 'query_dataset',
54
55
  'preview_bank_statement_attachments', 'list_bank_reconciliation_queue',
@@ -167,6 +168,7 @@ def _handle(msg: dict, user: dict):
167
168
  else PROTOCOL_VERSION),
168
169
  "capabilities": {"tools": {"listChanged": False}},
169
170
  "serverInfo": {"name": "lambda-erp", "version": get_app_version()},
171
+ "instructions": "Call get_erp_context for live record types and workflow guidance, then inspect field schemas before writes. tools/list reflects your permissions. No ERP chat is invoked by the standard business tools.",
170
172
  })
171
173
  if method in ("notifications/initialized", "notifications/cancelled"):
172
174
  return None
@@ -15,7 +15,7 @@ _ADMINS = frozenset({"admin"})
15
15
 
16
16
  TOOL_ROLES = {
17
17
  **dict.fromkeys((
18
- "list_documents", "get_document_fields", "get_document", "generate_document_pdf",
18
+ "get_erp_context", "list_documents", "get_document_fields", "get_document", "generate_document_pdf",
19
19
  "get_master_fields", "search_masters", "get_report", "get_current_time",
20
20
  "retrieve_chat_history", "list_chat_attachments", "retrieve_chat_attachment",
21
21
  "preview_bank_statement_attachments", "query_dataset",
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "lambda-erp"
3
- version = "1.1.5"
3
+ version = "1.1.7"
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
File without changes