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.
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/PKG-INFO +1 -1
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/chat.py +21 -11
- lambda_erp-1.1.7/api/erp_context.py +61 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/providers.py +37 -7
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/routers/mcp.py +2 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/tool_permissions.py +1 -1
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/pyproject.toml +1 -1
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/.gitignore +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/LICENSE +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/README.md +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/__init__.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/attachments.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/auth.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/bootstrap.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/chat_links.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/demo_limits.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/deps.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/errors.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/list_values.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/main.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/mcp_oauth.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/oauth.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/pdf.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/pdf_contract.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/pdf_exports.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/pdf_profiles.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/remarks_md.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/routers/__init__.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/routers/accounting.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/routers/actions.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/routers/admin.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/routers/analytics.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/routers/availability.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/routers/bank_reconciliation.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/routers/bank_statements.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/routers/chat_api.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/routers/documents.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/routers/masters.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/routers/proposals.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/routers/reports.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/routers/setup.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/services.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/templates/_proposal_totals.html +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/templates/_record_base.html +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/templates/document.html +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/templates/proposal.html +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/templates/record.html +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/api/time_filters.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/docs/agents/README.md +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/frontend/LICENSE +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/frontend/README.md +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/frontend/src/api/client.ts +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/__init__.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/__init__.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/bank_account.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/bank_reconciliation.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/bank_statement_import.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/bank_transaction.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/budget.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/camt.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/chart_of_accounts.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/general_ledger.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/journal_entry.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/payment_entry.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/pos_invoice.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/purchase_invoice.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/revaluation.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/sales_invoice.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/settlement.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/setup/__init__.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/setup/engine.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/setup/pack.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/setup/packs/__init__.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/setup/packs/ch.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/setup/packs/de_common.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/setup/packs/de_skr03.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/setup/packs/de_skr04.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/setup/packs/generic.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/setup/profiles.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/setup/spine.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/accounting/subscription.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/assets/__init__.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/assets/asset.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/assets/lifecycle.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/assets/reservation.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/buying/__init__.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/buying/purchase_order.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/controllers/__init__.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/controllers/currency.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/controllers/defaults.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/controllers/item_price.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/controllers/item_prices.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/controllers/pricing_rule.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/controllers/taxes_and_totals.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/database.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/exceptions.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/hooks.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/master_integrity.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/model.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/selling/__init__.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/selling/proposal.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/selling/quotation.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/selling/sales_order.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/simulation.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/stock/__init__.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/stock/delivery_note.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/stock/purchase_receipt.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/stock/stock_entry.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/stock/stock_ledger.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/timestamps.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/utils.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/validation.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/lambda_erp/workflow.py +0 -0
- {lambda_erp-1.1.5 → lambda_erp-1.1.7}/terraform/README.md +0 -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-
|
|
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
|
|
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-
|
|
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
|
|
3271
|
-
ORCHESTRATOR_MODEL = "gpt-
|
|
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:
|
|
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
|
-
#
|
|
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-
|
|
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
|
|
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
|
|
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
|
|
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`.
|
|
4
|
-
|
|
5
|
-
|
|
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.
|
|
35
|
-
{"max_input_tokens": None, "input":
|
|
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 —
|
|
58
|
-
#
|
|
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",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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
|