
sidebar.wechat

sidebar.feishu
sidebar.chooseYourWayToJoin

sidebar.scanToAddConsultant
In AskTable Canvas, Data nodes produce SQL, Chart nodes build visual configs, and Python nodes run transforms. This post sketches how we split responsibilities across agents.
Tools: schema_linker, sql_executor, sql_guard
Tools: chart_recommender, echarts_generator
Tools: python_executor, dataframe_loader
class BaseAgent(ABC):
"""Base agent for a Canvas node."""
def __init__(self, node: NodeModel):
self.node = node
self.messages: list[dict] = []
self.result: dict = {}
@abstractmethod
async def run(self, message: str) -> AsyncGenerator[dict, None]:
"""Stream events."""
...
@abstractmethod
def get_result(self) -> dict:
...
def get_messages(self) -> list[dict]:
return self.messages
class DataNodeAgent(BaseAgent):
async def run(self, message: str) -> AsyncGenerator[dict, None]:
yield {"type": "status", "data": "Linking schema..."}
meta_context = await self.schema_linker.link(message)
yield {"type": "status", "data": "Generating SQL..."}
sql = await self.generate_sql(message, meta_context)
yield {"type": "sql", "data": sql}
yield {"type": "status", "data": "Executing..."}
df = await self.datasource.execute_sql(sql)
yield {"type": "dataframe", "data": df.to_dict()}
yield {"type": "status", "data": "Summarizing..."}
explanation = await self.generate_explanation(message, df)
yield {"type": "explanation", "data": explanation}
self.result = {"sql": sql, "dataframe": df, "explanation": explanation}
class ChartNodeAgent(BaseAgent):
async def run(self, message: str) -> AsyncGenerator[dict, None]:
yield {"type": "status", "data": "Profiling data..."}
df = self.parent_contexts[0]["dataframe"]
yield {"type": "status", "data": "Choosing chart..."}
chart_type = await self.recommend_chart_type(df, message)
yield {"type": "chart_type", "data": chart_type}
yield {"type": "status", "data": "Building config..."}
config = await self.generate_chart_config(df, chart_type, message)
yield {"type": "config", "data": config}
self.result = {"chart_type": chart_type, "config": config}
class ToolRegistry:
def __init__(self):
self.tools: dict[str, Callable] = {}
def register(self, name: str, func: Callable):
self.tools[name] = func
async def call(self, name: str, **kwargs) -> Any:
if name not in self.tools:
raise ValueError(f"Tool {name} not found")
return await self.tools[name](**kwargs)
registry = ToolRegistry()
registry.register("execute_sql", execute_sql)
registry.register("generate_chart", generate_chart)
See also: Canvas streaming execution · asktable.com
sidebar.noProgrammingNeeded
sidebar.startFreeTrial