This commit is contained in:
jeremygan2021
2026-01-10 01:58:17 +08:00
parent 20c280405a
commit 6190fae22f
3 changed files with 124 additions and 83 deletions

Binary file not shown.

Binary file not shown.

189
main.py
View File

@@ -11,12 +11,29 @@ import random
import uuid import uuid
import os import os
import difflib import difflib
from concurrent.futures import ProcessPoolExecutor
from dotenv import load_dotenv from dotenv import load_dotenv
load_dotenv() load_dotenv()
app = FastAPI() app = FastAPI()
# 进程池全局变量
process_pool = None
@app.on_event("startup")
async def startup_event():
global process_pool
# RK3588 有 8 个核心,预留一些给数据库和系统,使用 6 个核心进行计算
process_pool = ProcessPoolExecutor(max_workers=6)
print("ProcessPoolExecutor initialized with 6 workers")
@app.on_event("shutdown")
async def shutdown_event():
if process_pool:
process_pool.shutdown()
print("ProcessPoolExecutor shutdown")
# Database connection parameters # Database connection parameters
DB_CONFIG = { DB_CONFIG = {
"host": os.getenv("DB_HOST", "121.43.104.161"), "host": os.getenv("DB_HOST", "121.43.104.161"),
@@ -25,7 +42,6 @@ DB_CONFIG = {
"password": os.getenv("DB_PASSWORD", "123gsdh"), "password": os.getenv("DB_PASSWORD", "123gsdh"),
"database": os.getenv("DB_NAME", "gsdh") "database": os.getenv("DB_NAME", "gsdh")
} }
# 商业领域同义词库 (Business Thesaurus) - 用于解决模糊语义匹配 # 商业领域同义词库 (Business Thesaurus) - 用于解决模糊语义匹配
BUSINESS_THESAURUS = { BUSINESS_THESAURUS = {
# 核心意图: [关联行业/关键词列表] # 核心意图: [关联行业/关键词列表]
@@ -42,14 +58,21 @@ BUSINESS_THESAURUS = {
"供应链": ["物流", "仓储", "采购", "原材料", "制造", "工厂", "代工", "OEM"], "供应链": ["物流", "仓储", "采购", "原材料", "制造", "工厂", "代工", "OEM"],
"人力": ["招聘", "猎头", "培训", "HR", "劳务", "派遣", "灵活用工"], "人力": ["招聘", "猎头", "培训", "HR", "劳务", "派遣", "灵活用工"],
# 短视频与互联网专项扩展
"自媒体": ["抖音", "快手", "视频号", "小红书", "B站", "直播", "带货", "种草", "网红", "KOL", "KOC", "MCN", "内容创作", "剪辑", "拍摄", "流量", "完播率", "点赞", "评论", "转发", "DOU+", "投流", "橱窗", "小黄车", "团购", "同城号", "剧情号", "知识号", "颜值号", "三农号"],
"互联网": ["电商", "平台", "流量", "运营", "产品", "用户增长", "裂变", "留存", "转化", "GMV", "DAU", "MAU", "PV", "UV", "SEO", "SEM", "ASO", "投放", "拉新", "促活", "留存", "变现", "闭环", "私域", "公域", "矩阵", "账号", "内容", "社群", "小程序", "H5", "Web", "App", "iOS", "Android", "中台", "SaaS", "PaaS", "IaaS", "云原生", "微服务", "低代码", "零代码", "敏捷", "DevOps", "CI/CD"],
"电商": ["购物", "订单", "支付", "物流", "仓储", "采购", "原材料", "制造", "工厂", "代工", "OEM"],
# AI 行业专项扩展 # AI 行业专项扩展
"AI": ["大模型", "算法", "算力", "芯片", "数据", "数字人", "机器人", "智能", "自动化", "Agent", "RAG", "AIGC"], "AI": ["大模型", "算法", "算力", "芯片", "数据", "数字人", "机器人", "智能", "自动化", "Agent", "RAG", "AIGC", "智能体"],
"智能体": ["Agent", "Copilot", "数字员工", "LangChain", "LlamaIndex", "AutoGPT", "Coze", "Dify", "扣子", "工作流", "Workflow", "编排", "RAG", "知识库", "向量", "Embedding", "工具调用", "Function Call", "多智能体", "Multi-Agent", "Swarm", "CrewAI", "AutoGen"],
"大模型": ["OpenAI", "GPT", "文心", "通义", "Llama", "微调", "训练", "部署", "推理", "Token", "向量", "Prompt", "提示词"], "大模型": ["OpenAI", "GPT", "文心", "通义", "Llama", "微调", "训练", "部署", "推理", "Token", "向量", "Prompt", "提示词"],
"算力": ["GPU", "显卡", "英伟达", "H800", "4090", "服务器", "云计算", "智算中心", "租赁", "托管"], "算力": ["GPU", "显卡", "英伟达", "H800", "4090", "服务器", "云计算", "智算中心", "租赁", "托管"],
"芯片": ["半导体", "集成电路", "英伟达", "华为昇腾", "寒武纪", "FPGA", "ASIC"], "芯片": ["半导体", "集成电路", "英伟达", "华为昇腾", "寒武纪", "FPGA", "ASIC"],
"数据": ["标注", "清洗", "采集", "语料", "数据集", "版权", "向量数据库"], "数据": ["标注", "清洗", "采集", "语料", "数据集", "版权", "向量数据库"],
"数字人": ["直播", "短视频", "IP", "形象", "克隆", "配音", "虚拟人", "元宇宙"], "数字人": ["直播", "短视频", "IP", "形象", "克隆", "配音", "虚拟人", "元宇宙"],
"具身智能": ["机器人", "机械臂", "无人机", "自动驾驶", "传感器", "视觉", "雷达", "端侧模型"] "具身智能": ["机器人", "机械臂", "无人机", "自动驾驶", "传感器", "视觉", "雷达", "端侧模型"],
"AIGC": ["生成式AI", "文本生成", "图像生成", "视频生成", "音乐生成", "代码生成"],
"AI短剧": ["短剧", "视频", "内容创作", "剪辑", "拍摄", "流量", "完播率", "点赞", "评论", "转发"]
} }
def compute_expert_score(text_a: str, text_b: str) -> float: def compute_expert_score(text_a: str, text_b: str) -> float:
@@ -102,6 +125,66 @@ def compute_expert_score(text_a: str, text_b: str) -> float:
# total = 0.6 -> 属于高匹配 # total = 0.6 -> 属于高匹配
return min(base_score + semantic_boost, 1.0) return min(base_score + semantic_boost, 1.0)
def calculate_matches_task(user_industry: str, user_vision: str, others: List[Dict]) -> Dict:
"""
CPU 密集型匹配任务,将在子进程中运行。
"""
matches = {
"customers": [], # My Industry vs Their Vision
"partners": [], # My Vision vs Their Industry
"peers": [] # My Industry vs Their Industry
}
for other in others:
# Handle potential None values safely
other_ind_comp = other.get('industry_company') or ''
other_bus_scope = other.get('business_scope') or ''
other_industry = f"{other_ind_comp} {other_bus_scope}".strip()
other_vision = other.get('vision_2026') or ""
# 3.1 Customers (They need me)
# My Industry (Supply) matches Their Vision (Demand)
if user_industry and other_vision:
score = compute_expert_score(user_industry, other_vision)
if score > 0.15:
matches["customers"].append({**other, "score": score})
# 3.2 Partners (I need them)
# My Vision (Demand) matches Their Industry (Supply)
if user_vision and other_industry:
score = compute_expert_score(user_vision, other_industry)
if score > 0.15:
matches["partners"].append({**other, "score": score})
# 3.3 Peers (Same industry)
# My Industry matches Their Industry
if user_industry and other_industry:
score = compute_expert_score(user_industry, other_industry)
if score > 0.2:
matches["peers"].append({**other, "score": score})
# 4. Sort and Limit
for key in matches:
matches[key].sort(key=lambda x: x["score"], reverse=True)
# Limit to top 5 per category
matches[key] = matches[key][:5]
# Hide sensitive info
for p in matches[key]:
# Safe phone masking
p_phone = p.get('phone', '')
if len(p_phone) >= 7:
p['phone'] = p_phone[:3] + "****" + p_phone[-4:]
else:
p['phone'] = "****"
p['location'] = "???" # Hidden location
p['unlocked'] = False
return matches
# Initialize Connection Pool # Initialize Connection Pool
try: try:
postgreSQL_pool = psycopg2.pool.ThreadedConnectionPool(1, 20, **DB_CONFIG) postgreSQL_pool = psycopg2.pool.ThreadedConnectionPool(1, 20, **DB_CONFIG)
@@ -132,21 +215,35 @@ class AddUserRequest(BaseModel):
payment_channel: Optional[str] = None payment_channel: Optional[str] = None
def get_db_connection(): def get_db_connection():
max_retries = 5
for attempt in range(max_retries):
conn = None
try: try:
conn = postgreSQL_pool.getconn() conn = postgreSQL_pool.getconn()
if conn.closed:
# Should not happen with getconn() usually, but just in case
postgreSQL_pool.putconn(conn, close=True)
continue
try: try:
with conn.cursor() as cur: with conn.cursor() as cur:
cur.execute('SELECT 1') cur.execute('SELECT 1')
return conn return conn
except (psycopg2.OperationalError, psycopg2.InterfaceError): except (psycopg2.OperationalError, psycopg2.InterfaceError, psycopg2.DatabaseError):
# Connection is dead, remove it from pool and create a new one # Connection is dead, remove it from pool
if conn:
postgreSQL_pool.putconn(conn, close=True) postgreSQL_pool.putconn(conn, close=True)
return postgreSQL_pool.getconn() # Loop will continue to get next connection
continue
except Exception as e: except Exception as e:
# If pool is exhausted or DB is down if conn:
print(f"Error getting DB connection: {e}") postgreSQL_pool.putconn(conn, close=True)
print(f"Error getting DB connection (attempt {attempt+1}): {e}")
if attempt == max_retries - 1:
raise e raise e
raise Exception("Failed to get a valid database connection after retries")
def release_db_connection(conn): def release_db_connection(conn):
if conn: if conn:
postgreSQL_pool.putconn(conn) postgreSQL_pool.putconn(conn)
@@ -294,21 +391,21 @@ def get_tablemates(cur, location: str, exclude_id: str, user_vision: str = "", u
sim = compute_expert_score(user_vision, cand_vision) sim = compute_expert_score(user_vision, cand_vision)
# Weight this score # Weight this score
cand["score"] += sim * 1.0 cand["score"] += sim * 1.0
if sim > 0.3: # Threshold for "similarity" if sim > 0.25: # Threshold for "similarity" (Adjusted to 0.25 for better precision)
cand["match_type"].append("志同道合 (愿景相似)") cand["match_type"].append("志同道合 (愿景相似)")
# 2. Cross Match: My Industry matches Their Vision (I can help them) # 2. Cross Match: My Industry matches Their Vision (I can help them)
if user_industry and cand_vision: if user_industry and cand_vision:
sim = compute_expert_score(user_industry, cand_vision) sim = compute_expert_score(user_industry, cand_vision)
cand["score"] += sim * 1.5 # Give higher weight to potential business match cand["score"] += sim * 1.5 # Give higher weight to potential business match
if sim > 0.3: if sim > 0.25:
cand["match_type"].append("潜在合作 (您的行业匹配对方愿景)") cand["match_type"].append("潜在合作 (您的行业匹配对方愿景)")
# 3. Cross Match: Their Industry matches My Vision (They can help me) # 3. Cross Match: Their Industry matches My Vision (They can help me)
if user_vision and cand_industry: if user_vision and cand_industry:
sim = compute_expert_score(user_vision, cand_industry) sim = compute_expert_score(user_vision, cand_industry)
cand["score"] += sim * 1.5 cand["score"] += sim * 1.5
if sim > 0.3: if sim > 0.25:
cand["match_type"].append("潜在贵人 (对方行业匹配您的愿景)") cand["match_type"].append("潜在贵人 (对方行业匹配您的愿景)")
# Sort by score descending # Sort by score descending
@@ -358,7 +455,6 @@ def resource_match(req: ResourceMatchRequest):
# 1. Fetch current user # 1. Fetch current user
# Optimize: Only fetch necessary fields # Optimize: Only fetch necessary fields
# Points are now in checkin_info
cur.execute(""" cur.execute("""
SELECT gd.new_id, gd.name, gd.phone, ci.social_point as points, SELECT gd.new_id, gd.name, gd.phone, ci.social_point as points,
gd.industry_company, ci.business_scope, ci.vision_2026 gd.industry_company, ci.business_scope, ci.vision_2026
@@ -392,68 +488,14 @@ def resource_match(req: ResourceMatchRequest):
cur.close() cur.close()
release_db_connection(conn) release_db_connection(conn)
# 3. Calculate Matches (In-Memory Python) # 3. Calculate Matches (Using Process Pool for Concurrency)
# difflib.SequenceMatcher is O(N*M), running it 3 times for every user is expensive. # 将 CPU 密集型计算提交给进程池,避免阻塞主进程和 GIL 锁竞争
# We can optimize by pre-calculating and caching, or just doing it efficiently. if process_pool:
future = process_pool.submit(calculate_matches_task, user_industry, user_vision, others)
matches = { matches = future.result() # Wait for result (blocks this thread, but not the whole server)
"customers": [], # My Industry vs Their Vision
"partners": [], # My Vision vs Their Industry
"peers": [] # My Industry vs Their Industry
}
# Optimization: Pre-compile SequenceMatcher objects if possible, but ratio() needs both strings.
# We use a threshold to quickly filter obvious non-matches if we had embeddings.
# For now, we stick to string matching but handle None values gracefully.
for other in others:
# Handle potential None values safely
other_ind_comp = other.get('industry_company') or ''
other_bus_scope = other.get('business_scope') or ''
other_industry = f"{other_ind_comp} {other_bus_scope}".strip()
other_vision = other.get('vision_2026') or ""
# 3.1 Customers (They need me)
# My Industry (Supply) matches Their Vision (Demand)
if user_industry and other_vision:
# Quick length check optimization: if length difference is huge, ratio will be low
score = compute_expert_score(user_industry, other_vision)
if score > 0.2:
matches["customers"].append({**other, "score": score})
# 3.2 Partners (I need them)
# My Vision (Demand) matches Their Industry (Supply)
if user_vision and other_industry:
score = compute_expert_score(user_vision, other_industry)
if score > 0.2:
matches["partners"].append({**other, "score": score})
# 3.3 Peers (Same industry)
# My Industry matches Their Industry
if user_industry and other_industry:
score = compute_expert_score(user_industry, other_industry)
if score > 0.3:
matches["peers"].append({**other, "score": score})
# 4. Sort and Limit
for key in matches:
matches[key].sort(key=lambda x: x["score"], reverse=True)
# Limit to top 20 for display performance
matches[key] = matches[key][:20]
# Hide sensitive info by default
for p in matches[key]:
# Safe phone masking
p_phone = p.get('phone', '')
if len(p_phone) >= 7:
p['phone'] = p_phone[:3] + "****" + p_phone[-4:]
else: else:
p['phone'] = "****" # Fallback if pool not initialized
matches = calculate_matches_task(user_industry, user_vision, others)
p['location'] = "???" # Hidden location
p['unlocked'] = False
# Clean up internal fields to reduce JSON size
# p.pop('new_id', None)
return { return {
"success": True, "success": True,
@@ -480,7 +522,6 @@ def unlock_contact(req: UnlockRequest):
cur = conn.cursor(cursor_factory=RealDictCursor) cur = conn.cursor(cursor_factory=RealDictCursor)
# 1. Check User Points # 1. Check User Points
# Points are now in checkin_info, queried by gsdh_id (which we can get from phone via gsdh_data join)
cur.execute(""" cur.execute("""
SELECT ci.social_point as points SELECT ci.social_point as points
FROM checkin_info ci FROM checkin_info ci