mirror of
https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools.git
synced 2026-06-17 23:09:35 +00:00
Revolutionary AI system for autonomous B2B partnerships, negotiations, and deals: Models (strategic_deal.py - 238 lines): - CompanyProfile: Rich Saudi company profiles with CR, capabilities, needs - StrategicDeal: Full deal lifecycle (discovery → negotiation → close) - DealMatch: AI-generated company matches with scoring Services (4 files, ~2,060 lines): - company_profiler.py: Profile creation, AI enrichment, needs/capability analysis - deal_matcher.py: 6-dimension scoring, semantic matching, barter chain discovery - deal_negotiator.py: Multi-round Arabic negotiation with cultural awareness - deal_agent.py: Autonomous outreach via WhatsApp/LinkedIn/Email API (strategic_deals.py - 681 lines, 16 endpoints): - Profile management + AI enrichment - Match discovery + approval - Deal lifecycle (create → negotiate → proposal → term sheet → close) - Barter chain scanning - Analytics dashboard Deal types: partnership, distribution, franchise, JV, referral, acquisition, barter Channels: WhatsApp (primary), LinkedIn, Email Languages: Arabic (Saudi dialect) + English Cultural: Saudi negotiation norms, relationship-first, face-saving https://claude.ai/code/session_01LsnvBa7HwF5hs99VZbgLGj
480 lines
19 KiB
Python
480 lines
19 KiB
Python
"""
|
|
Deal Negotiator — Autonomous AI negotiator for B2B deals.
|
|
المفاوض الذكي: مفاوض آلي بالذكاء الاصطناعي للصفقات بين الشركات
|
|
"""
|
|
|
|
import json
|
|
import logging
|
|
from dataclasses import dataclass, field
|
|
from datetime import datetime, timezone
|
|
from typing import Optional
|
|
|
|
from pydantic import BaseModel
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.models.strategic_deal import (
|
|
StrategicDeal, CompanyProfile, DealStatus, DealType,
|
|
)
|
|
from app.services.llm.provider import get_llm
|
|
|
|
logger = logging.getLogger("dealix.strategic_deals.negotiator")
|
|
|
|
|
|
# ── Models ───────────────────────────────────────────────────────────────────
|
|
|
|
|
|
class NegotiationStrategy(BaseModel):
|
|
"""Strategy configuration for autonomous negotiation."""
|
|
target_terms: dict = {} # Ideal outcome
|
|
acceptable_range: dict = {} # Min/max for each variable
|
|
walk_away_point: dict = {} # Absolute limits / deal breakers
|
|
priorities: list[str] = [] # Ordered from most to least important
|
|
style: str = "collaborative" # collaborative, competitive, accommodating
|
|
|
|
|
|
@dataclass
|
|
class NegotiationRound:
|
|
"""Result of a single negotiation round."""
|
|
round_number: int = 0
|
|
action: str = "" # opening_offer, counter_offer, acceptance, rejection, escalation
|
|
our_terms: dict = field(default_factory=dict)
|
|
their_terms: dict = field(default_factory=dict)
|
|
message_ar: str = ""
|
|
message_en: str = ""
|
|
concessions_made: list[str] = field(default_factory=list)
|
|
concessions_gained: list[str] = field(default_factory=list)
|
|
within_range: bool = True
|
|
confidence: float = 0.0
|
|
timestamp: str = ""
|
|
|
|
|
|
# ── Escalation thresholds ────────────────────────────────────────────────────
|
|
|
|
ESCALATION_VALUE_SAR = 500_000 # Deals above this need human oversight
|
|
MAX_AUTO_ROUNDS = 5 # After this many rounds, escalate
|
|
STALL_THRESHOLD = 3 # Same terms repeated this many times = stall
|
|
|
|
|
|
class DealNegotiator:
|
|
"""
|
|
Autonomous AI negotiator that handles B2B deal negotiations.
|
|
Respects Saudi business culture: relationship-first, patience, mutual respect.
|
|
مفاوض ذكي يحترم ثقافة الأعمال السعودية
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.llm = get_llm()
|
|
|
|
# ── Start Negotiation ────────────────────────────────────────────────────
|
|
|
|
async def start_negotiation(
|
|
self,
|
|
deal_id,
|
|
strategy: NegotiationStrategy,
|
|
db: AsyncSession,
|
|
) -> NegotiationRound:
|
|
"""
|
|
Generate opening offer based on strategy and Saudi negotiation culture.
|
|
إنشاء العرض الأولي بناءً على الاستراتيجية وثقافة التفاوض السعودية
|
|
"""
|
|
deal = await self._load_deal(deal_id, db)
|
|
|
|
initiator = await self._load_profile(deal.initiator_profile_id, db)
|
|
target_name = deal.target_company_name or "الطرف الآخر"
|
|
if deal.target_profile_id:
|
|
target = await self._load_profile(deal.target_profile_id, db)
|
|
target_name = target.company_name if target else target_name
|
|
|
|
context = f"""Deal: {deal.deal_title}
|
|
Deal type: {deal.deal_type}
|
|
Our company: {initiator.company_name}
|
|
Target company: {target_name}
|
|
Our offer: {deal.our_offer or 'not specified'}
|
|
Our need: {deal.our_need or 'not specified'}
|
|
Strategy style: {strategy.style}
|
|
Target terms: {json.dumps(strategy.target_terms, ensure_ascii=False)}
|
|
Priorities: {', '.join(strategy.priorities)}"""
|
|
|
|
style_guidance = {
|
|
"collaborative": "ابدأ بعرض عادل ومتوازن يظهر الرغبة في شراكة طويلة المدى",
|
|
"competitive": "ابدأ بعرض طموح لكن معقول مع ترك مساحة للتفاوض",
|
|
"accommodating": "ابدأ بعرض سخي يظهر حسن النية والرغبة في بناء علاقة",
|
|
}
|
|
|
|
system_prompt = f"""أنت مفاوض أعمال سعودي محترف. أنشئ عرضاً أولياً للصفقة.
|
|
|
|
التوجيه: {style_guidance.get(strategy.style, style_guidance['collaborative'])}
|
|
|
|
Important Saudi negotiation culture:
|
|
- Start with relationship building (سلامات واستفسار عن الأحوال)
|
|
- Show respect for the other party
|
|
- Be patient, don't rush to numbers
|
|
- Present win-win framing
|
|
|
|
Return JSON:
|
|
{{
|
|
"opening_terms": {{"key": "value for each negotiable item"}},
|
|
"message_ar": "رسالة العرض الأولي بالعربي (تبدأ بالسلام والتحية)",
|
|
"message_en": "Opening message in English",
|
|
"rationale_ar": "مبررات العرض",
|
|
"confidence": 0.0 to 1.0
|
|
}}"""
|
|
|
|
llm_response = await self.llm.complete(
|
|
system_prompt=system_prompt,
|
|
user_message=context,
|
|
json_mode=True,
|
|
temperature=0.4,
|
|
)
|
|
result = llm_response.parse_json() or {}
|
|
|
|
now_str = datetime.now(timezone.utc).isoformat()
|
|
round_data = NegotiationRound(
|
|
round_number=1,
|
|
action="opening_offer",
|
|
our_terms=result.get("opening_terms", strategy.target_terms),
|
|
their_terms={},
|
|
message_ar=result.get("message_ar", ""),
|
|
message_en=result.get("message_en", ""),
|
|
concessions_made=[],
|
|
concessions_gained=[],
|
|
within_range=True,
|
|
confidence=result.get("confidence", 0.5),
|
|
timestamp=now_str,
|
|
)
|
|
|
|
# Update deal
|
|
deal.proposed_terms = round_data.our_terms
|
|
deal.status = DealStatus.NEGOTIATING.value
|
|
deal.ai_confidence = round_data.confidence
|
|
history = list(deal.negotiation_history or [])
|
|
history.append({
|
|
"round": round_data.round_number,
|
|
"action": round_data.action,
|
|
"our_terms": round_data.our_terms,
|
|
"their_terms": round_data.their_terms,
|
|
"message_ar": round_data.message_ar,
|
|
"timestamp": now_str,
|
|
})
|
|
deal.negotiation_history = history
|
|
await db.flush()
|
|
|
|
logger.info("Started negotiation for deal %s (round 1)", deal_id)
|
|
return round_data
|
|
|
|
# ── Handle Counter-Offer ─────────────────────────────────────────────────
|
|
|
|
async def handle_counter_offer(
|
|
self,
|
|
deal_id,
|
|
their_terms: dict,
|
|
db: AsyncSession,
|
|
) -> NegotiationRound:
|
|
"""
|
|
Analyze a counter-offer and generate a response.
|
|
تحليل عرض مضاد وتوليد رد مناسب
|
|
"""
|
|
deal = await self._load_deal(deal_id, db)
|
|
history = list(deal.negotiation_history or [])
|
|
round_num = len(history) + 1
|
|
|
|
# Get the latest strategy from proposed terms
|
|
our_latest = deal.proposed_terms or {}
|
|
|
|
context = f"""Deal: {deal.deal_title}
|
|
Deal type: {deal.deal_type}
|
|
Our latest terms: {json.dumps(our_latest, ensure_ascii=False)}
|
|
Their counter-offer: {json.dumps(their_terms, ensure_ascii=False)}
|
|
Negotiation history (rounds): {len(history)}
|
|
Estimated value SAR: {deal.estimated_value_sar or 'unknown'}"""
|
|
|
|
system_prompt = """أنت مفاوض أعمال سعودي محترف. الطرف الآخر قدم عرضاً مضاداً.
|
|
|
|
حلل العرض وقرر:
|
|
1. هل العرض مقبول؟
|
|
2. هل نحتاج عرض مضاد؟
|
|
3. هل يجب رفع الموضوع لإنسان؟
|
|
|
|
Saudi culture: never be aggressive. Show appreciation for their offer before countering.
|
|
Handle common responses: "غالي" (too expensive), "نبي نفكر" (need to think), "عندنا عرض ثاني" (we have another offer)
|
|
|
|
Return JSON:
|
|
{
|
|
"action": "accept/counter/reject/escalate",
|
|
"counter_terms": {"key": "value"},
|
|
"message_ar": "الرد بالعربي",
|
|
"message_en": "Response in English",
|
|
"concessions_made": ["what we gave up"],
|
|
"concessions_gained": ["what we got"],
|
|
"within_acceptable_range": true/false,
|
|
"confidence": 0.0 to 1.0,
|
|
"analysis_ar": "تحليل العرض المضاد"
|
|
}"""
|
|
|
|
llm_response = await self.llm.complete(
|
|
system_prompt=system_prompt,
|
|
user_message=context,
|
|
json_mode=True,
|
|
temperature=0.3,
|
|
)
|
|
result = llm_response.parse_json() or {}
|
|
|
|
action = result.get("action", "counter")
|
|
now_str = datetime.now(timezone.utc).isoformat()
|
|
|
|
round_data = NegotiationRound(
|
|
round_number=round_num,
|
|
action=action,
|
|
our_terms=result.get("counter_terms", our_latest),
|
|
their_terms=their_terms,
|
|
message_ar=result.get("message_ar", ""),
|
|
message_en=result.get("message_en", ""),
|
|
concessions_made=result.get("concessions_made", []),
|
|
concessions_gained=result.get("concessions_gained", []),
|
|
within_range=result.get("within_acceptable_range", True),
|
|
confidence=result.get("confidence", 0.5),
|
|
timestamp=now_str,
|
|
)
|
|
|
|
# Update deal state
|
|
if action == "accept":
|
|
deal.agreed_terms = their_terms
|
|
deal.status = DealStatus.TERM_SHEET.value
|
|
elif action == "reject":
|
|
deal.status = DealStatus.CLOSED_LOST.value
|
|
deal.closed_at = datetime.now(timezone.utc)
|
|
else:
|
|
deal.proposed_terms = round_data.our_terms
|
|
|
|
deal.ai_confidence = round_data.confidence
|
|
history.append({
|
|
"round": round_data.round_number,
|
|
"action": action,
|
|
"our_terms": round_data.our_terms,
|
|
"their_terms": their_terms,
|
|
"message_ar": round_data.message_ar,
|
|
"timestamp": now_str,
|
|
})
|
|
deal.negotiation_history = history
|
|
await db.flush()
|
|
|
|
logger.info("Handled counter-offer for deal %s (round %d, action=%s)", deal_id, round_num, action)
|
|
return round_data
|
|
|
|
# ── Generate Negotiation Response ────────────────────────────────────────
|
|
|
|
async def generate_response(
|
|
self,
|
|
deal_id,
|
|
message: str,
|
|
db: AsyncSession,
|
|
) -> str:
|
|
"""
|
|
Generate a culturally appropriate Arabic/English negotiation response.
|
|
توليد رد تفاوضي مناسب ثقافياً بالعربي أو الإنجليزي
|
|
"""
|
|
deal = await self._load_deal(deal_id, db)
|
|
history = deal.negotiation_history or []
|
|
|
|
# Summarize negotiation context
|
|
history_summary = ""
|
|
for h in history[-3:]: # Last 3 rounds
|
|
history_summary += f"Round {h.get('round', '?')}: {h.get('action', '?')} - {h.get('message_ar', '')[:100]}\n"
|
|
|
|
context = f"""Deal: {deal.deal_title}
|
|
Deal type: {deal.deal_type}
|
|
Current status: {deal.status}
|
|
Our proposed terms: {json.dumps(deal.proposed_terms or {}, ensure_ascii=False)}
|
|
Recent history:
|
|
{history_summary}
|
|
|
|
Incoming message from counter-party: {message}"""
|
|
|
|
system_prompt = """أنت مفاوض أعمال سعودي محترف. رد على رسالة الطرف الآخر بشكل مناسب.
|
|
|
|
Rules:
|
|
- إذا الرسالة بالعربي، رد بالعربي
|
|
- إذا الرسالة بالإنجليزي، رد بالإنجليزي
|
|
- كن محترماً وودوداً دائماً
|
|
- لا تكن عدوانياً أبداً
|
|
- حافظ على العلاقة حتى لو الصفقة لم تنجح
|
|
|
|
Handle:
|
|
- "غالي" → أظهر المرونة واعرض بدائل
|
|
- "نبي نفكر" → أعطهم وقت مع اقتراح موعد متابعة
|
|
- "عندنا عرض ثاني" → أبرز المميزات الفريدة بدون تقليل المنافسين
|
|
- "ما يناسبنا" → اسأل عن التفاصيل واعرض تعديلات
|
|
|
|
Return the response message directly as text (not JSON)."""
|
|
|
|
llm_response = await self.llm.complete(
|
|
system_prompt=system_prompt,
|
|
user_message=context,
|
|
temperature=0.5,
|
|
)
|
|
response_text = llm_response.content.strip()
|
|
|
|
# Log the exchange in negotiation history
|
|
history = list(deal.negotiation_history or [])
|
|
history.append({
|
|
"round": len(history) + 1,
|
|
"action": "response",
|
|
"their_message": message[:500],
|
|
"our_response": response_text[:500],
|
|
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
})
|
|
deal.negotiation_history = history
|
|
await db.flush()
|
|
|
|
logger.info("Generated negotiation response for deal %s", deal_id)
|
|
return response_text
|
|
|
|
# ── Should Escalate? ─────────────────────────────────────────────────────
|
|
|
|
async def should_escalate(
|
|
self,
|
|
deal_id,
|
|
db: AsyncSession,
|
|
) -> bool:
|
|
"""
|
|
Determine if a human should take over the negotiation.
|
|
تحديد ما إذا كان يجب تصعيد التفاوض لإنسان
|
|
"""
|
|
deal = await self._load_deal(deal_id, db)
|
|
history = deal.negotiation_history or []
|
|
round_count = len(history)
|
|
|
|
# Rule 1: High-value deals
|
|
value = float(deal.estimated_value_sar or 0)
|
|
if value > ESCALATION_VALUE_SAR:
|
|
logger.info("Escalation: deal %s value (%.0f SAR) exceeds threshold", deal_id, value)
|
|
return True
|
|
|
|
# Rule 2: Too many rounds without resolution
|
|
if round_count >= MAX_AUTO_ROUNDS:
|
|
logger.info("Escalation: deal %s reached %d rounds", deal_id, round_count)
|
|
return True
|
|
|
|
# Rule 3: Stalled negotiation (same terms repeating)
|
|
if round_count >= STALL_THRESHOLD:
|
|
recent_terms = [
|
|
json.dumps(h.get("our_terms", {}), sort_keys=True)
|
|
for h in history[-STALL_THRESHOLD:]
|
|
]
|
|
if len(set(recent_terms)) == 1:
|
|
logger.info("Escalation: deal %s stalled for %d rounds", deal_id, STALL_THRESHOLD)
|
|
return True
|
|
|
|
# Rule 4: Low confidence
|
|
if deal.ai_confidence is not None and deal.ai_confidence < 0.3:
|
|
logger.info("Escalation: deal %s AI confidence too low (%.2f)", deal_id, deal.ai_confidence)
|
|
return True
|
|
|
|
# Rule 5: Counter-party explicitly requested human contact
|
|
if history:
|
|
last_msg = (history[-1].get("their_message", "") or "").lower()
|
|
human_keywords = [
|
|
"أبي أكلم شخص", "أبي أكلم المدير", "ابي اتكلم مع انسان",
|
|
"speak to someone", "talk to a person", "human", "manager",
|
|
"مدير", "مسؤول",
|
|
]
|
|
for kw in human_keywords:
|
|
if kw in last_msg:
|
|
logger.info("Escalation: deal %s counter-party requested human", deal_id)
|
|
return True
|
|
|
|
return False
|
|
|
|
# ── Generate Term Sheet ──────────────────────────────────────────────────
|
|
|
|
async def generate_term_sheet(
|
|
self,
|
|
deal_id,
|
|
db: AsyncSession,
|
|
) -> dict:
|
|
"""
|
|
Generate a formal Arabic term sheet from agreed terms.
|
|
إنشاء ورقة شروط رسمية بالعربي من الشروط المتفق عليها
|
|
"""
|
|
deal = await self._load_deal(deal_id, db)
|
|
initiator = await self._load_profile(deal.initiator_profile_id, db)
|
|
|
|
target_name = deal.target_company_name or "الطرف الثاني"
|
|
target_cr = ""
|
|
if deal.target_profile_id:
|
|
target = await self._load_profile(deal.target_profile_id, db)
|
|
if target:
|
|
target_name = target.company_name
|
|
target_cr = target.cr_number or ""
|
|
|
|
terms = deal.agreed_terms or deal.proposed_terms or {}
|
|
|
|
context = f"""Parties:
|
|
- Party A: {initiator.company_name} (CR: {initiator.cr_number or 'N/A'})
|
|
- Party B: {target_name} (CR: {target_cr or 'N/A'})
|
|
|
|
Deal: {deal.deal_title}
|
|
Deal type: {deal.deal_type}
|
|
Agreed terms: {json.dumps(terms, ensure_ascii=False)}
|
|
Estimated value: {deal.estimated_value_sar or 'TBD'} SAR
|
|
Our offer: {deal.our_offer or 'N/A'}
|
|
Our need: {deal.our_need or 'N/A'}"""
|
|
|
|
system_prompt = """أنت مستشار قانوني سعودي متخصص في صياغة أوراق الشروط.
|
|
أنشئ ورقة شروط رسمية باللغة العربية.
|
|
|
|
Return JSON:
|
|
{
|
|
"title_ar": "عنوان ورقة الشروط",
|
|
"date": "التاريخ",
|
|
"parties": [
|
|
{"name": "اسم الطرف", "role": "الطرف الأول/الطرف الثاني", "cr": "رقم السجل التجاري"}
|
|
],
|
|
"preamble_ar": "مقدمة ورقة الشروط",
|
|
"scope_ar": "نطاق الاتفاقية",
|
|
"terms": [
|
|
{"title_ar": "عنوان البند", "description_ar": "تفاصيل البند"}
|
|
],
|
|
"obligations_party_a_ar": ["التزامات الطرف الأول"],
|
|
"obligations_party_b_ar": ["التزامات الطرف الثاني"],
|
|
"financial_terms_ar": "الشروط المالية",
|
|
"duration_ar": "مدة الاتفاقية",
|
|
"termination_ar": "شروط الإنهاء",
|
|
"confidentiality_ar": "شرط السرية",
|
|
"dispute_resolution_ar": "حل النزاعات",
|
|
"governing_law_ar": "القانون الحاكم: أنظمة المملكة العربية السعودية",
|
|
"next_steps_ar": ["الخطوات التالية"]
|
|
}"""
|
|
|
|
llm_response = await self.llm.complete(
|
|
system_prompt=system_prompt,
|
|
user_message=context,
|
|
json_mode=True,
|
|
temperature=0.2,
|
|
)
|
|
term_sheet = llm_response.parse_json() or {}
|
|
|
|
# Update deal status
|
|
if deal.status == DealStatus.NEGOTIATING.value:
|
|
deal.status = DealStatus.TERM_SHEET.value
|
|
await db.flush()
|
|
|
|
logger.info("Generated term sheet for deal %s", deal_id)
|
|
return term_sheet
|
|
|
|
# ── Helpers ──────────────────────────────────────────────────────────────
|
|
|
|
async def _load_deal(self, deal_id, db: AsyncSession) -> StrategicDeal:
|
|
result = await db.execute(select(StrategicDeal).where(StrategicDeal.id == deal_id))
|
|
deal = result.scalar_one_or_none()
|
|
if not deal:
|
|
raise ValueError(f"Deal {deal_id} not found")
|
|
return deal
|
|
|
|
async def _load_profile(self, profile_id, db: AsyncSession) -> Optional[CompanyProfile]:
|
|
if not profile_id:
|
|
return None
|
|
result = await db.execute(select(CompanyProfile).where(CompanyProfile.id == profile_id))
|
|
return result.scalar_one_or_none()
|