v1μ Phase νμ΄νλΌμΈμ μ μ§νλ©΄μ, λꡬ λͺ©λ‘μ΄ μμ±κΈ°Β·μ€νκΈ°Β·κ²μ¦κΈ°λ₯Ό κ΄ν΅νμ¬ λμ μΌλ‘ νλ₯΄κ² νλ€. μλλ°μ€λ₯Ό λκ΅¬λ‘ λ ΈμΆνκ³ , μΊλ²μ€ μμ΄λ μν¬νλ‘μ°λ₯Ό μ½λλ‘ μ‘°λ¦½Β·λΆκΈ°ν μ μκ² νλ€. ν΅μ¬ μ€κ³ μ² ν: νλμ ToolRegistryλ₯Ό λͺ¨λ μ»΄ν¬λνΈκ° 곡μ νκ³ , λ§€ iterationλ§λ€ μ΅μ λꡬλ₯Ό μ‘°ννλ€.
v2λ v1μ 5-Phase νμ΄νλΌμΈμ κ·Έλλ‘ μ μ§νλ€. Phase μμΈλ blueprint.md μ°Έμ‘°.
Phase 1: PREPARE μ μ²λ¦¬ β μν¬νλ‘μ° λ‘λ, λꡬ μμ§, 컨ν
μ€νΈ 쑰립
Phase 2: RESOLVE κ²°μ β λꡬ κ²μ, μ
λ ₯ λ§€ν, μ€ν€λ§ κ²°μ
Phase 3: EXECUTE μ€ν β ThinkβActβObserve 루ν, λꡬ νΈμΆ
Phase 4: STREAM μ λ¬ β μ€ν μ΄λ²€νΈ β SSE λ³ν β ν΄λΌμ΄μΈνΈ
Phase 5: PERSIST μ μ₯ β DB, Trace, μΈμ
μν, 리μμ€ μ 리
v2λ μ΄ μμ λμ λꡬ νλ¦, μλλ°μ€ λꡬν, μν¬νλ‘μ° μμ§, Tool Storeλ₯Ό μΉλλ€.
v1 νλ¦:
Phase 2 (RESOLVE) β tools_schema κ²°μ (κ³ μ )
Phase 3 (EXECUTE) β μ΄ κ³ μ λ λͺ©λ‘μΌλ‘ κ³μ μ€ν
Iteration 1: LLM β create_tool("send_slack") β μμ± + Registry λ±λ‘
Iteration 2: LLM β send_slack("#general", "hello") β β λꡬ μμ
β tools_schemaκ° Phase 2μμ μ°μ μ€λ
μ·μ΄λΌ μ λκ΅¬κ° μ 보μ
mantis v2 ν΄κ²° λ°©μ:
v2 νλ¦:
Executor._resolve_tools()κ° λ§€ iterationλ§λ€ νΈμΆλ¨
β registry.to_openai_tools()κ° νμ¬ μμ μ μ 체 λꡬ λ°ν
β create_toolλ‘ λ±λ‘λ λκ΅¬κ° λ€μ iterationμμ μ¦μ μ¬μ© κ°λ₯
v1:
DockerSandbox β ToolGenerator λ΄λΆμμλ§ μ¬μ©
Agentκ° "μ΄ μ½λ λλ €λ΄" ν μ μμ
mantis v2:
@tool(name="execute_code") β Agentκ° μμ λ‘κ² μ½λ μ€ν
@tool(name="execute_code_with_test") β μ½λ + ν
μ€νΈ ν¨κ» μ€ν
β μΌλ° λꡬμ λμΌνκ² Registryμ λ±λ‘λ¨
OpenAI Codex:
Containerλ₯Ό λꡬμ²λΌ λμ§ β μ€ν β κ²°κ³Ό β λ€μ νλ¨
xgen-workflow:
async_workflow_executor.py β μΊλ²μ€ JSONμ λ
Έλ/μ£μ§λ₯Ό μν
λꡬ λͺ©λ‘ = μΊλ²μ€ μ£μ§μ μ°κ²°λ κ²λ§ (κ³ μ )
Router = μ μ λΆκΈ° (μ€ν μ€ μ‘°κ±΄ λ³κ²½ λΆκ°)
β μ½λλ‘ μν¬νλ‘μ°λ₯Ό 쑰립ν μ μμ
β AIκ° μν¬νλ‘μ°λ₯Ό μλ μμ±ν μ μμ
β μ€ν μ€ λμ λΆκΈ° λΆκ°
λꡬ μμ±κΈ° μ€νκΈ° xgen-workflow (μΊλ²μ€)
λꡬλ₯Ό λ§λ¦ λꡬλ₯Ό μ λ
Έλλ‘ λꡬ μ°κ²°
ββ μ€νκΈ°μ λΆλ¦¬λ¨ ββ μμ±κΈ°μ λΆλ¦¬ ββ μ€μκ° λκΈ°ν μμ
λ―Έλ: xgen μ± μ¬λ¬ κ°
μ± A (κ³ κ° μλ΄) μ± B (λ°μ΄ν° λΆμ) μ± C (μ΄μ μλν)
β κ°μ λꡬ νμ 곡μ ν΄μΌ ν¨
| λ¬Έμ | v1 μν | v2 λͺ©ν |
|---|---|---|
| λꡬ λͺ©λ‘ κ³ μ | Phase 2μμ μ€λ μ· | λ§€ iterationλ§λ€ Registry μ¬μ‘°ν |
| μλλ°μ€ | ToolGenerator λ΄λΆ μΈνλΌ | Agentκ° νΈμΆνλ λꡬ |
| λꡬ μμ± β μ¦μ μ¬μ© | λΆκ° | μμ± β Registry λ±λ‘ β λ€μ iterationμμ μ¬μ© |
| μν¬νλ‘μ° | μΊλ²μ€ JSONλ§ | μ½λ 쑰립 + μΊλ²μ€ νΈν + λμ λΆκΈ° |
| λ©ν° μ± | λ¨μΌ νλ‘μΈμ€ | 곡μ Registry λ°±μλ |
Phase 2 (RESOLVE) Phase 3 (EXECUTE)
tools_schema κ²°μ ββ(κ³ μ μ λ¬)βββ μ΄ λͺ©λ‘μΌλ‘ μ€ν
ToolRegistry (νλμ μΈμ€ν΄μ€)
β
ββββββββββββββββββββββΌβββββββββββββββββββββ
β β β
Executor ToolGenerator WorkflowEngine
λ§€ iterationλ§λ€ μμ± β μ¦μ λ±λ‘ λ
Έλλ³ λꡬ λ°μΈλ©
to_openai_tools()
μ¬μ‘°ν
mantis v2μ ν¨ν΄:
# νλμ registryλ₯Ό λͺ¨λ κ³³μμ μ°Έμ‘°
tool_registry = ToolRegistry()
# β Agent, ToolGenerator, GraphToolManager λͺ¨λ μ΄ νλμ μΈμ€ν΄μ€ 곡μ
# β create_toolμ΄ registryμ λ±λ‘νλ©΄ Agentμ λ€μ iterationμμ μ¦μ 보μμ
λ ₯: ToolSpec (name, description, parameters, fn)
μΆλ ₯: OpenAI function calling μ€ν€λ§
νλ μΌ: λ±λ‘/μ‘°ν/μ€ν
μ
λ ₯: ToolSpec + source + session_id (μ ν)
μΆλ ₯: OpenAI function calling μ€ν€λ§ (κΈλ‘λ² + μΈμ
λꡬ ν©μ°)
νλ μΌ:
1. λꡬ λ±λ‘ (κΈλ‘λ² λλ μΈμ
μ€μ½ν)
2. μμ€ μΆμ (builtin, generated, sandbox, mcp, openapi)
3. μΈμ
λ³ λ
립 λꡬ κ΄λ¦¬
4. μΈμ
μ’
λ£ μ μ 리
class ToolRegistry:
"""v2: μΈμ
μ€μ½ν + μμ€ μΆμ .
v1κ³Ό νμ νΈν: session_idλ₯Ό μ μ°λ©΄ v1κ³Ό λμΌνκ² λμ.
"""
_tools: dict[str, ToolSpec] # κΈλ‘λ² λꡬ
_sources: dict[str, str] # tool_name β source
_session_tools: dict[str, dict[str, ToolSpec]] # session_id β {name: spec}
def register(self, spec: ToolSpec, source: str = "manual", session_id: str | None = None):
"""λꡬ λ±λ‘.
session_id μμΌλ©΄ β ν΄λΉ μΈμ
μμλ§ λ³΄μ΄λ λꡬ
session_id μμΌλ©΄ β κΈλ‘λ² (λͺ¨λ μΈμ
μμ μ¬μ©)
"""
if session_id:
self._session_tools.setdefault(session_id, {})[spec.name] = spec
else:
self._tools[spec.name] = spec
self._sources[spec.name] = source
def to_openai_tools(self, session_id: str | None = None) -> list[dict]:
"""νμ¬ μμ μ λꡬ λͺ©λ‘ λ°ν.
β
ν΅μ¬: νΈμΆν λλ§λ€ μ΅μ μνλ₯Ό λ°ννλ€.
create_toolλ‘ λ±λ‘λ λκ΅¬κ° μ¦μ ν¬ν¨λ¨.
"""
tools = dict(self._tools)
if session_id and session_id in self._session_tools:
tools.update(self._session_tools[session_id])
return [spec.to_openai_schema() for spec in tools.values()]
def cleanup_session(self, session_id: str):
"""μΈμ
μ’
λ£ μ μΈμ
μ€μ½ν λꡬ μ 리."""
self._session_tools.pop(session_id, None)μ
λ ₯: ResolvedContext (tools_schema κ³ μ )
β λͺ¨λ iterationμμ κ°μ λꡬ λͺ©λ‘ μ¬μ©
μ
λ ₯: ResolvedContext + ToolRegistry μ°Έμ‘°
β λ§€ iterationλ§λ€ registry.to_openai_tools(session_id) μ¬μ‘°ν
β create_toolλ‘ λ§λ λκ΅¬κ° λ€μ iterationμμ μ¦μ μ¬μ© κ°λ₯
class ExecutePhase:
async def run(self, ctx: ResolvedContext) -> AsyncGenerator[ExecutionEvent]:
for iteration in range(max_iterations):
# β
v2: λ§€ iterationλ§λ€ μ΅μ λꡬ μ‘°ν
tools_schema = self._resolve_tools(
query=ctx.context.message,
session_id=ctx.context.session_id,
)
# Think
yield ExecutionEvent("thinking", {"iteration": iteration})
response = await self.llm.generate(
messages=conversation.to_messages(),
tools=tools_schema,
)
# μ’
λ£
if not response.tool_calls:
yield ExecutionEvent("done", {"text": response.text})
return
# Act
for tc in response.tool_calls:
tc = self.validate(tc)
if self.approval and self.approval.needs(tc):
yield ExecutionEvent("approval_required", tc.to_dict())
decision = await self.approval.wait()
if not decision.approved:
continue
yield ExecutionEvent("tool_call", {"name": tc.name, "args": tc.args})
result = await self.tool_registry.execute(tc)
yield ExecutionEvent("tool_result", {"name": tc.name, "result": result})
ctx.messages.add_tool_result(tc, result)
def _resolve_tools(self, query: str, session_id: str | None) -> list[dict]:
"""λ§€ iterationλ§λ€ μ΅μ λꡬ λͺ©λ‘ μ‘°ν."""
if self.graph_search and self.graph_search.should_use_search:
result = self.graph_search.retrieve_as_openai_tools(query)
if result:
return result
return self.tool_registry.to_openai_tools(session_id=session_id)μ¬μ©μ: "μ¬λ λꡬ λ§λ€μ΄μ #generalμ μΈμ¬ν΄"
Iteration 1:
_resolve_tools() β [create_tool, execute_code, ...]
LLM β create_tool(description="μ¬λ μ±λμ λ©μμ§λ₯Ό 보λ΄λ λꡬ")
ToolGenerator β LLM μ½λμμ± β Sandbox κ²μ¦ β registry.register("send_slack", session_id="s1")
Iteration 2:
_resolve_tools(session_id="s1") β [..., send_slack] β μ¦μ ν¬ν¨λ¨
LLM β send_slack(channel="#general", text="μλ
νμΈμ!")
μ€ν β μ±κ³΅
Iteration 3:
LLM β "λ©μμ§λ₯Ό 보λμ΅λλ€." (done)
DockerSandbox
ββ ToolGenerator._test_syntax()μμ μ¬μ©
ββ ToolGenerator._test_functional()μμ μ¬μ©
ββ ToolTester.smoke_test()μμ μ¬μ©
β Agentκ° μ§μ νΈμΆ λΆκ°
Sandboxλ₯Ό @tool ν¨μλ‘ κ°μΈμ Registryμ λ±λ‘:
DockerSandbox
ββ μΈνλΌ: ToolGenerator, ToolTesterκ° λ΄λΆμ μΌλ‘ μ¬μ© (v1κ³Ό λμΌ)
ββ λꡬ: execute_code, execute_code_with_testλ₯Ό ToolRegistryμ λ±λ‘
β Agentκ° μμ λ‘κ² μ½λ μ€ν/μ€ν κ°λ₯
# mantis/sandbox/tools.py
def make_sandbox_tools(sandbox: DockerSandbox) -> list[ToolSpec]:
"""DockerSandboxλ₯Ό Agentκ° μΈ μ μλ λꡬ 2κ°λ‘ λ³ν."""
@tool(
name="execute_code",
description="Python μ½λλ₯Ό 격리λ Docker 컨ν
μ΄λμμ μ€ννλ€. "
"λ°μ΄ν° λΆμ, κ³μ°, νμΌ μ²λ¦¬, API ν
μ€νΈ λ±μ μ¬μ©.",
parameters={
"code": {"type": "string", "description": "μ€νν Python μ½λ"},
"pip_packages": {
"type": "array", "items": {"type": "string"},
"description": "μ€μΉν pip ν¨ν€μ§ (μ ν)",
},
"timeout": {
"type": "integer",
"description": "νμμμ μ΄ (κΈ°λ³Έ 30, μ΅λ 120)",
},
},
)
async def execute_code(code: str, timeout: int = 30, pip_packages: list = None) -> dict:
result = await sandbox.execute(code, pip_packages=pip_packages, timeout=min(timeout, 120))
return {"stdout": result.stdout, "stderr": result.stderr, "exit_code": result.exit_code}
@tool(
name="execute_code_with_test",
description="Python μ½λμ ν
μ€νΈ μ½λλ₯Ό ν¨κ» μ€ννμ¬ κ²μ¦νλ€.",
parameters={
"code": {"type": "string", "description": "κ²μ¦ν μ½λ"},
"test_code": {"type": "string", "description": "ν
μ€νΈ μ½λ"},
},
)
async def execute_code_with_test(code: str, test_code: str) -> dict:
combined = code + "\n\n# --- Tests ---\n\n" + test_code
result = await sandbox.execute(combined, timeout=30)
return {
"stdout": result.stdout, "stderr": result.stderr,
"exit_code": result.exit_code,
"tests_passed": "ALL_TESTS_PASSED" in result.stdout,
}
return [execute_code._tool_spec, execute_code_with_test._tool_spec]sandbox = DockerSandbox()
# Sandbox λꡬλ₯Ό μΌλ° λꡬμ λμΌνκ² λ±λ‘
for spec in make_sandbox_tools(sandbox):
registry.register(spec, source="sandbox")
# μ΄μ Agentλ execute_code, execute_code_with_testλ₯Ό μμ λ‘κ² νΈμΆ1. LLM μ½λ μμ± (temperature=0.3, ꡬ쑰ν ν둬ννΈ)
β @tool λ°μ½λ μ΄ν° ν¬ν¨ Python μ½λ + ν
μ€νΈ μ½λ
2. λꡬ μ΄λ¦ μΆμΆ
β regex: @tool(name="xxx")
3. Sandbox λ¬Έλ² κ²μ¦ (timeout=15s)
β _MOCK_PREAMBLE + code + print("SYNTAX_OK")
β DockerSandboxμμ μ€ν
4. Sandbox κΈ°λ₯ ν
μ€νΈ (timeout=30s)
β _MOCK_PREAMBLE + code + test_code
β "ALL_TESTS_PASSED" in stdout
5. νμΌ μ μ₯
β tools/{tool_name}.py
6. Registry λ±λ‘
β registry.load_from_file(path)
β λ€μ iterationμμ μ¦μ μ¬μ© κ°λ₯
μ
λ ₯: description (λ¬Έμμ΄) + session_id
μΆλ ₯: ToolSpec (μ΄λ―Έ Registryμ λ±λ‘λ μν)
νλ μΌ:
1. LLM μ½λ μμ±
2. λꡬ μ΄λ¦ μΆμΆ
3. Sandbox λ¬Έλ² κ²μ¦
4. Sandbox κΈ°λ₯ ν
μ€νΈ
5. νμΌ μ μ₯ (μ ν β λΌμ΄λΈλ¬λ¦¬μ΄λ―λ‘)
6. Registry λ±λ‘ (session_id μ§μ μΆκ°)
class ToolGenerator:
"""λꡬ μμ± νμ΄νλΌμΈ.
ν΅μ¬ λ³κ²½ (vs v1):
- session_id μ§μ β μΈμ
λ³ λꡬ 격리
- registry.register(source="generated") β μ¦μ μ¬μ© κ°λ₯
- make_create_tool()λ‘ Agent νΈμΆ κ°λ₯ν λκ΅¬λ‘ λ³ν
"""
def __init__(
self,
llm: ModelClient,
registry: ToolRegistry,
sandbox: DockerSandbox,
tools_dir: Path | None = None, # Noneμ΄λ©΄ νμΌ μ μ₯ μ ν¨
):
self.llm = llm
self.registry = registry
self.sandbox = sandbox
self.tools_dir = tools_dir
async def create(self, description: str, session_id: str | None = None) -> ToolSpec:
"""λꡬ μμ± μ 체 νμ΄νλΌμΈ. λ°ν μμ μ μ΄λ―Έ Registryμ λ±λ‘λ¨."""
# 1. LLM μ½λ μμ±
code, test_code = await self._generate_code(description)
tool_name = self._extract_tool_name(code)
# 2. Sandbox λ¬Έλ² κ²μ¦
syntax = await self.sandbox.execute(
code=_MOCK_PREAMBLE + code + '\nprint("SYNTAX_OK")',
timeout=15,
)
if not syntax.success or "SYNTAX_OK" not in syntax.stdout:
raise ToolGenerationError("syntax", syntax.stderr)
# 3. Sandbox κΈ°λ₯ ν
μ€νΈ
test = await self.sandbox.execute(
code=_MOCK_PREAMBLE + code + "\n\n" + test_code,
timeout=30,
)
if "ALL_TESTS_PASSED" not in test.stdout:
raise ToolGenerationError("test", test.stderr)
# 4. νμΌ μ μ₯ (μ ν)
if self.tools_dir:
(self.tools_dir / f"{tool_name}.py").write_text(code)
# 5. Registryμ μ¦μ λ±λ‘
spec = self._code_to_spec(code, tool_name)
self.registry.register(spec, source="generated", session_id=session_id)
return spec
def make_create_tool(generator: ToolGenerator) -> ToolSpec:
"""ToolGeneratorλ₯Ό Agentκ° νΈμΆν μ μλ λκ΅¬λ‘ λ³ν."""
@tool(
name="create_tool",
description="μλ‘μ΄ λꡬλ₯Ό λ§λ λ€. μ€λͺ
μ λ°μ Python μ½λλ₯Ό μμ±νκ³ , "
"Dockerμμ ν
μ€νΈν ν λ±λ‘νλ€. λ±λ‘ μ¦μ μ¬μ© κ°λ₯.",
parameters={
"description": {"type": "string", "description": "λ§λ€ λꡬμ κΈ°λ₯ μ€λͺ
"},
},
)
async def create_tool(description: str) -> dict:
spec = await generator.create(description)
return {"tool_name": spec.name, "status": "registered"}
return create_tool._tool_specasync_workflow_executor.py ν΅μ¬ λ‘μ§:
execution_order = topological_sort(nodes, edges) # Kahn μκ³ λ¦¬μ¦
node_outputs: dict[str, dict[str, Any]] = {} # λ
Έλλ³ μΆλ ₯ μ μ₯
excluded_nodes: set[str] = set() # Routerμ μν΄ λΉνμ±
for node_id in execution_order:
if node_id in excluded_nodes: continue
# μ£μ§μμ μ
λ ₯ μμ§
kwargs = {}
for edge in edges_to(node_id):
kwargs[edge.target_port] = node_outputs[edge.source_node][edge.source_port]
# λ
Έλ μ€ν
result = node.execute(**kwargs)
node_outputs[node_id] = {port_id: result}
# Routerλ©΄ λΆκΈ° μ²λ¦¬
if is_router(node_id):
inactive_branches = get_inactive_branches(result)
excluded_nodes.update(inactive_branches)
μ
λ ₯: λ
Έλ + μ£μ§ (μΊλ²μ€ JSON, μ½λ, λλ LLM μμ±)
μΆλ ₯: AsyncGenerator[WorkflowEvent]
νλ μΌ:
1. μμ μ λ ¬ (Kahn μκ³ λ¦¬μ¦)
2. λ
Έλλ³ μμ°¨ μ€ν
3. μ£μ§ ν΅ν΄ μΆλ ₯βμ
λ ₯ μ λ¬ (node_outputs)
4. Router λ
Έλμμ 쑰건 λΆκΈ° (excluded_nodes)
5. κ° λ¨κ³λ₯Ό WorkflowEventλ‘ yield
3κ°μ§ μμ± λͺ¨λ:
A. from_canvas(json) β xgen-workflow νΈν
B. μ½λλ‘ μ§μ 쑰립 β μΊλ²μ€ μμ΄ μ¬μ©
C. from_llm(description) β AIκ° μν¬νλ‘μ° μλ μμ±
class WorkflowEngine:
"""xgen-workflowμ async_workflow_executorλ₯Ό λΌμ΄λΈλ¬λ¦¬λ‘ μ¬κ΅¬μ±."""
def __init__(self, registry: ToolRegistry):
self.registry = registry
self.nodes: dict[str, WorkflowNode] = {}
self.edges: list[Edge] = []
# βββ λͺ¨λ A: μΊλ²μ€ νΈν βββ
@classmethod
def from_canvas(cls, workflow_data: dict, registry: ToolRegistry) -> "WorkflowEngine":
"""xgen-workflow μΊλ²μ€ JSON β WorkflowEngine λ³ν.
λ
Έλ νμ
λ§€ν:
agents/xgen β AgentNode (mantis Agent μ€ν)
router β RouterNode (쑰건 λΆκΈ°)
api_tool β ToolNode (λꡬ μ§μ νΈμΆ)
qdrant β RAGNode (λ²‘ν° κ²μ)
schema_input β InputNode
end β OutputNode
"""
engine = cls(registry)
for node in workflow_data["nodes"]:
engine.add_node(cls._map_node(node, registry))
for edge in workflow_data["edges"]:
engine.add_edge(Edge(
source_node=edge["source"]["nodeId"],
source_port=edge["source"]["portId"],
target_node=edge["target"]["nodeId"],
target_port=edge["target"]["portId"],
))
return engine
# βββ λͺ¨λ B: μ½λ 쑰립 βββ
def add_node(self, node: WorkflowNode): ...
def add_edge(self, edge: Edge): ...
# βββ λͺ¨λ C: LLM μλ μμ± βββ
@classmethod
async def from_llm(cls, description: str, llm: ModelClient, registry: ToolRegistry) -> "WorkflowEngine":
"""LLMμ΄ μν¬νλ‘μ°λ₯Ό μλ μμ±.
μ¬μ© κ°λ₯ν λꡬ λͺ©λ‘μ LLMμ μλ €μ£Όκ³ ,
λ
Έλ/μ£μ§ ꡬ쑰λ₯Ό JSONμΌλ‘ λ°μμ 쑰립.
"""
available_tools = registry.list_names()
response = await llm.generate(messages=[
{"role": "system", "content": f"Available tools: {available_tools}\n"
"Design a workflow as JSON with nodes and edges."},
{"role": "user", "content": description},
])
return cls.from_canvas(json.loads(response.text), registry)
# βββ μ€ν βββ
async def run(self, input_data: dict) -> AsyncIterator[WorkflowEvent]:
"""μν¬νλ‘μ° μ€ν.
xgen-workflowμ _execute_workflow_syncλ₯Ό λ체.
"""
execution_order = self._topological_sort()
node_outputs: dict[str, dict[str, Any]] = {}
excluded: set[str] = set()
for node_id in execution_order:
if node_id in excluded:
continue
node = self.nodes[node_id]
inputs = self._collect_inputs(node_id, node_outputs)
yield WorkflowEvent("node_start", {"node_id": node_id, "type": node.type})
result = await node.execute(inputs)
node_outputs[node_id] = result
yield WorkflowEvent("node_complete", {"node_id": node_id, "output": result})
if isinstance(node, RouterNode):
inactive = self._get_inactive_branches(node_id, result)
excluded.update(inactive)
yield WorkflowEvent("workflow_complete", {"outputs": node_outputs})class WorkflowNode(Protocol):
id: str
type: str
async def execute(self, inputs: dict) -> dict[str, Any]: ...
class AgentNode:
"""mantis Agentλ‘ μ€ν. xgen-workflowμ agent_core.py λ체.
LangChain λΈλλ°μ€ λμ ThinkβActβObserve μ§μ 루ν."""
type = "agent"
class RouterNode:
"""쑰건 λΆκΈ°. xgen-workflowμ process_router_node_output λ체."""
type = "router"
class ToolNode:
"""λ¨μΌ λꡬ μ§μ μ€ν."""
type = "tool"
class RAGNode:
"""λ²‘ν° κ²μ (Qdrant λ±)."""
type = "rag"
class InputNode:
"""μ
λ ₯ μ€ν€λ§ νμ±."""
type = "input"
class OutputNode:
"""κ²°κ³Ό μΆλ ₯."""
type = "output"engine = WorkflowEngine(registry=registry)
engine.add_node(AgentNode(id="analyze", model="gpt-4o-mini", prompt="λ°μ΄ν° λΆμ"))
engine.add_node(RouterNode(id="check", conditions={
"good": lambda s: s.get("confidence", 0) > 0.8,
"retry": lambda s: s.get("confidence", 0) <= 0.8,
}))
engine.add_node(AgentNode(id="report", model="gpt-4o-mini", prompt="리ν¬νΈ μμ±"))
engine.add_edge(Edge("analyze", "result", "check", "input"))
engine.add_edge(Edge("check", "good", "report", "text"))
engine.add_edge(Edge("check", "retry", "analyze", "text")) # 루νλ°±
async for event in engine.run({"text": "λ§€μΆ λ°μ΄ν° λΆμν΄μ€"}):
print(event)controller/workflow/utils/execution_core.py (695μ€)
β μ μ²λ¦¬ + μ΄λ²€νΈ νμ± + DB μ μ₯
β
βΌ
editor/async_workflow_executor.py (1089μ€)
β DAG μ컀 (μ€λ λν + queue.Queue β async λ³ν)
β node_outputsλ‘ λ
Έλ κ° λ°μ΄ν° μ λ¬
β
βΌ
editor/nodes/xgen/agent/agent_core.py (1193μ€)
β LangChain create_agent(model, tools) β λΈλλ°μ€ μ€ν
β [AGENT_EVENT] λ¬Έμμ΄ νκ·Έλ‘ μ΄λ²€νΈ μΆλ ₯
β
βΌ
editor/nodes/xgen/agent/agent_xgen.py (1525μ€)
μΊλ²μ€ Agent λ
Έλ β tools ν¬νΈμμ λꡬ μμ§
xgen-workflow (νμ¬) mantis (κ΅μ²΄)
ββββββββββββββββββββββ ββββββββββββββ
execution_core.py β Phase Pipeline
workflow_data λ‘λ PREPARE: μν¬νλ‘μ° νμ±
νμΌ μ ν, bypass μ μ²λ¦¬ PREPARE: μ μ²λ¦¬
[AGENT_EVENT] νκ·Έ νμ± STREAM: ꡬ쑰ν μ΄λ²€νΈ (νμ± λΆνμ)
ExecutionIO DB μ μ₯ PERSIST: DB μ μ₯
Redis μΈμ
μ
λ°μ΄νΈ PERSIST: μν μ
λ°μ΄νΈ
async_workflow_executor.py β WorkflowEngine
topological_sort() WorkflowEngine._topological_sort()
node_outputs dict WorkflowEngine.node_outputs
μ£μ§ κΈ°λ° μ
λ ₯ μμ§ WorkflowEngine._collect_inputs()
Router λΆκΈ° + excluded_nodes RouterNode + _get_inactive_branches()
μ€λ λν + Queue β async λ€μ΄ν°λΈ async/await (μ€λ λ λΆνμ)
Generator tee / BufferedGeneratorFactory AsyncGenerator μ§μ μ¬μ©
agent_core.py + agent_xgen.py β AgentNode + Agent
LangChain create_agent(tools) mantis Agent(tool_registry)
prepare_llm_components() ToolRegistry.to_openai_tools()
kwargs['tools'] (μ£μ§μμ κ³ μ ) λ§€ iteration Registry μ¬μ‘°ν
[AGENT_EVENT] λ¬Έμμ΄ μΆλ ₯ ExecutionEvent ꡬ쑰ν κ°μ²΄
LangGraph λΈλλ°μ€ ThinkβActβObserve μ§μ 루ν
# repos/xgen-workflow/editor/nodes/xgen/agent/agent_core.py
# LangChain μ½λλ₯Ό mantisλ‘ κ΅μ²΄
# Before (LangChain)
from langchain.agents import create_agent
agent = create_agent(model=llm, tools=tools_list)
result = agent.invoke(user_input)
# After (mantis)
from mantis import Agent, ToolRegistry
from mantis.llm.openai_provider import ModelClient
registry = ToolRegistry()
for tool_obj in tools_list: # μΊλ²μ€ μ£μ§μμ μ¨ λꡬ
registry.register(tool_obj)
agent = Agent(
name=node_name,
model_client=ModelClient(model=model_name, api_key=api_key),
tool_registry=registry,
system_prompt=system_prompt,
)
# μ€νΈλ¦¬λ° β [AGENT_EVENT] νκ·Έ νμ± λμ ꡬ쑰ν μ΄λ²€νΈ
async for event in agent.run_stream(user_input):
yield event # {"type": "tool_call", "data": {...}}# Before (695μ€ β μ μ²λ¦¬+μ€ν+νμ±+μ μ₯ λ€μμ)
async def run_workflow_execution(...):
workflow_data = load_from_db(...)
apply_file_selection(...)
apply_bypass(...)
executor = create_executor(workflow_data)
async for chunk in executor.execute_streaming():
# [AGENT_EVENT] μ κ·μ νμ±
# [AGENT_STATUS] νμ±
# IO μ΄κ·
# μλ¬ μΉν
yield sse_format(chunk)
save_execution_io(...)
update_redis(...)
# After (mantis Phase νμ΄νλΌμΈ)
from mantis.workflow import WorkflowEngine
from mantis.pipeline import build_pipeline
async def run_workflow_execution(workflow_data: dict, user_input: str):
engine = WorkflowEngine.from_canvas(workflow_data, registry)
async for event in engine.run({"text": user_input}):
yield event.to_sse()
# PERSIST Phaseκ° DB μ μ₯ + μν μ
λ°μ΄νΈ μλ μ²λ¦¬Before (μΊλ²μ€ μ£μ§ κ³ μ ):
[API Tool] ββμ£μ§βββ Agent.tools ν¬νΈ β kwargs['tools'] = [tool1, tool2]
β μ€ν μ€ λꡬ μΆκ° λΆκ°
β λꡬ 30κ° μ λΆ LLMμ μ λ¬
After (mantis ToolRegistry):
[API Tool] ββμ£μ§βββ AgentNode β registry.register(tool)
β registry.to_openai_tools()λ‘ λ§€ iteration μ΅μ μ‘°ν
β GraphToolManagerλ‘ κ΄λ ¨ λκ΅¬λ§ κ²μ
β create_toolλ‘ μ€ν μ€ λꡬ μΆκ° κ°λ₯
Before (μ€λ λ + ν + λ¬Έμμ΄ νκ·Έ):
_execute_workflow_sync() β λκΈ°, μ€λ λνμμ μ€ν
β self._streaming_queue.put(('data', chunk))
β self._streaming_queue.put(('agent_event', {...}))
execute_workflow_async_streaming() β λΉλκΈ°, ν ν΄λ§
β queue.get(timeout=0.02)
execution_core.py
β [AGENT_EVENT]{json}[/AGENT_EVENT] μ κ·μ νμ±
After (λ€μ΄ν°λΈ async + ꡬ쑰ν μ΄λ²€νΈ):
WorkflowEngine.run() β λ€μ΄ν°λΈ async
β yield WorkflowEvent("node_start", {...})
β AgentNode β yield ExecutionEvent("tool_call", {...})
β yield WorkflowEvent("node_complete", {...})
β λ¬Έμμ΄ νμ± λΆνμ, μ€λ λ λΆνμ
v2μ ν΅μ¬: νλμ ToolRegistryλ₯Ό ν΅ν΄ λͺ¨λ κ³³μμ λκΈ°ν.
λꡬ μμ€ ToolRegistry μλΉμ
ββββββββββ ββββββββββββββ ββββββ
@tool λ°μ½λ μ΄ν° ββregisterβββ βββ Executor (λ§€ iter μ‘°ν)
load_from_directory ββregisterβββ βββ GraphToolManager (κ²μ)
make_sandbox_tools() ββregisterβββ to_openai_tools βββ LLM (function calling)
make_create_tool() ββregisterβββ (session_id) βββ WorkflowEngine (λ
Έλ λ°μΈλ©)
MCP Bridge ββregisterβββ
OpenAPI Loader ββregisterβββ
λꡬ κ²μ¦:
@tool λ±λ‘ μ β ToolTester.smoke_test()
create_tool μμ± μ β Sandbox λ¬Έλ²+κΈ°λ₯ ν
μ€νΈ
MCP/OpenAPI λ±λ‘ μ β ToolTester.validate_schema()
μ€ν¨ β λ±λ‘ κ±°λΆ β κΉ¨μ§ λκ΅¬κ° LLMμ μ λ¬ μ λ¨
registry = ToolRegistry()
agent = Agent(name="bot", ..., tool_registry=registry)class ToolRegistryBackend(Protocol):
"""μ μ₯μ λ°±μλ β κ΅μ²΄ κ°λ₯."""
async def list_tools(self, session_id: str | None) -> list[ToolSpec]: ...
async def register(self, spec: ToolSpec, source: str, session_id: str | None): ...
class InMemoryBackend: # λ¨μΌ νλ‘μΈμ€ (κΈ°λ³Έκ°)
class RedisBackend: # λ©ν° μ± κ³΅μ (Redis pub/subλ‘ λ³κ²½ μλ¦Ό)ββββββββββββββββββββββββββββββββββββββββ
β Shared ToolRegistry (Redis) β
β builtin + MCP + generated β
ββββββββ¬βββββββββββ¬βββββββββββ¬ββββββββββ
β β β
xgen-app-1 xgen-app-2 xgen-app-3
(κ³ κ° μλ΄) (λ°μ΄ν° λΆμ) (μ΄μ μλν)
νμ¬:
ToolGenerator β λꡬ μμ± β λ‘컬 Registryμλ§ λ±λ‘
β μΈμ
λλλ©΄ μ¬λΌμ§
β λ€λ₯Έ μ¬λ/μ±μ΄ κ°μ Έλ€ μΈ λ°©λ² μμ
β κ²μ¦ μμ΄ λ±λ‘λ¨ (Sandbox ν
μ€νΈλ§)
νμν κ²:
μμ±λ λꡬλ₯Ό μ΄λκ°μ μ μ₯ β κ²μ¦ β κ³΅κ° β λ€λ₯Έ κ³³μμ κ°μ Έλ€ μ
npm/PyPI κ°μ λꡬ μ μ© λ μ§μ€νΈλ¦¬
λꡬ μλͺ
μ£ΌκΈ°:
μμ± β κ²μ¦ β κ²μ β κ²μ β μ€μΉ β μ¬μ©
β β β β β β
β β β β β ββ ToolRegistry.execute()
β β β β ββ mantis install <tool-name>
β β β ββ mantis search "μ¬λ λ©μμ§"
β β ββ mantis publish <tool-name>
β ββ μλ κ²μ¦ νμ΄νλΌμΈ (Sandbox ν
μ€νΈ + μ€ν€λ§ κ²μ¦ + 보μ μ€μΊ)
ββ ToolGenerator.create() λλ μλ μμ±
λꡬ = Python νμΌ + λ©νλ°μ΄ν°
β Git μ μ₯μμ μμ°μ€λ½κ² λ§μ (μ½λλκΉ)
tool-store/
βββ tools/
β βββ send_slack_message/
β β βββ tool.py β @tool λ°μ½λ μ΄ν° μ½λ
β β βββ test_tool.py β ν
μ€νΈ μ½λ
β β βββ manifest.json β λ©νλ°μ΄ν° (μ΄λ¦, μ€λͺ
, λ²μ , μμ‘΄μ±, μμ±μ)
β β βββ README.md
β βββ fetch_weather/
β β βββ tool.py
β β βββ test_tool.py
β β βββ manifest.json
β βββ ...
βββ categories.json β μΉ΄ν
κ³ λ¦¬ λΆλ₯
βββ verified.json β κ²μ¦ ν΅κ³Όν λꡬ λͺ©λ‘
{
"name": "send_slack_message",
"version": "1.0.0",
"description": "μ¬λ μ±λμ λ©μμ§λ₯Ό 보λ΄λ λꡬ",
"author": "jinsoo",
"category": "communication",
"tags": ["slack", "message", "notification"],
"parameters": {
"channel": {"type": "string", "description": "μ¬λ μ±λ"},
"text": {"type": "string", "description": "λ©μμ§ λ΄μ©"}
},
"dependencies": ["httpx"],
"verified": true,
"verification": {
"sandbox_test": "passed",
"schema_validation": "passed",
"security_scan": "passed",
"verified_at": "2026-03-19T10:00:00Z"
}
}λꡬ κ²μ μμ² (mantis publish)
β
βΌ
Step 1: μ€ν€λ§ κ²μ¦
β manifest.json νμ νλ νμΈ
β parameters νμ
/description κ²μ¦
β @tool λ°μ½λ μ΄ν° μ‘΄μ¬ νμΈ
β
βΌ
Step 2: Sandbox ν
μ€νΈ
β Docker 컨ν
μ΄λμμ test_tool.py μ€ν
β "ALL_TESTS_PASSED" νμΈ
β νμμμ, λ©λͺ¨λ¦¬ μ ν νμΈ
β
βΌ
Step 3: 보μ μ€μΊ
β μνν import κ°μ§ (os.system, subprocess, eval)
β λ€νΈμν¬ μ κ·Ό ν¨ν΄ νμΈ
β νμΌ μμ€ν
μ κ·Ό λ²μ νμΈ
β
βΌ
Step 4: λ±λ‘
β Git μ μ₯μμ μ»€λ° (λλ API μλ²μ μ
λ‘λ)
β verified.json μ
λ°μ΄νΈ
β κ²μ μΈλ±μ€ κ°±μ
class ToolStore:
"""λꡬ μ μ₯μ β λꡬλ₯Ό κ²μνκ³ κ°μ Έμ€λ λ μ§μ€νΈλ¦¬.
μ μ₯μ λ°±μλ:
- GitBackend: Git μ μ₯μ (GitHub, GitLab λ±)
- APIBackend: REST API μλ² (μ체 νΈμ€ν
)
"""
def __init__(self, backend: StoreBackend):
self.backend = backend
async def publish(self, tool_dir: Path, registry: ToolRegistry) -> PublishResult:
"""λꡬλ₯Ό μ μ₯μμ κ²μ.
1. manifest.json λ‘λ + κ²μ¦
2. Sandbox ν
μ€νΈ μ€ν
3. 보μ μ€μΊ
4. μ μ₯μμ μ
λ‘λ
"""
manifest = load_manifest(tool_dir / "manifest.json")
code = (tool_dir / "tool.py").read_text()
test_code = (tool_dir / "test_tool.py").read_text()
# κ²μ¦
verification = await self._verify(code, test_code, manifest)
if not verification.passed:
return PublishResult(success=False, errors=verification.errors)
# κ²μ
await self.backend.upload(manifest["name"], tool_dir)
return PublishResult(success=True, name=manifest["name"], version=manifest["version"])
async def install(self, tool_name: str, registry: ToolRegistry) -> ToolSpec:
"""μ μ₯μμμ λꡬλ₯Ό κ°μ Έμ ToolRegistryμ λ±λ‘."""
tool_dir = await self.backend.download(tool_name)
spec = registry.load_from_file(tool_dir / "tool.py")
return spec
async def search(self, query: str, category: str | None = None) -> list[ToolManifest]:
"""λꡬ κ²μ."""
return await self.backend.search(query, category=category)
async def list_categories(self) -> list[str]:
"""μΉ΄ν
κ³ λ¦¬ λͺ©λ‘."""
return await self.backend.list_categories()
class GitStoreBackend:
"""Git μ μ₯μ κΈ°λ° λ°±μλ.
GitHub/GitLab λ ν¬λ₯Ό λꡬ μ μ₯μλ‘ μ¬μ©.
κ²μ = PR μμ±, κ²μ¦ = CI νμ΄νλΌμΈ, μ€μΉ = git clone.
"""
def __init__(self, repo_url: str, branch: str = "main"):
self.repo_url = repo_url
self.branch = branch
async def upload(self, tool_name: str, tool_dir: Path):
"""PR μμ±μΌλ‘ λꡬ κ²μ."""
# git clone β λꡬ λ³΅μ¬ β commit β push β PR μμ±
...
async def download(self, tool_name: str) -> Path:
"""νΉμ λꡬ λλ ν λ¦¬λ§ λ€μ΄λ‘λ."""
# sparse checkoutμΌλ‘ tools/{tool_name}/ λ§ κ°μ Έμ΄
...
async def search(self, query: str, **kwargs) -> list[ToolManifest]:
"""manifest.json κΈ°λ° κ²μ."""
...
class APIStoreBackend:
"""REST API κΈ°λ° λ°±μλ.
μ체 νΈμ€ν
λꡬ λ μ§μ€νΈλ¦¬ μλ².
"""
def __init__(self, base_url: str, api_key: str | None = None):
self.base_url = base_url
self.api_key = api_key
async def upload(self, tool_name: str, tool_dir: Path): ...
async def download(self, tool_name: str) -> Path: ...
async def search(self, query: str, **kwargs) -> list[ToolManifest]: ...# λꡬ κ²μ
mantis store search "μ¬λ λ©μμ§"
mantis store search --category communication
# λꡬ μ€μΉ
mantis store install send_slack_message
# λꡬ κ²μ
mantis store publish ./my_tools/send_slack_message/
# λꡬ λͺ©λ‘
mantis store list
mantis store list --category database
# μ μ₯μ μ€μ
mantis store config --backend git --repo https://github.com/myorg/tool-store.git
mantis store config --backend api --url https://tools.mycompany.com# λꡬ μμ± ν μλμΌλ‘ Storeμ κ²μνλ μ΅μ
generator = ToolGenerator(
llm=llm,
registry=registry,
sandbox=sandbox,
store=ToolStore(GitStoreBackend("https://github.com/myorg/tool-store.git")),
auto_publish=True, # μμ± + κ²μ¦ ν΅κ³Ό μ μλ κ²μ
)
# λλ μλ κ²μ
spec = await generator.create("μ¬λ λ©μμ§ λ³΄λ΄λ λꡬ")
await store.publish(spec.source_dir, registry)μΉ΄ν
κ³ λ¦¬ μμ:
communication/ β μ¬λ, μ΄λ©μΌ, Teams, Discord
database/ β SQL 쿼리, MongoDB, Redis
file/ β νμΌ μ½κΈ°/μ°κΈ°, CSV νμ±, PDF λ³ν
api/ β REST API νΈμΆ, GraphQL, webhook
analysis/ β λ°μ΄ν° λΆμ, ν΅κ³, μκ°ν
search/ β μΉ κ²μ, λ²‘ν° κ²μ, λ¬Έμ κ²μ
automation/ β μ€μΌμ€λ§, ν¬λ‘€λ§, μλ¦Ό
cloud/ β AWS, GCP, Azure μ°λ
dev_tools/ β Git, CI/CD, μ½λ 리뷰
custom/ β μ¬μ©μ μ μ
ToolGenerator Tool Store λ€λ₯Έ μ±/μ¬μ©μ
β β β
β create("μ¬λ λꡬ") β β
β β LLM μ½λμμ± β β
β β Sandbox κ²μ¦ β β
β β ToolRegistry λ±λ‘ β β
β β β
β publish(tool_dir) ββββββββββ β κ²μ¦ νμ΄νλΌμΈ β
β β β μ€ν€λ§ κ²μ¦ β
β β β Sandbox ν
μ€νΈ β
β β β 보μ μ€μΊ β
β β β Git μ μ₯μμ μ»€λ° β
β β β
β β βββββ search("μ¬λ") β
β β ββββββ [send_slack_message] β
β β β
β β βββββ install("send_slack") β
β β ββββββ tool.py λ€μ΄λ‘λ β
β β ToolRegistry λ±λ‘ β
v1 λλΉ μΆκ°/λ³κ²½ λΆλΆ:
mantis/
βββ __init__.py
βββ __main__.py
β
βββ tools/
β βββ decorator.py β v1 κ·Έλλ‘
β βββ registry.py β v2: session_id, source μΆκ°
β
βββ engine/
β βββ runner.py β v2: λ§€ iteration Registry μ¬μ‘°ν
β
βββ sandbox/
β βββ sandbox.py β v1 κ·Έλλ‘
β βββ runner.py β v1 κ·Έλλ‘
β βββ tools.py β β
v2 μ κ·: make_sandbox_tools()
β
βββ generate/
β βββ tool_generator.py β v2: session_id μ§μ + make_create_tool()
β
βββ workflow/ β β
v2 μ κ· ν¨ν€μ§
β βββ __init__.py
β βββ engine.py β WorkflowEngine (from_canvas, from_code, from_llm)
β βββ nodes.py β AgentNode, RouterNode, ToolNode, RAGNode
β βββ models.py β WorkflowNode, Edge, WorkflowEvent
β
βββ store/ β β
v2 μ κ· ν¨ν€μ§
β βββ __init__.py
β βββ store.py β ToolStore (publish, install, search)
β βββ backends.py β GitStoreBackend, APIStoreBackend
β βββ manifest.py β ToolManifest νμ±/κ²μ¦
β βββ verify.py β κ²μ¦ νμ΄νλΌμΈ (μ€ν€λ§+Sandbox+보μ)
β
βββ pipeline/ β v1 κ·Έλλ‘ (νμ νΈν)
βββ llm/ β v1 κ·Έλλ‘
βββ context/ β v1 κ·Έλλ‘
βββ safety/ β v1 κ·Έλλ‘
βββ search/ β v1 κ·Έλλ‘
βββ state/ β v1 κ·Έλλ‘
βββ trace/ β v1 κ·Έλλ‘
βββ testing/ β v1 κ·Έλλ‘
βββ adapters/ β v1 κ·Έλλ‘
from mantis import Agent, tool, ToolRegistry
from mantis.llm.openai_provider import ModelClient
@tool(name="greet", description="μΈμ¬νλ€")
async def greet(name: str) -> dict:
return {"message": f"μλ
{name}"}
registry = ToolRegistry()
registry.register(greet._tool_spec)
agent = Agent(
name="bot",
model_client=ModelClient(model="gpt-4o-mini", api_key="sk-..."),
tool_registry=registry,
)
result = await agent.run("μΈμ¬ν΄μ€")from mantis import Agent, tool, ToolRegistry
from mantis.llm.openai_provider import ModelClient
from mantis.sandbox.sandbox import DockerSandbox
from mantis.sandbox.tools import make_sandbox_tools
from mantis.generate.tool_generator import ToolGenerator, make_create_tool
from mantis.search.graph_search import GraphToolManager
# 1. 곡μ Registry
registry = ToolRegistry()
# 2. μ¬μ©μ λꡬ
@tool(name="lookup_order", description="μ£Όλ¬Έ μ‘°ν")
async def lookup_order(order_id: str) -> dict:
return {"order_id": order_id, "status": "shipped"}
registry.register(lookup_order._tool_spec, source="builtin")
# 3. Sandbox λꡬ λ±λ‘ β Agentκ° μ½λ μ€ν κ°λ₯
sandbox = DockerSandbox()
for spec in make_sandbox_tools(sandbox):
registry.register(spec, source="sandbox")
# 4. ToolGenerator + create_tool β Agentκ° λꡬ μμ± κ°λ₯
llm = ModelClient(model="gpt-4o-mini", api_key="sk-...")
generator = ToolGenerator(llm=llm, registry=registry, sandbox=sandbox)
registry.register(make_create_tool(generator), source="builtin")
# 5. Graph κ²μ
graph = GraphToolManager()
graph.ingest_from_registry(registry)
# 6. Agent μμ± β λꡬ νν©:
# - lookup_order (builtin)
# - execute_code (sandbox)
# - execute_code_with_test (sandbox)
# - create_tool (builtin)
agent = Agent(
name="full-agent",
model_client=llm,
tool_registry=registry,
graph_tool_manager=graph,
approval_patterns=["DELETE *"],
)
# Agentκ° λꡬ λ§λ€κ³ , μ½λ λλ¦¬κ³ , μ¦μ μ¬μ© κ°λ₯
async for event in agent.run_stream("μ¬λ λꡬ λ§λ€μ΄μ λ©μμ§ λ³΄λ΄"):
print(event)from mantis.workflow import WorkflowEngine
# μΊλ²μ€ JSONμμ (xgen-workflow νΈν)
engine = WorkflowEngine.from_canvas(workflow_data, registry)
# λλ μ½λλ‘ μ§μ
engine = WorkflowEngine(registry)
engine.add_node(AgentNode(id="agent1", model="gpt-4o-mini"))
engine.add_edge(Edge("input", "result", "agent1", "text"))
async for event in engine.run({"text": "λΆμν΄μ€"}):
print(event)| v1 | v2 | |
|---|---|---|
| λꡬ λͺ©λ‘ | Phase 2 μ€λ μ· (κ³ μ ) | λ§€ iteration μ΅μ μ‘°ν (λΌμ΄λΈ) |
| λꡬ μμ± β μ¬μ© | λ€μ μ€νμμμΌ κ°λ₯ | κ°μ λνμμ μ¦μ μ¬μ© |
| μλλ°μ€ | ToolGenerator λ΄λΆ μΈνλΌ | Agentκ° νΈμΆνλ λꡬ |
| μν¬νλ‘μ° | μΊλ²μ€ JSONλ§ μ§μ | μΊλ²μ€ + μ½λ + LLM μμ± |
| μΈμ 격리 | μμ | session_idλ³ λꡬ κ΄λ¦¬ |
| λꡬ μμ€ μΆμ | μμ | builtin/generated/sandbox/mcp/openapi |
| λ©ν° μ± | λ¨μΌ νλ‘μΈμ€ | Redis λ°±μλλ‘ κ³΅μ κ°λ₯ |
| νμ νΈν | β | v1 μ½λ κ·Έλλ‘ λμ |
- ToolRegistryμ session_id, source νλΌλ―Έν° μΆκ°
- to_openai_tools(session_id) κΈλ‘λ²+μΈμ ν©μ°
- ExecutePhase λ§€ iterationλ§λ€ Registry μ¬μ‘°ν
- cleanup_session() μΆκ°
- ν¨κ³Ό: create_tool β μ¦μ μ¬μ© ν΄κ²°
- mantis/sandbox/tools.py μ κ· (make_sandbox_tools)
- execute_code, execute_code_with_test @tool ν¨μ
- ν¨κ³Ό: Agentκ° μ½λ μ€ν/μ€ν κ°λ₯
- session_id μ λ¬ μ§μ
- make_create_tool() ν¬νΌ
- _MOCK_PREAMBLE mantis ν¨ν€μ§μ© κ°±μ
- ν¨κ³Ό: λꡬ μμ±βκ²μ¦βλ±λ‘βμ¦μ μ¬μ© μ 체 νμ΄νλΌμΈ
- mantis/workflow/ ν¨ν€μ§ μ κ·
- WorkflowEngine, WorkflowNode, Edge ꡬν
- from_canvas() μΊλ²μ€ νΈν
- AgentNode, RouterNode, ToolNode ꡬν
- from_llm() LLM μν¬νλ‘μ° μλ μμ±
- ν¨κ³Ό: xgen-workflow μ€νκΈ° λ체
- ToolRegistryBackend Protocol
- RedisBackend ꡬν
- ν¨κ³Ό: xgen μ± κ° λꡬ 곡μ
- ToolStore, GitStoreBackend, APIStoreBackend ꡬν
- κ²μ¦ νμ΄νλΌμΈ (μ€ν€λ§ + Sandbox + 보μ μ€μΊ)
- manifest.json ν¬λ§· μ μ
- CLI:
mantis store search/install/publish - ToolGenerator β Store μλ κ²μ μ΅μ
- ν¨κ³Ό: λꡬλ₯Ό μ μ₯μμ κ²μνκ³ λ€λ₯Έ μ±/νμ΄ κ°μ Έλ€ μ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Mantis v2 (λΌμ΄λΈλ¬λ¦¬) β
β β
β pip install mantis β
β from mantis import Agent, tool β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β ToolRegistry (νλμ μΈμ€ν΄μ€, λΌμ΄λΈ) β β
β β β β
β β builtin sandbox generated MCP OpenAPI β β
β β @tool() execute_code create_tool mcp: api_to_tool β β
β β execute_test β μ¦μ λ±λ‘ slack β β
β β β β
β β to_openai_tools(session_id) β λ§€ iteration μ΅μ λꡬ λ°ν β β
β βββββββββββββββββ¬βββββββββββββββββββ¬ββββββββββββββββ¬ββββββββββββββββ β
β β β β β
β ββββββββββββββββββΌβββ βββββββββββββΌββββ βββββββββΌβββββββββββ β
β β Executor β β ToolGenerator β β WorkflowEngine β β
β β β β β β β β
β β ThinkβAct β β LLM μ½λμμ± β β from_canvas() β β
β β βObserve β β Sandbox κ²μ¦ β β μ½λ 쑰립 β β
β β β β Registry λ±λ‘ β β from_llm() β β
β β λ§€ iteration β β β μ¦μ μ¬μ© β β 쑰건 λΆκΈ° β β
β β λꡬ μ¬μ‘°ν β β β β 루νλ°± β β
β βββββββββββββββββββββ βββββββββββββββββ ββββββββββββββββββββ β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β DockerSandbox (μΈνλΌ + λꡬ) β β
β β μΈνλΌ: ToolGenerator κ²μ¦, ToolTester smoke_test β β
β β λꡬ: execute_code, execute_code_with_test β Agentκ° νΈμΆ β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Phase Pipeline (v1 νΈν) β β
β β PREPARE β RESOLVE β EXECUTE β STREAM β PERSIST β β
β β β ToolRegistry.to_openai_tools() λ§€ iter β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Adapters (μ΄μ λ μ΄μ΄) β β
β β WorkflowAdapter β μΊλ²μ€ JSONμ Phase νμ΄νλΌμΈμΌλ‘ λ³ν β β
β β SSEAdapter β StreamEvent β SSE ν¬λ§· β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββΌββββββββββββββββββ
βΌ βΌ βΌ
xgen-workflow xgen3.0 μ νλ‘μ νΈ
(μΊλ²μ€ μ μ§, (κΈ°μ‘΄μ²λΌ (pip install
μ€νκΈ°λ§ κ΅μ²΄) μ¬μ©) ν΄μ λ°λ‘)