Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# ==========================================
# YiGraph 环境变量配置模板
# 复制此文件为 .env 并填入实际值:
# cp .env.example .env
# ==========================================

# ---------- LLM API Keys ----------
# 百炼 DashScope API Key(用于通义系列模型)
DASHSCOPE_API_KEY=your_dashscope_key_here

# OpenAI 兼容 API Key(用于 GPT 系列模型及兼容接口)
OPENAI_API_KEY=your_openai_key_here

# ---------- 数据库密码 ----------
# Neo4j 图数据库密码
NEO4J_PASSWORD=your_neo4j_password

# NebulaGraph 图数据库密码
NEBULA_PASSWORD=your_nebula_password

# ---------- Web 安全 ----------
# Flask 调试模式(生产环境必须设为 0)
FLASK_DEBUG=0

# CORS 允许的来源地址(前端部署域名)
CORS_ORIGINS=http://localhost:5089

# WebSocket 认证 Token
YIGRAPH_WS_TOKEN=your_websocket_token
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,13 @@ docs/_build

# database
*.db

# ==========================================
# 含敏感凭证的配置文件(禁止提交真实密钥)
# ==========================================
web/frontend/models.json
aag/expert_search_engine/data_process/openai_extractor/openai_config.json
aag/expert_search_engine/data_process/openai_extractor/openai_config_4o.json

# 示例模板(允许提交)
!*.example
217 changes: 100 additions & 117 deletions aag/api/services/chat_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,6 @@
import logging
import asyncio
from typing import Dict, Any, Optional, Callable, List
import sys
import os

# Add project root to path
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))))

from .engine_service import EngineService

Expand All @@ -36,14 +31,14 @@ class ChatService:
def __init__(self):
"""Initialize chat service."""
self.engine_service = EngineService.get_instance()

