Update app.py
Browse files
app.py
CHANGED
|
@@ -2901,4 +2901,945 @@ async def simple_chat_stream(chat_req: ChatRequest, request: Request):
|
|
| 2901 |
"Connection": "keep-alive",
|
| 2902 |
"X-Accel-Buffering": "no",
|
| 2903 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2904 |
)
|
|
|
|
| 2901 |
"Connection": "keep-alive",
|
| 2902 |
"X-Accel-Buffering": "no",
|
| 2903 |
}
|
| 2904 |
+
)
|
| 2905 |
+
# =====================================================
|
| 2906 |
+
# BACKGROUND CLEANUP TASK
|
| 2907 |
+
# =====================================================
|
| 2908 |
+
async def cleanup_task():
|
| 2909 |
+
"""مهمة تنظيف دورية للجلسات القديمة"""
|
| 2910 |
+
while True:
|
| 2911 |
+
try:
|
| 2912 |
+
await asyncio.sleep(3600) # كل ساعة
|
| 2913 |
+
CONTEXT_STORE.cleanup_old_sessions(max_age_hours=24)
|
| 2914 |
+
logger.info("[Cleanup] Old sessions cleaned")
|
| 2915 |
+
except Exception as e:
|
| 2916 |
+
logger.error(f"[Cleanup] Error: {e}")
|
| 2917 |
+
|
| 2918 |
+
@app.on_event("startup")
|
| 2919 |
+
async def startup_event():
|
| 2920 |
+
"""تشغيل المهام عند بدء التطبيق"""
|
| 2921 |
+
asyncio.create_task(cleanup_task())
|
| 2922 |
+
logger.info("=" * 60)
|
| 2923 |
+
logger.info("🚀 G4F Execution Engine v6.0.0 Started")
|
| 2924 |
+
logger.info(f"🍪 Cookies: {COOKIE_STATUS}")
|
| 2925 |
+
logger.info(f"🔌 Providers: {list(REAL_PROVIDERS.keys())}")
|
| 2926 |
+
logger.info("🔧 Execution Engine: ACTIVE")
|
| 2927 |
+
logger.info("✅ Problems Fixed:")
|
| 2928 |
+
logger.info(" 1. Smart Watcher Syndrome → ResponseAnalyzer")
|
| 2929 |
+
logger.info(" 2. Lost in Translation → JSON Cleaner")
|
| 2930 |
+
logger.info(" 3. Missing Final Push → ExecutionEnforcer")
|
| 2931 |
+
logger.info(" 4. Identity Confusion → EXECUTOR mode")
|
| 2932 |
+
logger.info("=" * 60)
|
| 2933 |
+
|
| 2934 |
+
@app.on_event("shutdown")
|
| 2935 |
+
async def shutdown_event():
|
| 2936 |
+
"""تنظيف عند إغلاق التطبيق"""
|
| 2937 |
+
logger.info("[Shutdown] G4F Execution Engine stopping...")
|
| 2938 |
+
active_sessions = len(CONTEXT_STORE._store)
|
| 2939 |
+
logger.info(f"[Shutdown] Active sessions at shutdown: {active_sessions}")
|
| 2940 |
+
|
| 2941 |
+
# =====================================================
|
| 2942 |
+
# نقطة تشخيص: عرض حالة النظام الكاملة
|
| 2943 |
+
# =====================================================
|
| 2944 |
+
@app.get("/debug/system-status")
|
| 2945 |
+
async def debug_system_status(request: Request):
|
| 2946 |
+
"""عرض حالة النظام الكاملة للتشخيص"""
|
| 2947 |
+
verify_api_key(request)
|
| 2948 |
+
|
| 2949 |
+
# إحصائيات الجلسات
|
| 2950 |
+
session_stats = {
|
| 2951 |
+
"total_sessions": 0,
|
| 2952 |
+
"sessions_with_pending_tools": 0,
|
| 2953 |
+
"sessions_with_description_failures": 0,
|
| 2954 |
+
"sessions_completed": 0,
|
| 2955 |
+
"total_tool_executions": 0,
|
| 2956 |
+
"total_description_failures": 0,
|
| 2957 |
+
}
|
| 2958 |
+
|
| 2959 |
+
with CONTEXT_STORE._lock:
|
| 2960 |
+
session_stats["total_sessions"] = len(CONTEXT_STORE._store)
|
| 2961 |
+
for sid, ctx in CONTEXT_STORE._store.items():
|
| 2962 |
+
pending = len(ctx.get("pending_tool_calls", []))
|
| 2963 |
+
desc_failures = ctx.get("consecutive_description_count", 0)
|
| 2964 |
+
executed = len(ctx.get("executed_tool_calls", []))
|
| 2965 |
+
state = ctx.get("current_state", "")
|
| 2966 |
+
|
| 2967 |
+
if pending > 0:
|
| 2968 |
+
session_stats["sessions_with_pending_tools"] += 1
|
| 2969 |
+
if desc_failures > 0:
|
| 2970 |
+
session_stats["sessions_with_description_failures"] += 1
|
| 2971 |
+
if state == ExecutionState.COMPLETED:
|
| 2972 |
+
session_stats["sessions_completed"] += 1
|
| 2973 |
+
|
| 2974 |
+
session_stats["total_tool_executions"] += executed
|
| 2975 |
+
session_stats["total_description_failures"] += desc_failures
|
| 2976 |
+
|
| 2977 |
+
# إحصائيات المزودين
|
| 2978 |
+
provider_stats = {}
|
| 2979 |
+
for pname, pobj in REAL_PROVIDERS.items():
|
| 2980 |
+
models = discover_provider_models(pobj, pname)
|
| 2981 |
+
provider_stats[pname] = {
|
| 2982 |
+
"available": True,
|
| 2983 |
+
"model_count": len(models),
|
| 2984 |
+
"models": models[:3], # أول 3 نماذج فقط
|
| 2985 |
+
}
|
| 2986 |
+
|
| 2987 |
+
# إحصائيات محرك التنفيذ
|
| 2988 |
+
engine_stats = {
|
| 2989 |
+
"description_patterns_count": len(ResponseAnalyzer.DESCRIPTION_PHRASES),
|
| 2990 |
+
"tool_call_start_markers": len(ToolCallParser.START_MARKERS),
|
| 2991 |
+
"max_execution_retries": ExecutionEnforcer.MAX_EXECUTION_RETRIES,
|
| 2992 |
+
"cache_size": len(CACHE.cache),
|
| 2993 |
+
"cache_max_size": CACHE.max_size,
|
| 2994 |
+
"cache_ttl_seconds": CACHE.ttl,
|
| 2995 |
+
}
|
| 2996 |
+
|
| 2997 |
+
return {
|
| 2998 |
+
"status": "operational",
|
| 2999 |
+
"version": "6.0.0",
|
| 3000 |
+
"uptime_info": {
|
| 3001 |
+
"cookie_status": COOKIE_STATUS,
|
| 3002 |
+
"providers_loaded": len(REAL_PROVIDERS),
|
| 3003 |
+
},
|
| 3004 |
+
"session_statistics": session_stats,
|
| 3005 |
+
"provider_statistics": provider_stats,
|
| 3006 |
+
"execution_engine_statistics": engine_stats,
|
| 3007 |
+
"execution_states": {
|
| 3008 |
+
"IDLE": ExecutionState.IDLE,
|
| 3009 |
+
"PLANNING": ExecutionState.PLANNING,
|
| 3010 |
+
"TOOL_PENDING": ExecutionState.TOOL_PENDING,
|
| 3011 |
+
"TOOL_EXECUTING": ExecutionState.TOOL_EXECUTING,
|
| 3012 |
+
"TOOL_DONE": ExecutionState.TOOL_DONE,
|
| 3013 |
+
"AWAITING_NEXT": ExecutionState.AWAITING_NEXT,
|
| 3014 |
+
"COMPLETED": ExecutionState.COMPLETED,
|
| 3015 |
+
},
|
| 3016 |
+
"timestamp": int(time.time())
|
| 3017 |
+
}
|
| 3018 |
+
|
| 3019 |
+
# =====================================================
|
| 3020 |
+
# نقطة تشخيص: اختبار مزود محدد
|
| 3021 |
+
# =====================================================
|
| 3022 |
+
@app.post("/debug/test-provider")
|
| 3023 |
+
async def debug_test_provider(request: Request):
|
| 3024 |
+
"""اختبار مزود محدد بشكل مباشر"""
|
| 3025 |
+
verify_api_key(request)
|
| 3026 |
+
body = await request.json()
|
| 3027 |
+
provider_name = body.get("provider", "Blackbox")
|
| 3028 |
+
model_name = body.get("model", "gpt-4o")
|
| 3029 |
+
test_message = body.get("message", "Say 'hello' in one word.")
|
| 3030 |
+
timeout_seconds = body.get("timeout", 30)
|
| 3031 |
+
|
| 3032 |
+
pobj = REAL_PROVIDERS.get(provider_name)
|
| 3033 |
+
if not pobj:
|
| 3034 |
+
return {
|
| 3035 |
+
"success": False,
|
| 3036 |
+
"error": f"Provider '{provider_name}' not found",
|
| 3037 |
+
"available_providers": list(REAL_PROVIDERS.keys())
|
| 3038 |
+
}
|
| 3039 |
+
|
| 3040 |
+
start_time = time.time()
|
| 3041 |
+
response_chunks = []
|
| 3042 |
+
error_msg = None
|
| 3043 |
+
|
| 3044 |
+
try:
|
| 3045 |
+
msgs = [{"role": "user", "content": test_message}]
|
| 3046 |
+
stream = g4f.ChatCompletion.create(
|
| 3047 |
+
model=model_name,
|
| 3048 |
+
provider=pobj,
|
| 3049 |
+
messages=msgs,
|
| 3050 |
+
stream=True,
|
| 3051 |
+
timeout=timeout_seconds
|
| 3052 |
+
)
|
| 3053 |
+
|
| 3054 |
+
for chunk in stream:
|
| 3055 |
+
c = clean_stream(chunk)
|
| 3056 |
+
if c:
|
| 3057 |
+
response_chunks.append(c)
|
| 3058 |
+
# توقف بعد 500 حرف للاختبار
|
| 3059 |
+
if sum(len(r) for r in response_chunks) > 500:
|
| 3060 |
+
break
|
| 3061 |
+
|
| 3062 |
+
except Exception as e:
|
| 3063 |
+
error_msg = str(e)
|
| 3064 |
+
|
| 3065 |
+
elapsed = time.time() - start_time
|
| 3066 |
+
full_response = "".join(response_chunks)
|
| 3067 |
+
|
| 3068 |
+
return {
|
| 3069 |
+
"success": bool(full_response and not error_msg),
|
| 3070 |
+
"provider": provider_name,
|
| 3071 |
+
"model": model_name,
|
| 3072 |
+
"test_message": test_message,
|
| 3073 |
+
"response_preview": full_response[:300] if full_response else "",
|
| 3074 |
+
"response_length": len(full_response),
|
| 3075 |
+
"elapsed_seconds": round(elapsed, 2),
|
| 3076 |
+
"error": error_msg,
|
| 3077 |
+
"timestamp": int(time.time())
|
| 3078 |
+
}
|
| 3079 |
+
|
| 3080 |
+
# =====================================================
|
| 3081 |
+
# نقطة تشخيص: اختبار دورة كاملة مع أداة
|
| 3082 |
+
# =====================================================
|
| 3083 |
+
@app.post("/debug/test-full-tool-cycle")
|
| 3084 |
+
async def debug_test_full_tool_cycle(request: Request):
|
| 3085 |
+
"""
|
| 3086 |
+
اختبار دورة كاملة: رسالة → كشف أداة → تنفيذ → نتيجة
|
| 3087 |
+
يُظهر بوضوح هل المحرك يعمل أم لا
|
| 3088 |
+
"""
|
| 3089 |
+
verify_api_key(request)
|
| 3090 |
+
body = await request.json()
|
| 3091 |
+
test_message = body.get(
|
| 3092 |
+
"message",
|
| 3093 |
+
"Create a file called 'hello.py' with content: print('Hello World')"
|
| 3094 |
+
)
|
| 3095 |
+
model = body.get("model", "gpt-4o")
|
| 3096 |
+
|
| 3097 |
+
test_tools = [
|
| 3098 |
+
{
|
| 3099 |
+
"name": "write_file",
|
| 3100 |
+
"description": "Write content to a file on disk",
|
| 3101 |
+
"input_schema": {
|
| 3102 |
+
"type": "object",
|
| 3103 |
+
"properties": {
|
| 3104 |
+
"path": {
|
| 3105 |
+
"type": "string",
|
| 3106 |
+
"description": "The file path to write to"
|
| 3107 |
+
},
|
| 3108 |
+
"content": {
|
| 3109 |
+
"type": "string",
|
| 3110 |
+
"description": "The content to write to the file"
|
| 3111 |
+
}
|
| 3112 |
+
},
|
| 3113 |
+
"required": ["path", "content"]
|
| 3114 |
+
}
|
| 3115 |
+
}
|
| 3116 |
+
]
|
| 3117 |
+
|
| 3118 |
+
test_session = f"test_cycle_{uuid.uuid4().hex[:8]}"
|
| 3119 |
+
results = {
|
| 3120 |
+
"test_session": test_session,
|
| 3121 |
+
"test_message": test_message,
|
| 3122 |
+
"model": model,
|
| 3123 |
+
"steps": [],
|
| 3124 |
+
"final_tool_calls": [],
|
| 3125 |
+
"success": False,
|
| 3126 |
+
"description_attempts": 0,
|
| 3127 |
+
"execution_attempts": 0,
|
| 3128 |
+
}
|
| 3129 |
+
|
| 3130 |
+
# المرحلة 1: تحويل الرسالة
|
| 3131 |
+
messages = [{"role": "user", "content": test_message}]
|
| 3132 |
+
full_message, history = MessageConverter.convert_messages(
|
| 3133 |
+
messages, "", test_tools, "auto", test_session
|
| 3134 |
+
)
|
| 3135 |
+
|
| 3136 |
+
results["steps"].append({
|
| 3137 |
+
"step": 1,
|
| 3138 |
+
"name": "Message Conversion",
|
| 3139 |
+
"message_length": len(full_message),
|
| 3140 |
+
"has_tool_instructions": "tool_call" in full_message.lower(),
|
| 3141 |
+
"has_executor_rule": "EXECUTOR" in full_message,
|
| 3142 |
+
})
|
| 3143 |
+
|
| 3144 |
+
# المرحلة 2: إرسال للنموذج
|
| 3145 |
+
step2_start = time.time()
|
| 3146 |
+
full_response = ""
|
| 3147 |
+
try:
|
| 3148 |
+
for chunk in ask(
|
| 3149 |
+
full_message, history, "Blackbox", model,
|
| 3150 |
+
tools=test_tools, session_id=test_session
|
| 3151 |
+
):
|
| 3152 |
+
full_response += chunk
|
| 3153 |
+
except Exception as e:
|
| 3154 |
+
results["steps"].append({
|
| 3155 |
+
"step": 2,
|
| 3156 |
+
"name": "Model Request",
|
| 3157 |
+
"error": str(e)
|
| 3158 |
+
})
|
| 3159 |
+
return results
|
| 3160 |
+
|
| 3161 |
+
step2_elapsed = time.time() - step2_start
|
| 3162 |
+
|
| 3163 |
+
# المرحلة 3: تحليل الرد
|
| 3164 |
+
analysis = ResponseAnalyzer.analyze(full_response, test_tools)
|
| 3165 |
+
clean_text, tool_calls = ToolCallParser.parse_tool_calls(full_response, test_tools)
|
| 3166 |
+
|
| 3167 |
+
results["steps"].append({
|
| 3168 |
+
"step": 2,
|
| 3169 |
+
"name": "Model Request",
|
| 3170 |
+
"response_length": len(full_response),
|
| 3171 |
+
"response_preview": full_response[:200],
|
| 3172 |
+
"elapsed_seconds": round(step2_elapsed, 2),
|
| 3173 |
+
"has_actual_tool_call": analysis["has_actual_tool_call"],
|
| 3174 |
+
"has_description_without_execution": analysis["has_description_without_execution"],
|
| 3175 |
+
"tool_calls_found": len(tool_calls),
|
| 3176 |
+
"described_actions": analysis["described_actions"][:3],
|
| 3177 |
+
"intended_tool": analysis.get("intended_tool_name"),
|
| 3178 |
+
})
|
| 3179 |
+
|
| 3180 |
+
if analysis["has_description_without_execution"]:
|
| 3181 |
+
results["description_attempts"] += 1
|
| 3182 |
+
|
| 3183 |
+
# المرحلة 4: تطبيق محرك التنفيذ إذا لزم
|
| 3184 |
+
if not tool_calls and analysis["has_description_without_execution"]:
|
| 3185 |
+
enforcement = ExecutionEnforcer.post_process_response(
|
| 3186 |
+
full_response, test_session, test_tools, tool_calls
|
| 3187 |
+
)
|
| 3188 |
+
|
| 3189 |
+
results["steps"].append({
|
| 3190 |
+
"step": 3,
|
| 3191 |
+
"name": "Execution Enforcer",
|
| 3192 |
+
"needs_retry": enforcement["needs_retry"],
|
| 3193 |
+
"retry_message_preview": enforcement["retry_message"][:200],
|
| 3194 |
+
})
|
| 3195 |
+
|
| 3196 |
+
if enforcement["needs_retry"]:
|
| 3197 |
+
results["execution_attempts"] += 1
|
| 3198 |
+
retry_response = ""
|
| 3199 |
+
retry_start = time.time()
|
| 3200 |
+
|
| 3201 |
+
try:
|
| 3202 |
+
for chunk in ask(
|
| 3203 |
+
enforcement["retry_message"], [], "Blackbox", model,
|
| 3204 |
+
tools=test_tools, session_id=test_session
|
| 3205 |
+
):
|
| 3206 |
+
retry_response += chunk
|
| 3207 |
+
except Exception as e:
|
| 3208 |
+
results["steps"].append({
|
| 3209 |
+
"step": 4,
|
| 3210 |
+
"name": "Retry Request",
|
| 3211 |
+
"error": str(e)
|
| 3212 |
+
})
|
| 3213 |
+
|
| 3214 |
+
retry_elapsed = time.time() - retry_start
|
| 3215 |
+
clean_text2, tool_calls2 = ToolCallParser.parse_tool_calls(
|
| 3216 |
+
retry_response, test_tools
|
| 3217 |
+
)
|
| 3218 |
+
|
| 3219 |
+
results["steps"].append({
|
| 3220 |
+
"step": 4,
|
| 3221 |
+
"name": "Retry Request",
|
| 3222 |
+
"response_length": len(retry_response),
|
| 3223 |
+
"response_preview": retry_response[:200],
|
| 3224 |
+
"elapsed_seconds": round(retry_elapsed, 2),
|
| 3225 |
+
"tool_calls_found": len(tool_calls2),
|
| 3226 |
+
})
|
| 3227 |
+
|
| 3228 |
+
if tool_calls2:
|
| 3229 |
+
tool_calls = tool_calls2
|
| 3230 |
+
|
| 3231 |
+
# النتيجة النهائية
|
| 3232 |
+
results["final_tool_calls"] = tool_calls
|
| 3233 |
+
results["success"] = len(tool_calls) > 0
|
| 3234 |
+
|
| 3235 |
+
# تنظيف الجلسة التجريبية
|
| 3236 |
+
with CONTEXT_STORE._lock:
|
| 3237 |
+
if test_session in CONTEXT_STORE._store:
|
| 3238 |
+
del CONTEXT_STORE._store[test_session]
|
| 3239 |
+
|
| 3240 |
+
results["summary"] = {
|
| 3241 |
+
"total_steps": len(results["steps"]),
|
| 3242 |
+
"description_attempts": results["description_attempts"],
|
| 3243 |
+
"execution_attempts": results["execution_attempts"],
|
| 3244 |
+
"tool_calls_extracted": len(results["final_tool_calls"]),
|
| 3245 |
+
"verdict": (
|
| 3246 |
+
"✅ SUCCESS: Tool was actually called"
|
| 3247 |
+
if results["success"]
|
| 3248 |
+
else "❌ FAILURE: Model described but didn't execute"
|
| 3249 |
+
)
|
| 3250 |
+
}
|
| 3251 |
+
|
| 3252 |
+
return results
|
| 3253 |
+
|
| 3254 |
+
# =====================================================
|
| 3255 |
+
# نقطة: مسح كل الجلسات (للتطوير فقط)
|
| 3256 |
+
# =====================================================
|
| 3257 |
+
@app.delete("/context/all/clear")
|
| 3258 |
+
async def clear_all_sessions(request: Request):
|
| 3259 |
+
"""مسح جميع الجلسات - للتطوير فقط"""
|
| 3260 |
+
verify_api_key(request)
|
| 3261 |
+
with CONTEXT_STORE._lock:
|
| 3262 |
+
count = len(CONTEXT_STORE._store)
|
| 3263 |
+
CONTEXT_STORE._store.clear()
|
| 3264 |
+
return {
|
| 3265 |
+
"message": f"Cleared {count} sessions",
|
| 3266 |
+
"timestamp": int(time.time())
|
| 3267 |
+
}
|
| 3268 |
+
|
| 3269 |
+
# =====================================================
|
| 3270 |
+
# نقطة: إضافة نتيجة أداة يدوياً (للاختبار)
|
| 3271 |
+
# =====================================================
|
| 3272 |
+
@app.post("/context/{session_id}/add-tool-result")
|
| 3273 |
+
async def add_tool_result_manually(
|
| 3274 |
+
session_id: str,
|
| 3275 |
+
request: Request
|
| 3276 |
+
):
|
| 3277 |
+
"""إضافة نتيجة أداة يدوياً - مفيد للاختبار"""
|
| 3278 |
+
verify_api_key(request)
|
| 3279 |
+
body = await request.json()
|
| 3280 |
+
tool_name = body.get("tool_name", "unknown")
|
| 3281 |
+
tool_id = body.get("tool_id", ToolCallParser.generate_tool_id())
|
| 3282 |
+
result = body.get("result", "")
|
| 3283 |
+
|
| 3284 |
+
CONTEXT_STORE.add_tool_result(session_id, tool_name, tool_id, result)
|
| 3285 |
+
|
| 3286 |
+
return {
|
| 3287 |
+
"message": f"Tool result added for session {session_id}",
|
| 3288 |
+
"tool_name": tool_name,
|
| 3289 |
+
"tool_id": tool_id,
|
| 3290 |
+
"result_preview": result[:100],
|
| 3291 |
+
"session_state": CONTEXT_STORE.get_or_create_session(session_id).get(
|
| 3292 |
+
"current_state", ExecutionState.IDLE
|
| 3293 |
+
)
|
| 3294 |
+
}
|
| 3295 |
+
|
| 3296 |
+
# =====================================================
|
| 3297 |
+
# نقطة: تحديث حالة الجلسة يدوياً
|
| 3298 |
+
# =====================================================
|
| 3299 |
+
@app.post("/context/{session_id}/set-state")
|
| 3300 |
+
async def set_session_state(session_id: str, request: Request):
|
| 3301 |
+
"""تحديث حالة الجلسة يدوياً"""
|
| 3302 |
+
verify_api_key(request)
|
| 3303 |
+
body = await request.json()
|
| 3304 |
+
new_state = body.get("state", ExecutionState.IDLE)
|
| 3305 |
+
next_action = body.get("next_required_action", "")
|
| 3306 |
+
|
| 3307 |
+
valid_states = [
|
| 3308 |
+
ExecutionState.IDLE,
|
| 3309 |
+
ExecutionState.PLANNING,
|
| 3310 |
+
ExecutionState.TOOL_PENDING,
|
| 3311 |
+
ExecutionState.TOOL_EXECUTING,
|
| 3312 |
+
ExecutionState.TOOL_DONE,
|
| 3313 |
+
ExecutionState.AWAITING_NEXT,
|
| 3314 |
+
ExecutionState.COMPLETED,
|
| 3315 |
+
]
|
| 3316 |
+
|
| 3317 |
+
if new_state not in valid_states:
|
| 3318 |
+
raise HTTPException(
|
| 3319 |
+
status_code=400,
|
| 3320 |
+
detail=f"Invalid state. Valid states: {valid_states}"
|
| 3321 |
+
)
|
| 3322 |
+
|
| 3323 |
+
CONTEXT_STORE.update_session(
|
| 3324 |
+
session_id,
|
| 3325 |
+
current_state=new_state,
|
| 3326 |
+
next_required_action=next_action
|
| 3327 |
+
)
|
| 3328 |
+
|
| 3329 |
+
return {
|
| 3330 |
+
"message": f"Session {session_id} state updated",
|
| 3331 |
+
"new_state": new_state,
|
| 3332 |
+
"next_required_action": next_action,
|
| 3333 |
+
"execution_directive": CONTEXT_STORE.get_execution_directive(session_id)
|
| 3334 |
+
}
|
| 3335 |
+
|
| 3336 |
+
# =====================================================
|
| 3337 |
+
# نقطة: تسجيل أداة معلّقة يدوياً
|
| 3338 |
+
# =====================================================
|
| 3339 |
+
@app.post("/context/{session_id}/register-pending-tool")
|
| 3340 |
+
async def register_pending_tool_manually(
|
| 3341 |
+
session_id: str,
|
| 3342 |
+
request: Request
|
| 3343 |
+
):
|
| 3344 |
+
"""تسجيل أداة معلّقة يدوياً - لإجبار البوت على تنفيذها"""
|
| 3345 |
+
verify_api_key(request)
|
| 3346 |
+
body = await request.json()
|
| 3347 |
+
tool_name = body.get("name", "")
|
| 3348 |
+
tool_args = body.get("arguments", {})
|
| 3349 |
+
reason = body.get("reason", "Manually registered")
|
| 3350 |
+
|
| 3351 |
+
if not tool_name:
|
| 3352 |
+
raise HTTPException(status_code=400, detail="Tool name is required")
|
| 3353 |
+
|
| 3354 |
+
tool_id = ToolCallParser.generate_tool_id()
|
| 3355 |
+
pending_tool = PendingToolCall(
|
| 3356 |
+
name=tool_name,
|
| 3357 |
+
arguments=tool_args,
|
| 3358 |
+
tool_id=tool_id,
|
| 3359 |
+
reason=reason
|
| 3360 |
+
)
|
| 3361 |
+
|
| 3362 |
+
CONTEXT_STORE.register_pending_tool(session_id, pending_tool)
|
| 3363 |
+
|
| 3364 |
+
return {
|
| 3365 |
+
"message": f"Pending tool '{tool_name}' registered for session {session_id}",
|
| 3366 |
+
"tool_id": tool_id,
|
| 3367 |
+
"directive": CONTEXT_STORE.get_execution_directive(session_id)
|
| 3368 |
+
}
|
| 3369 |
+
|
| 3370 |
+
# =====================================================
|
| 3371 |
+
# نقطة: اختبار JSON cleaner
|
| 3372 |
+
# =====================================================
|
| 3373 |
+
@app.post("/debug/test-json-cleaner")
|
| 3374 |
+
async def debug_test_json_cleaner(request: Request):
|
| 3375 |
+
"""اختبار مُصلح JSON على حالات شائعة"""
|
| 3376 |
+
verify_api_key(request)
|
| 3377 |
+
|
| 3378 |
+
test_cases = [
|
| 3379 |
+
{
|
| 3380 |
+
"name": "Trailing comma",
|
| 3381 |
+
"input": '{"name": "write_file", "arguments": {"path": "test.py",}}',
|
| 3382 |
+
"expected_valid": True
|
| 3383 |
+
},
|
| 3384 |
+
{
|
| 3385 |
+
"name": "Curly quotes",
|
| 3386 |
+
"input": '{\u201cname\u201d: \u201cwrite_file\u201d, \u201carguments\u201d: {}}',
|
| 3387 |
+
"expected_valid": True
|
| 3388 |
+
},
|
| 3389 |
+
{
|
| 3390 |
+
"name": "Missing closing brace",
|
| 3391 |
+
"input": '{"name": "write_file", "arguments": {"path": "test.py"',
|
| 3392 |
+
"expected_valid": True
|
| 3393 |
+
},
|
| 3394 |
+
{
|
| 3395 |
+
"name": "Valid JSON",
|
| 3396 |
+
"input": '{"name": "read_file", "arguments": {"path": "config.json"}}',
|
| 3397 |
+
"expected_valid": True
|
| 3398 |
+
},
|
| 3399 |
+
{
|
| 3400 |
+
"name": "Double trailing commas",
|
| 3401 |
+
"input": '{"name": "tool",, "arguments": {}}',
|
| 3402 |
+
"expected_valid": False # هذا صعب الإصلاح
|
| 3403 |
+
},
|
| 3404 |
+
]
|
| 3405 |
+
|
| 3406 |
+
results = []
|
| 3407 |
+
for tc in test_cases:
|
| 3408 |
+
cleaned = ToolCallParser._clean_json_string(tc["input"])
|
| 3409 |
+
parse_success = False
|
| 3410 |
+
parse_result = None
|
| 3411 |
+
error_msg = None
|
| 3412 |
+
|
| 3413 |
+
try:
|
| 3414 |
+
parse_result = json.loads(cleaned)
|
| 3415 |
+
parse_success = True
|
| 3416 |
+
except json.JSONDecodeError as e:
|
| 3417 |
+
error_msg = str(e)
|
| 3418 |
+
# محاولة الإصلاح التلقائي
|
| 3419 |
+
fixed = ToolCallParser._try_fix_json(tc["input"])
|
| 3420 |
+
if fixed:
|
| 3421 |
+
parse_result = fixed
|
| 3422 |
+
parse_success = True
|
| 3423 |
+
error_msg = None
|
| 3424 |
+
|
| 3425 |
+
results.append({
|
| 3426 |
+
"test_name": tc["name"],
|
| 3427 |
+
"original_input": tc["input"],
|
| 3428 |
+
"cleaned_output": cleaned,
|
| 3429 |
+
"parse_success": parse_success,
|
| 3430 |
+
"parse_result": parse_result,
|
| 3431 |
+
"error": error_msg,
|
| 3432 |
+
"expected_valid": tc["expected_valid"],
|
| 3433 |
+
"passed": parse_success == tc["expected_valid"]
|
| 3434 |
+
})
|
| 3435 |
+
|
| 3436 |
+
passed = sum(1 for r in results if r["passed"])
|
| 3437 |
+
return {
|
| 3438 |
+
"test_results": results,
|
| 3439 |
+
"summary": {
|
| 3440 |
+
"passed": passed,
|
| 3441 |
+
"failed": len(results) - passed,
|
| 3442 |
+
"total": len(results)
|
| 3443 |
+
}
|
| 3444 |
+
}
|
| 3445 |
+
|
| 3446 |
+
# =====================================================
|
| 3447 |
+
# نقطة: اختبار ResponseAnalyzer على نص مخصص
|
| 3448 |
+
# =====================================================
|
| 3449 |
+
@app.post("/debug/analyze-response")
|
| 3450 |
+
async def debug_analyze_response(request: Request):
|
| 3451 |
+
"""تحليل نص مخصص بواسطة ResponseAnalyzer"""
|
| 3452 |
+
verify_api_key(request)
|
| 3453 |
+
body = await request.json()
|
| 3454 |
+
response_text = body.get("response", "")
|
| 3455 |
+
tools = body.get("tools", [])
|
| 3456 |
+
|
| 3457 |
+
if not response_text:
|
| 3458 |
+
raise HTTPException(status_code=400, detail="response field is required")
|
| 3459 |
+
|
| 3460 |
+
analysis = ResponseAnalyzer.analyze(response_text, tools)
|
| 3461 |
+
|
| 3462 |
+
# محاكاة قرار المحرك
|
| 3463 |
+
test_session = f"analyze_{uuid.uuid4().hex[:8]}"
|
| 3464 |
+
enforcement = ExecutionEnforcer.post_process_response(
|
| 3465 |
+
response_text, test_session, tools, analysis["tool_calls_found"]
|
| 3466 |
+
)
|
| 3467 |
+
|
| 3468 |
+
# تنظيف
|
| 3469 |
+
with CONTEXT_STORE._lock:
|
| 3470 |
+
if test_session in CONTEXT_STORE._store:
|
| 3471 |
+
del CONTEXT_STORE._store[test_session]
|
| 3472 |
+
|
| 3473 |
+
return {
|
| 3474 |
+
"input_response_preview": response_text[:300],
|
| 3475 |
+
"analysis": {
|
| 3476 |
+
"has_actual_tool_call": analysis["has_actual_tool_call"],
|
| 3477 |
+
"has_description_without_execution": (
|
| 3478 |
+
analysis["has_description_without_execution"]
|
| 3479 |
+
),
|
| 3480 |
+
"needs_execution_push": analysis["needs_execution_push"],
|
| 3481 |
+
"described_actions_found": analysis["described_actions"],
|
| 3482 |
+
"intended_tool_name": analysis.get("intended_tool_name"),
|
| 3483 |
+
"tool_calls_extracted": len(analysis["tool_calls_found"]),
|
| 3484 |
+
"tool_calls_details": analysis["tool_calls_found"],
|
| 3485 |
+
},
|
| 3486 |
+
"enforcer_decision": {
|
| 3487 |
+
"needs_retry": enforcement["needs_retry"],
|
| 3488 |
+
"retry_message": enforcement["retry_message"],
|
| 3489 |
+
},
|
| 3490 |
+
"verdict": (
|
| 3491 |
+
"✅ EXECUTOR: Model actually called a tool"
|
| 3492 |
+
if analysis["has_actual_tool_call"]
|
| 3493 |
+
else (
|
| 3494 |
+
"⚠️ DESCRIBER: Model described without executing"
|
| 3495 |
+
if analysis["has_description_without_execution"]
|
| 3496 |
+
else "ℹ️ NEUTRAL: Regular text response (no tools involved)"
|
| 3497 |
+
)
|
| 3498 |
+
)
|
| 3499 |
+
}
|
| 3500 |
+
|
| 3501 |
+
# =====================================================
|
| 3502 |
+
# نقطة: مراقبة الكاش
|
| 3503 |
+
# =====================================================
|
| 3504 |
+
@app.get("/debug/cache-stats")
|
| 3505 |
+
async def debug_cache_stats(request: Request):
|
| 3506 |
+
"""إحصائيات الكاش"""
|
| 3507 |
+
verify_api_key(request)
|
| 3508 |
+
with CACHE._lock:
|
| 3509 |
+
cache_size = len(CACHE.cache)
|
| 3510 |
+
now = time.time()
|
| 3511 |
+
expired_count = sum(
|
| 3512 |
+
1 for _, ts in CACHE.cache.values()
|
| 3513 |
+
if now - ts > CACHE.ttl
|
| 3514 |
+
)
|
| 3515 |
+
entries = []
|
| 3516 |
+
for key, (value, ts) in list(CACHE.cache.items())[:10]:
|
| 3517 |
+
entries.append({
|
| 3518 |
+
"key_preview": key[:50],
|
| 3519 |
+
"value_length": len(value),
|
| 3520 |
+
"age_seconds": round(now - ts),
|
| 3521 |
+
"expired": now - ts > CACHE.ttl
|
| 3522 |
+
})
|
| 3523 |
+
|
| 3524 |
+
return {
|
| 3525 |
+
"cache_size": cache_size,
|
| 3526 |
+
"max_size": CACHE.max_size,
|
| 3527 |
+
"ttl_seconds": CACHE.ttl,
|
| 3528 |
+
"expired_entries": expired_count,
|
| 3529 |
+
"active_entries": cache_size - expired_count,
|
| 3530 |
+
"sample_entries": entries,
|
| 3531 |
+
"timestamp": int(time.time())
|
| 3532 |
+
}
|
| 3533 |
+
|
| 3534 |
+
# =====================================================
|
| 3535 |
+
# نقطة: تصدير سياق جلسة كاملة
|
| 3536 |
+
# =====================================================
|
| 3537 |
+
@app.get("/context/{session_id}/export")
|
| 3538 |
+
async def export_session_context(session_id: str, request: Request):
|
| 3539 |
+
"""تصدير سياق الجلسة كاملاً بصيغة JSON"""
|
| 3540 |
+
verify_api_key(request)
|
| 3541 |
+
|
| 3542 |
+
ctx = CONTEXT_STORE.get_or_create_session(session_id)
|
| 3543 |
+
summary = CONTEXT_STORE.get_context_summary(session_id)
|
| 3544 |
+
directive = CONTEXT_STORE.get_execution_directive(session_id)
|
| 3545 |
+
|
| 3546 |
+
export_data = {
|
| 3547 |
+
"session_id": session_id,
|
| 3548 |
+
"exported_at": int(time.time()),
|
| 3549 |
+
"context": {
|
| 3550 |
+
"original_task": ctx.get("original_task", ""),
|
| 3551 |
+
"current_state": ctx.get("current_state", ExecutionState.IDLE),
|
| 3552 |
+
"message_count": ctx.get("message_count", 0),
|
| 3553 |
+
"completed_steps": ctx.get("completed_steps", []),
|
| 3554 |
+
"tool_results": ctx.get("tool_results", []),
|
| 3555 |
+
"pending_tool_calls": ctx.get("pending_tool_calls", []),
|
| 3556 |
+
"executed_tool_calls": ctx.get("executed_tool_calls", []),
|
| 3557 |
+
"described_but_not_executed": ctx.get("described_but_not_executed", []),
|
| 3558 |
+
"consecutive_description_count": ctx.get(
|
| 3559 |
+
"consecutive_description_count", 0
|
| 3560 |
+
),
|
| 3561 |
+
"next_required_action": ctx.get("next_required_action", ""),
|
| 3562 |
+
"provider_history": ctx.get("provider_history", [])[-5:],
|
| 3563 |
+
"created_at": ctx.get("created_at", 0),
|
| 3564 |
+
"last_updated": ctx.get("last_updated", 0),
|
| 3565 |
+
},
|
| 3566 |
+
"human_readable_summary": summary,
|
| 3567 |
+
"current_execution_directive": directive,
|
| 3568 |
+
}
|
| 3569 |
+
|
| 3570 |
+
return export_data
|
| 3571 |
+
|
| 3572 |
+
# =====================================================
|
| 3573 |
+
# نقطة: استيراد سياق جلسة
|
| 3574 |
+
# =====================================================
|
| 3575 |
+
@app.post("/context/{session_id}/import")
|
| 3576 |
+
async def import_session_context(session_id: str, request: Request):
|
| 3577 |
+
"""استيراد سياق جلسة من بيانات JSON"""
|
| 3578 |
+
verify_api_key(request)
|
| 3579 |
+
body = await request.json()
|
| 3580 |
+
context_data = body.get("context", {})
|
| 3581 |
+
|
| 3582 |
+
if not context_data:
|
| 3583 |
+
raise HTTPException(status_code=400, detail="context field is required")
|
| 3584 |
+
|
| 3585 |
+
# التحقق من الحقول المطلوبة وتعيين القيم الافتراضية
|
| 3586 |
+
safe_context = {
|
| 3587 |
+
"original_task": context_data.get("original_task", ""),
|
| 3588 |
+
"task_breakdown": context_data.get("task_breakdown", []),
|
| 3589 |
+
"completed_steps": context_data.get("completed_steps", []),
|
| 3590 |
+
"tool_results": context_data.get("tool_results", []),
|
| 3591 |
+
"pending_tool_calls": context_data.get("pending_tool_calls", []),
|
| 3592 |
+
"executed_tool_calls": context_data.get("executed_tool_calls", []),
|
| 3593 |
+
"current_state": context_data.get("current_state", ExecutionState.IDLE),
|
| 3594 |
+
"next_required_action": context_data.get("next_required_action", ""),
|
| 3595 |
+
"created_at": context_data.get("created_at", time.time()),
|
| 3596 |
+
"last_updated": time.time(),
|
| 3597 |
+
"message_count": context_data.get("message_count", 0),
|
| 3598 |
+
"provider_history": context_data.get("provider_history", []),
|
| 3599 |
+
"last_model_response": context_data.get("last_model_response", ""),
|
| 3600 |
+
"described_but_not_executed": context_data.get("described_but_not_executed", []),
|
| 3601 |
+
"consecutive_description_count": context_data.get(
|
| 3602 |
+
"consecutive_description_count", 0
|
| 3603 |
+
),
|
| 3604 |
+
}
|
| 3605 |
+
|
| 3606 |
+
with CONTEXT_STORE._lock:
|
| 3607 |
+
CONTEXT_STORE._store[session_id] = safe_context
|
| 3608 |
+
|
| 3609 |
+
return {
|
| 3610 |
+
"message": f"Context imported for session {session_id}",
|
| 3611 |
+
"session_id": session_id,
|
| 3612 |
+
"state": safe_context["current_state"],
|
| 3613 |
+
"directive": CONTEXT_STORE.get_execution_directive(session_id)
|
| 3614 |
+
}
|
| 3615 |
+
|
| 3616 |
+
# =====================================================
|
| 3617 |
+
# نقطة: اختبار pattern matching للأنماط المحظورة
|
| 3618 |
+
# =====================================================
|
| 3619 |
+
@app.post("/debug/test-patterns")
|
| 3620 |
+
async def debug_test_patterns(request: Request):
|
| 3621 |
+
"""اختبار أنماط كشف الوصف بدون تنفيذ"""
|
| 3622 |
+
verify_api_key(request)
|
| 3623 |
+
body = await request.json()
|
| 3624 |
+
test_texts = body.get("texts", [
|
| 3625 |
+
"سأقوم بإنشاء الملف الآن",
|
| 3626 |
+
"I will create the file",
|
| 3627 |
+
"Let me write the code",
|
| 3628 |
+
"I'm going to use write_file",
|
| 3629 |
+
"Here is the result: 42",
|
| 3630 |
+
"سأستخدم أداة write_file",
|
| 3631 |
+
"Now I'll create the Python file",
|
| 3632 |
+
"The file has been created successfully",
|
| 3633 |
+
"I'll use the write_file tool to save your code",
|
| 3634 |
+
"Using the write_file tool to create the file",
|
| 3635 |
+
])
|
| 3636 |
+
|
| 3637 |
+
results = []
|
| 3638 |
+
for text in test_texts:
|
| 3639 |
+
matched_patterns = []
|
| 3640 |
+
for pattern in ResponseAnalyzer.DESCRIPTION_PHRASES:
|
| 3641 |
+
matches = re.findall(pattern, text, re.IGNORECASE)
|
| 3642 |
+
if matches:
|
| 3643 |
+
matched_patterns.append({
|
| 3644 |
+
"pattern": pattern,
|
| 3645 |
+
"matches": matches
|
| 3646 |
+
})
|
| 3647 |
+
|
| 3648 |
+
is_description = len(matched_patterns) > 0
|
| 3649 |
+
results.append({
|
| 3650 |
+
"text": text,
|
| 3651 |
+
"is_description_without_execution": is_description,
|
| 3652 |
+
"matched_patterns_count": len(matched_patterns),
|
| 3653 |
+
"matched_patterns": matched_patterns[:3], # أول 3 فقط
|
| 3654 |
+
"verdict": (
|
| 3655 |
+
"⚠️ DESCRIPTION (needs enforcement)"
|
| 3656 |
+
if is_description
|
| 3657 |
+
else "✅ OK (not a description pattern)"
|
| 3658 |
+
)
|
| 3659 |
+
})
|
| 3660 |
+
|
| 3661 |
+
description_count = sum(1 for r in results if r["is_description_without_execution"])
|
| 3662 |
+
return {
|
| 3663 |
+
"test_results": results,
|
| 3664 |
+
"summary": {
|
| 3665 |
+
"total_texts": len(results),
|
| 3666 |
+
"description_patterns_found": description_count,
|
| 3667 |
+
"clean_texts": len(results) - description_count,
|
| 3668 |
+
"total_patterns_available": len(ResponseAnalyzer.DESCRIPTION_PHRASES),
|
| 3669 |
+
}
|
| 3670 |
+
}
|
| 3671 |
+
|
| 3672 |
+
# =====================================================
|
| 3673 |
+
# نقطة: إحصائيات المزودين مع الأداء
|
| 3674 |
+
# =====================================================
|
| 3675 |
+
@app.get("/providers/stats")
|
| 3676 |
+
async def get_provider_stats(request: Request):
|
| 3677 |
+
"""إحصائيات المزودين مع معلومات الأداء من التاريخ"""
|
| 3678 |
+
verify_api_key(request)
|
| 3679 |
+
|
| 3680 |
+
provider_usage = {}
|
| 3681 |
+
for pname in REAL_PROVIDERS.keys():
|
| 3682 |
+
provider_usage[pname] = {
|
| 3683 |
+
"total_uses": 0,
|
| 3684 |
+
"recent_uses": 0,
|
| 3685 |
+
"models_used": set(),
|
| 3686 |
+
}
|
| 3687 |
+
|
| 3688 |
+
# تحليل تاريخ الاستخدام من الجلسات
|
| 3689 |
+
with CONTEXT_STORE._lock:
|
| 3690 |
+
for sid, ctx in CONTEXT_STORE._store.items():
|
| 3691 |
+
for ph in ctx.get("provider_history", []):
|
| 3692 |
+
pname = ph.get("provider", "")
|
| 3693 |
+
model = ph.get("model", "")
|
| 3694 |
+
ts = ph.get("timestamp", 0)
|
| 3695 |
+
|
| 3696 |
+
if pname in provider_usage:
|
| 3697 |
+
provider_usage[pname]["total_uses"] += 1
|
| 3698 |
+
if model:
|
| 3699 |
+
provider_usage[pname]["models_used"].add(model)
|
| 3700 |
+
# استخدام خلال آخر ساعة
|
| 3701 |
+
if time.time() - ts < 3600:
|
| 3702 |
+
provider_usage[pname]["recent_uses"] += 1
|
| 3703 |
+
|
| 3704 |
+
# تحويل sets إلى lists للـ JSON
|
| 3705 |
+
result = {}
|
| 3706 |
+
for pname, stats in provider_usage.items():
|
| 3707 |
+
pobj = REAL_PROVIDERS.get(pname)
|
| 3708 |
+
available_models = discover_provider_models(pobj, pname) if pobj else []
|
| 3709 |
+
result[pname] = {
|
| 3710 |
+
"available": True,
|
| 3711 |
+
"total_uses": stats["total_uses"],
|
| 3712 |
+
"recent_uses_last_hour": stats["recent_uses"],
|
| 3713 |
+
"models_actually_used": list(stats["models_used"]),
|
| 3714 |
+
"all_available_models": available_models,
|
| 3715 |
+
}
|
| 3716 |
+
|
| 3717 |
+
return {
|
| 3718 |
+
"providers": result,
|
| 3719 |
+
"timestamp": int(time.time())
|
| 3720 |
+
}
|
| 3721 |
+
|
| 3722 |
+
# =====================================================
|
| 3723 |
+
# نقطة: اختبار stream buffer مع أداة كاملة
|
| 3724 |
+
# =====================================================
|
| 3725 |
+
@app.post("/debug/test-stream-with-tool")
|
| 3726 |
+
async def debug_test_stream_with_tool(request: Request):
|
| 3727 |
+
"""اختبار stream buffer مع أداة كاملة مقسّمة على chunks"""
|
| 3728 |
+
verify_api_key(request)
|
| 3729 |
+
body = await request.json()
|
| 3730 |
+
chunk_size = body.get("chunk_size", 5)
|
| 3731 |
+
|
| 3732 |
+
test_tools = [{
|
| 3733 |
+
"name": "write_file",
|
| 3734 |
+
"description": "Write content to a file",
|
| 3735 |
+
"input_schema": {
|
| 3736 |
+
"type": "object",
|
| 3737 |
+
"properties": {
|
| 3738 |
+
"path": {"type": "string"},
|
| 3739 |
+
"content": {"type": "string"}
|
| 3740 |
+
},
|
| 3741 |
+
"required": ["path", "content"]
|
| 3742 |
+
}
|
| 3743 |
+
}]
|
| 3744 |
+
|
| 3745 |
+
# نص يحتوي على tool call كاملة
|
| 3746 |
+
test_text = (
|
| 3747 |
+
'I will create the file for you.\n'
|
| 3748 |
+
'<tool_call>\n'
|
| 3749 |
+
'{\n'
|
| 3750 |
+
' "name": "write_file",\n'
|
| 3751 |
+
' "arguments": {\n'
|
| 3752 |
+
' "path": "hello.py",\n'
|
| 3753 |
+
' "content": "print(\'Hello World\')"\n'
|
| 3754 |
+
' }\n'
|
| 3755 |
+
'}\n'
|
| 3756 |
+
'</tool_call>\n'
|
| 3757 |
+
'The file has been created.'
|
| 3758 |
+
)
|
| 3759 |
+
|
| 3760 |
+
buffer = StreamToolBuffer(available_tools=test_tools)
|
| 3761 |
+
all_events = []
|
| 3762 |
+
text_events = []
|
| 3763 |
+
tool_events = []
|
| 3764 |
+
|
| 3765 |
+
# محاكاة الـ streaming chunk by chunk
|
| 3766 |
+
for i in range(0, len(test_text), chunk_size):
|
| 3767 |
+
chunk = test_text[i:i + chunk_size]
|
| 3768 |
+
events = buffer.feed(chunk)
|
| 3769 |
+
for event in events:
|
| 3770 |
+
all_events.append({
|
| 3771 |
+
"chunk_index": i // chunk_size,
|
| 3772 |
+
"chunk_preview": chunk[:10],
|
| 3773 |
+
"event_type": event.get("type", "tool_use"),
|
| 3774 |
+
"event_preview": (
|
| 3775 |
+
event.get("text", "")[:30]
|
| 3776 |
+
if event.get("type") == "text"
|
| 3777 |
+
else f"tool: {event.get('name', '')}"
|
| 3778 |
+
)
|
| 3779 |
+
})
|
| 3780 |
+
if event.get("type") == "text":
|
| 3781 |
+
text_events.append(event["text"])
|
| 3782 |
+
else:
|
| 3783 |
+
tool_events.append(event)
|
| 3784 |
+
|
| 3785 |
+
# تفريغ ما تبقى
|
| 3786 |
+
remaining = buffer.flush()
|
| 3787 |
+
for event in remaining:
|
| 3788 |
+
all_events.append({
|
| 3789 |
+
"chunk_index": "flush",
|
| 3790 |
+
"event_type": event.get("type", "tool_use"),
|
| 3791 |
+
"event_preview": (
|
| 3792 |
+
event.get("text", "")[:30]
|
| 3793 |
+
if event.get("type") == "text"
|
| 3794 |
+
else f"tool: {event.get('name', '')}"
|
| 3795 |
+
)
|
| 3796 |
+
})
|
| 3797 |
+
if event.get("type") == "text":
|
| 3798 |
+
text_events.append(event["text"])
|
| 3799 |
+
else:
|
| 3800 |
+
tool_events.append(event)
|
| 3801 |
+
|
| 3802 |
+
return {
|
| 3803 |
+
"input_text_length": len(test_text),
|
| 3804 |
+
"chunk_size": chunk_size,
|
| 3805 |
+
"total_chunks": (len(test_text) + chunk_size - 1) // chunk_size,
|
| 3806 |
+
"total_events": len(all_events),
|
| 3807 |
+
"text_events_count": len(text_events),
|
| 3808 |
+
"tool_events_count": len(tool_events),
|
| 3809 |
+
"reconstructed_text": "".join(text_events),
|
| 3810 |
+
"tool_calls_detected": tool_events,
|
| 3811 |
+
"success": len(tool_events) > 0,
|
| 3812 |
+
"verdict": (
|
| 3813 |
+
"✅ Stream buffer correctly detected tool call"
|
| 3814 |
+
if len(tool_events) > 0
|
| 3815 |
+
else "❌ Stream buffer missed the tool call"
|
| 3816 |
+
),
|
| 3817 |
+
"all_events_sample": all_events[:20]
|
| 3818 |
+
}
|
| 3819 |
+
|
| 3820 |
+
# =====================================================
|
| 3821 |
+
# MAIN ENTRY POINT
|
| 3822 |
+
# =====================================================
|
| 3823 |
+
if __name__ == "__main__":
|
| 3824 |
+
import uvicorn
|
| 3825 |
+
|
| 3826 |
+
port = int(os.getenv("PORT", 8000))
|
| 3827 |
+
host = os.getenv("HOST", "0.0.0.0")
|
| 3828 |
+
workers = int(os.getenv("WORKERS", 1))
|
| 3829 |
+
log_level = os.getenv("LOG_LEVEL", "info").lower()
|
| 3830 |
+
|
| 3831 |
+
logger.info(f"Starting G4F Execution Engine v6.0.0")
|
| 3832 |
+
logger.info(f"Host: {host}, Port: {port}")
|
| 3833 |
+
logger.info(f"Workers: {workers}")
|
| 3834 |
+
logger.info(f"Cookie Status: {COOKIE_STATUS}")
|
| 3835 |
+
logger.info(f"Providers: {list(REAL_PROVIDERS.keys())}")
|
| 3836 |
+
|
| 3837 |
+
uvicorn.run(
|
| 3838 |
+
"main:app",
|
| 3839 |
+
host=host,
|
| 3840 |
+
port=port,
|
| 3841 |
+
workers=workers,
|
| 3842 |
+
log_level=log_level,
|
| 3843 |
+
reload=False,
|
| 3844 |
+
access_log=True,
|
| 3845 |
)
|