Connector calls, semantic rules, and DuckDB memory keep long runs stable, and stream retry stops chats from stalling. Co-authored-by: Cursor <cursoragent@cursor.com>
111 lines
4.5 KiB
Python
111 lines
4.5 KiB
Python
"""Prompt text for attached native connectors. Names and descriptions only."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from typing import Any, Dict, Iterable, List
|
|
|
|
from kamidso.native_connectors.models import ConnectorFunction
|
|
from kamidso.native_connectors.registry import get_registry
|
|
|
|
_PATH_MENTION = re.compile(
|
|
r"(?i)\b(?:GET|POST|PUT|PATCH|DELETE|HEAD)\s+/api/\S+|/api/\S+"
|
|
)
|
|
|
|
_DOMAIN_LABELS = {
|
|
"activity": "Activities",
|
|
"case": "Cases",
|
|
"company": "Companies",
|
|
"deal": "Deals",
|
|
"invoice": "Invoices",
|
|
"location": "Locations",
|
|
"note": "Notes",
|
|
"person": "People",
|
|
"pipeline": "Pipelines",
|
|
"product": "Products",
|
|
"user": "Users",
|
|
}
|
|
|
|
_RULES = """Native connector functions are predefined.
|
|
Never invent a connector function.
|
|
Use connector_call to fetch business data.
|
|
Do not use code_interpreter to call the external API.
|
|
Use code_interpreter only after connector_call when computation, dataframe operations, merging, charts, statistics or export are required.
|
|
If connector_call returns validation_error:
|
|
1. Read the returned schema.
|
|
2. Fix the body.
|
|
3. Retry the SAME function.
|
|
Do not create a different function to work around a malformed request.
|
|
A validation_error means the selected function exists and is appropriate enough to validate.
|
|
Prefer server-provided totals and aggregates from connector responses.
|
|
Do not paginate through all records merely to count them.
|
|
Didar data is fetched with connector_call. Database data uses database(). Excel and CSV files use code_interpreter.
|
|
For Didar management questions, call a semantic operation and pass a business body. Do not build Didar Criteria.
|
|
sales_summary answers فروش in a period and means deals that became Won in that period. Example body: {"period": "مهر ۱۴۰۵"}.
|
|
deals_created answers معاملات in a period and means deals registered in that period. State the period basis.
|
|
Other semantic operations: won_deals, lost_deals, pending_pipeline_snapshot, sales_by_owner, sales_over_time.
|
|
Keep every accuracy warning in the answer, including mode, date basis, currency, and panel parity.
|
|
Current deal status comes only from Status. ChangeToWonTime never overrides Pending.
|
|
Response.List is one page. Totals come from aggregates such as WonDealsCount, PendingDealsCount, and LostDealsCount.
|
|
Do not invent get_leads, search_leads, or list_leads. lead_count stays disabled.
|
|
Do not sum amounts that have different CurrencyId values, and do not guess a currency name.
|
|
On raw search calls, From and Limit are top-level siblings of Criteria, not fields inside Criteria.
|
|
"""
|
|
|
|
|
|
def _domain_label(domain: str) -> str:
|
|
return _DOMAIN_LABELS.get(domain, domain.replace("_", " ").title() or "Other")
|
|
|
|
|
|
def _public_description(description: str) -> str:
|
|
cleaned = _PATH_MENTION.sub("", description)
|
|
return " ".join(cleaned.split())
|
|
|
|
|
|
def _function_lines(functions: Iterable[ConnectorFunction]) -> List[str]:
|
|
grouped: Dict[str, List[ConnectorFunction]] = {}
|
|
for function in functions:
|
|
grouped.setdefault(function.domain or "other", []).append(function)
|
|
lines: List[str] = []
|
|
for domain, items in grouped.items():
|
|
lines.append(_domain_label(domain))
|
|
for function in items:
|
|
lines.append(f"- {function.name} — {_public_description(function.description)}")
|
|
lines.append("")
|
|
return lines
|
|
|
|
|
|
def render_native_prompt(instances: List[Dict[str, Any]]) -> str:
|
|
if not instances:
|
|
return ""
|
|
registry = get_registry()
|
|
blocks: List[str] = [
|
|
"## Connected Business Apps",
|
|
"",
|
|
_RULES.strip(),
|
|
"",
|
|
]
|
|
by_type: Dict[str, List[Dict[str, Any]]] = {}
|
|
for instance in instances:
|
|
by_type.setdefault(str(instance.get("connector_type") or ""), []).append(instance)
|
|
for connector_type, group in by_type.items():
|
|
if not registry.contains(connector_type):
|
|
continue
|
|
provider = registry.get(connector_type)
|
|
if provider.guidance:
|
|
blocks.append(provider.guidance)
|
|
blocks.append("")
|
|
for instance in group:
|
|
blocks.extend(
|
|
[
|
|
f"### {instance.get('display_name') or instance.get('connector_id')}",
|
|
f"Provider: {provider.display_name}",
|
|
"Access: Read only",
|
|
"",
|
|
"Available functions:",
|
|
"",
|
|
]
|
|
)
|
|
blocks.extend(_function_lines(provider.enabled_reads()))
|
|
return "\n".join(blocks).strip()
|