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