async def process_message(
self,
message: str,
model: Optional[str] = None,
dataset: Optional[str] = None,
mode: str = "normal",
expert_mode: bool = False
expert_mode: bool = False,
) -> Dict[str, Any]:
"""
Process a user message (sync-style API).
Expand All @@ -68,26 +63,19 @@ async def process_message(
logger.info(f"Select dataset: {dataset}")
engine.specific_dataset(dataset)

logger.info(f"Process message: mode={mode}, model={model}, message={message[:50]}...")
logger.info(
f"Process message: mode={mode}, model={model}, message={message[:50]}..."
)

result = await engine.run(message, mode=mode)

return {
"success": True,
"result": result,
"mode": mode
}
return {"success": True, "result": result, "mode": mode}
except Exception as e:
logger.error(f"Process message failed: {str(e)}")
return {
"success": False,
"error": str(e),
"mode": mode
}

return {"success": False, "error": str(e), "mode": mode}

async def process_dag_modification(
self,
modification_request: str
self, modification_request: str
) -> Dict[str, Any]:
"""
Handle DAG modification request (expert mode).
Expand All @@ -104,17 +92,11 @@ async def process_dag_modification(

result = await engine.expert_modify_dag(modification_request)

return {
"success": True,
**result
}
return {"success": True, **result}
except Exception as e:
logger.error(f"DAG modification failed: {str(e)}")
return {
"success": False,
"error": str(e)
}

return {"success": False, "error": str(e)}

async def start_expert_analysis(self) -> Dict[str, Any]:
"""
Start expert-mode analysis.
Expand All @@ -128,17 +110,11 @@ async def start_expert_analysis(self) -> Dict[str, Any]:

result = await engine.expert_start_analysis()

return {
"success": True,
"result": result
}
return {"success": True, "result": result}
except Exception as e:
logger.error(f"Expert analysis failed: {str(e)}")
return {
"success": False,
"error": str(e)
}

return {"success": False, "error": str(e)}

async def process_streaming_chat(
self,
message: str,
Expand All @@ -147,7 +123,7 @@ async def process_streaming_chat(
dataset_type: Optional[str] = None,
mode: str = "normal",
expert_mode: bool = False,
callback: Optional[Callable[[Dict[str, Any]], None]] = None
callback: Optional[Callable[[Dict[str, Any]], None]] = None,
):
"""
Stream chat (for WebSocket).
Expand All @@ -168,11 +144,13 @@ async def process_streaming_chat(
engine = self.engine_service.get_engine()

if callback:
callback({
"type": "thinking",
"contentType": "text",
"content": "Analyzing your question..."
})
callback(
{
"type": "thinking",
"contentType": "text",
"content": "Analyzing your question...",
}
)

if dataset:
logger.info(f"Select dataset: {dataset}, type: {dataset_type}")
Expand All @@ -182,71 +160,79 @@ async def process_streaming_chat(

result = await engine.run(message, mode=mode, callback=callback)

if mode in {"interact", "expert"} and isinstance(result, dict) and "dag_info" in result:
if (
mode in {"interact", "expert"}
and isinstance(result, dict)
and "dag_info" in result
):
dag_content = self._convert_dag_to_frontend_format(result)
if callback:
callback({
"type": "result",
"contentType": "dag",
"content": dag_content
})
elif mode in {"interact", "expert"} and isinstance(result, dict) and "error" in result:
callback(
{"type": "result", "contentType": "dag", "content": dag_content}
)
elif (
mode in {"interact", "expert"}
and isinstance(result, dict)
and "error" in result
):
if callback:
callback({
"type": "result",
"contentType": "text",
"content": CHAT_FRIENDLY_ERROR_MSG
})
callback(
{
"type": "result",
"contentType": "text",
"content": CHAT_FRIENDLY_ERROR_MSG,
}
)
elif mode == "normal" and isinstance(result, dict) and "dag_info" in result:
# Normal mode: result has DAG info — send DAG first
dag_content = self._convert_dag_to_frontend_format({
"dag_info": result.get("dag_info", {})
})
dag_content = self._convert_dag_to_frontend_format(
{"dag_info": result.get("dag_info", {})}
)
if callback:
callback({
"type": "result",
"contentType": "dag",
"content": dag_content
})
callback(
{"type": "result", "contentType": "dag", "content": dag_content}
)

result_text = result.get("analysis_result", "")
if callback and result_text:
paragraphs = [p.strip() for p in result_text.split('\n') if p.strip()]
paragraphs = [
p.strip() for p in result_text.split("\n") if p.strip()
]
for para in paragraphs:
callback({
"type": "result",
"contentType": "text",
"content": para
})
callback(
{"type": "result", "contentType": "text", "content": para}
)
else:
result_text = str(result) if result else "No result."
if _is_scheduler_error_text(result_text):
result_text = CHAT_FRIENDLY_ERROR_MSG
if callback:
paragraphs = [p.strip() for p in result_text.split('\n') if p.strip()]
paragraphs = [
p.strip() for p in result_text.split("\n") if p.strip()
]
for para in paragraphs:
callback({
"type": "result",
"contentType": "text",
"content": para
})
callback(
{"type": "result", "contentType": "text", "content": para}
)
if callback:
callback({
"type": "stream_end"
})

callback({"type": "stream_end"})

except Exception as e:
logger.error(f"Stream chat failed: {str(e)}", exc_info=True)
if callback:
callback({
"type": "result",
"contentType": "text",
"content": CHAT_FRIENDLY_ERROR_MSG
})
callback(
{
"type": "result",
"contentType": "text",
"content": CHAT_FRIENDLY_ERROR_MSG,
}
)
callback({"type": "stream_end"})
return

def _convert_dag_to_frontend_format(self, dag_result: Dict[str, Any]) -> Dict[str, Any]:

def _convert_dag_to_frontend_format(
self, dag_result: Dict[str, Any]
) -> Dict[str, Any]:
"""
Convert DAG result to frontend format.

Expand Down Expand Up @@ -277,38 +263,36 @@ def _convert_dag_to_frontend_format(self, dag_result: Dict[str, Any]) -> Dict[st
else:
tasktype_str = "Unknown"

nodes.append({
"id": str(step_id),
"label": step_info.get("question", ""),
"tasktype": tasktype_str
})
nodes.append(
{
"id": str(step_id),
"label": step_info.get("question", ""),
"tasktype": tasktype_str,
}
)

edges_info = dag_info.get("edges", [])
if edges_info:
edges = edges_info
else:
topological_order = dag_info.get("topological_order", [])
for i in range(len(topological_order) - 1):
edges.append({
"from": str(topological_order[i]),
"to": str(topological_order[i + 1])
})

return {
"nodes": nodes,
"edges": edges
}
edges.append(
{
"from": str(topological_order[i]),
"to": str(topological_order[i + 1]),
}
)

return {"nodes": nodes, "edges": edges}
except Exception as e:
logger.error(f"Convert DAG format failed: {str(e)}")
return {
"nodes": [],
"edges": []
}

return {"nodes": [], "edges": []}

def process_dag_confirmation(
self,
dag_confirm: str,
callback: Optional[Callable[[Dict[str, Any]], None]] = None
callback: Optional[Callable[[Dict[str, Any]], None]] = None,
) -> Dict[str, Any]:
"""
Handle DAG confirmation (yes/no).
Expand All @@ -324,12 +308,11 @@ def process_dag_confirmation(
return self.start_expert_analysis()
else:
if callback:
callback({
"type": "result",
"contentType": "text",
"content": "DAG rejected. Please modify and resubmit."
})
return {
"success": True,
"message": "DAG rejected"
}
callback(
{
"type": "result",
"contentType": "text",
"content": "DAG rejected. Please modify and resubmit.",
}
)
return {"success": True, "message": "DAG rejected"}
Loading