Skip to content
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
b0432fe
Initial commit
MalithaPrabhashana Sep 30, 2025
beae06a
clean up import statements and improve formatting in visit_by.py
MalithaPrabhashana Sep 30, 2025
45ee784
support syntax for visit by
MalithaPrabhashana Sep 30, 2025
cfccc2a
refactor: enhance exit_visit_stmt logic for visitor handling
MalithaPrabhashana Oct 1, 2025
9b42ba1
remove unused ThreeWheeler node and clean up visit logic
MalithaPrabhashana Oct 1, 2025
03dd1b1
add VehicleShowroom and showroom_visitor nodes with vehicle display l…
MalithaPrabhashana Oct 3, 2025
ff11c40
Merge branch 'main' into visit_by_implementation
MalithaPrabhashana Oct 6, 2025
6d346e1
Fix variable references in PyastGenPass to use constants
MalithaPrabhashana Oct 7, 2025
09a3c4f
Refactor node description functions: move to JacUtils and simplify usage
MalithaPrabhashana Oct 7, 2025
376f63b
Merge branch 'main' into visit_by_implementation
MalithaPrabhashana Oct 7, 2025
3c37350
Implement code changes to enhance functionality and improve performance
MalithaPrabhashana Oct 7, 2025
d83642a
Add vehicle showroom example with vehicle types and visitor interaction
MalithaPrabhashana Oct 7, 2025
812e777
Add blank lines for improved code readability in visit_by.py, pyast_g…
MalithaPrabhashana Oct 7, 2025
9b4edef
Merge branch 'main' into visit_by_implementation
MalithaPrabhashana Oct 8, 2025
a427851
Implement code changes to enhance functionality and improve performance
MalithaPrabhashana Oct 8, 2025
93c4fdd
Add static method decorators and update vehicle finder examples for i…
MalithaPrabhashana Oct 9, 2025
24a7c3d
Merge branch 'main' into visit_by_implementation
MalithaPrabhashana Oct 9, 2025
564288d
Implement code changes to enhance functionality and improve performance
MalithaPrabhashana Oct 9, 2025
79e0f9c
Add vehicle finder examples and update test paths for showroom functi…
MalithaPrabhashana Oct 9, 2025
3ff4125
used by decorator to visit_by
MalithaPrabhashana Oct 9, 2025
b5da61b
Add LLM-Powered Graph Traversal feature to release notes
MalithaPrabhashana Oct 9, 2025
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
23 changes: 23 additions & 0 deletions jac-byllm/byllm/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@

from byllm.llm import Model
from byllm.mtir import MTIR
from byllm.visit_by import _visit_by

from jaclang.runtimelib.constructs import (
EdgeArchetype,
NodeArchetype,
WalkerArchetype,
)
from jaclang.runtimelib.machine import hookimpl


Expand All @@ -23,6 +29,23 @@ def call_llm(model: Model, mtir: MTIR) -> object:
"""Call JacLLM and return the result."""
return model.invoke(mtir=mtir)

@staticmethod
@hookimpl
def visit_by(
model: Model,
walker: WalkerArchetype,
node: NodeArchetype,
connected_nodes: list[NodeArchetype],
) -> (
list[NodeArchetype | EdgeArchetype]
| list[NodeArchetype]
| list[EdgeArchetype]
| NodeArchetype
| EdgeArchetype
):
"""Go through the available nodes and decide which next nodes to visit based on their semantics using an llm."""
return _visit_by(model, walker, node, connected_nodes)


def by(model: Model) -> Callable:
"""Python library mode decorator for Jac's by llm() syntax."""
Expand Down
76 changes: 76 additions & 0 deletions jac-byllm/byllm/visit_by.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""This module contains functions for visiting and describing nodes in a graph-like structure."""

from __future__ import annotations

# from jaclang.runtimelib.builtin import *
from byllm import MTIR, Model

from jaclang import JacMachineInterface as _
from jaclang.runtimelib.constructs import (
EdgeArchetype,
NodeArchetype,
WalkerArchetype,
)


def get_where_to_visit_next(
model: Model,
walker: WalkerArchetype,
current_node: NodeArchetype,
connected_nodes: list[NodeArchetype | EdgeArchetype],
description: str = "",
) -> list[int]:
"""Determine the next nodes to visit by analyzing semantics using an LLM.

Walker is a transitioning agent while the nodes are agents that can be visited.
It returns the list of indexes of the next nodes to visit in order to complete the task of the walker.
If no suitable node is found, it returns [].
"""
return _.call_llm(
model=model(),
mtir=MTIR.factory(
caller=get_where_to_visit_next,
args={
"walker": walker,
"current_node": current_node,
"connected_nodes": connected_nodes,
"description": description,
},
call_params=model.call_params,
),
)


def _visit_by(
model: Model,
walker: WalkerArchetype,
node: NodeArchetype,
connected_nodes: list[NodeArchetype],
) -> (
list[NodeArchetype | EdgeArchetype]
| list[NodeArchetype]
| list[EdgeArchetype]
| NodeArchetype
| EdgeArchetype
):
"""Go through the available nodes and decide which next nodes to visit based on their semantics using an llm."""
if not isinstance(model, Model):
raise TypeError("Invalid llm object")
if not connected_nodes:
raise ValueError("No connected agents found for the walker")
next_node_indexes = get_where_to_visit_next(
model,
walker,
node,
connected_nodes,
description=_.describe_object_list(connected_nodes),
)
ordered_list = []
for index in next_node_indexes:
if index < len(connected_nodes) and index >= 0:
ordered_list.append(connected_nodes[index])
else:
raise IndexError("Index out of range for connected nodes")

_.visit(walker, ordered_list)
return ordered_list
46 changes: 46 additions & 0 deletions jac-byllm/examples/visit_by/vehicle_finder1.jac
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import from byllm.llm { Model }

glob llm = Model("gpt-4o");

node VehicleShowroom {
can show_vehicles with showroom_visitor entry {
print("Welcome to the Vehicle Showroom! Here are the vehicles available:\n");
visit [-->] by llm();
}
}

node Car {
can act with showroom_visitor entry {
print("🚙 Car: A sleek sedan with modern features.");
}
}

node Bike {
can act with showroom_visitor entry {
print("🚲 Bike: A sporty mountain bike perfect for off-road adventures.");
}
}

node Van {
can act with showroom_visitor entry {
print("🚚 Van: A spacious van ideal for family trips.");
}
}

walker showroom_visitor {
has vehicle_wheel_number: list[int];

can visit_showroom with `root entry {
print("Visitor: Entering the showroom...");
visit [-->];
}
}

with entry {
show_room = root ++> VehicleShowroom();
show_room ++> Car();
show_room ++> Bike();
show_room ++> Van();

showroom_visitor([2, 4]) spawn root;
}
44 changes: 44 additions & 0 deletions jac-byllm/examples/visit_by/vehicle_finder2.jac
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import from byllm { Model }

glob llm: Model = Model("gpt-4o");

node VehicleShowroom {
can show_vehicles with showroom_visitor entry {
print("Welcome to the Vehicle Showroom! Here are the vehicles available:\n");
visit [-->(`?Car)] by llm();
}
}

node Car {
has color: str;
can act with showroom_visitor entry {
print("🚙 Car: A sleek sedan with modern features.");
}
}

node Bike {
has color: str;
can act with showroom_visitor entry {
print("🚲 Bike: A sporty mountain bike perfect for off-road adventures.");
}
}

walker showroom_visitor {
has expected_color: list[str];

can visit_showroom with `root entry {
print("Visitor: Entering the showroom...");
visit [-->];
}
}

with entry {
show_room = root ++> VehicleShowroom();
show_room ++> Car("Red");
show_room ++> Car("Blue");
show_room ++> Car("Black");
show_room ++> Bike("Green");
show_room ++> Bike("Blue");

showroom_visitor(["Blue"]) spawn root;
}
2 changes: 1 addition & 1 deletion jac/jaclang/compiler/jac.lark
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ ctrl_stmt: KW_SKIP | KW_BREAK | KW_CONTINUE

// [Heading]: Walker visit and disengage (OSP).
spatial_stmt: visit_stmt | disenage_stmt
visit_stmt: KW_VISIT (COLON expression COLON)? expression (else_stmt | SEMI)
visit_stmt: KW_VISIT (COLON expression COLON)? expression by_llm? (else_stmt | SEMI)
disenage_stmt: KW_DISENGAGE SEMI

// [Heading]: Assignments.
Expand Down
4 changes: 2 additions & 2 deletions jac/jaclang/compiler/larkparse/jac_parser.py

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion jac/jaclang/compiler/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -1571,20 +1571,22 @@ def visit_stmt(self, _: None) -> uni.VisitStmt:
"""Grammar rule.

visit_stmt: KW_VISIT (COLON expression COLON)?
expression (else_stmt | SEMI)
expression by_llm? (else_stmt | SEMI)
"""
self.consume_token(Tok.KW_VISIT)
insert_loc = None
if self.match_token(Tok.COLON):
insert_loc = self.consume(uni.Expr)
self.consume_token(Tok.COLON)
target = self.consume(uni.Expr)
genai_call = self.match(uni.Expr)
else_body = self.match(uni.ElseStmt)
if else_body is None:
self.consume_token(Tok.SEMI)
return uni.VisitStmt(
insert_loc=insert_loc,
target=target,
genai_call=genai_call,
else_body=else_body,
kid=self.cur_nodes,
)
Expand Down
84 changes: 55 additions & 29 deletions jac/jaclang/compiler/passes/main/pyast_gen_pass.py
Original file line number Diff line number Diff line change
Expand Up @@ -1767,43 +1767,69 @@ def exit_visit_stmt(self, node: uni.VisitStmt) -> None:
if node.from_walker
else ast3.Name(id=Con.VISITOR.value, ctx=ast3.Load())
)
if (
node.genai_call
and isinstance(node.genai_call, uni.FuncCall)
and isinstance(node.genai_call.target, uni.Name)
):
_model = self.sync(
ast3.Name(id=node.genai_call.target.value, ctx=ast3.Load())
)
if loc.id == Con.VISITOR.value:
_self = self.sync(ast3.Name(id=Con.VISITOR.value, ctx=ast3.Load()))
_here = self.sync(ast3.Name(id="self", ctx=ast3.Load()))
else:
_self = self.sync(ast3.Name(id="self", ctx=ast3.Load()))
_here = self.sync(ast3.Name(id=Con.HERE.value, ctx=ast3.Load()))

visit_call = self.sync(
ast3.Call(
func=self.jaclib_obj("visit"),
args=cast(list[ast3.expr], [loc, node.target.gen.py_ast[0]]),
keywords=[],
_nodes = node.target.gen.py_ast[0]
visit_call = self.sync(
ast3.Call(
func=self.jaclib_obj("visit_by"),
args=cast(
list[ast3.expr], [_model, _self, _here, cast(ast3.expr, _nodes)]
),
keywords=[],
)
)
node.gen.py_ast = [self.sync(ast3.Expr(value=visit_call))]
else:
visit_call = self.sync(
ast3.Call(
func=self.jaclib_obj("visit"),
args=cast(list[ast3.expr], [loc, node.target.gen.py_ast[0]]),
keywords=[],
)
)
)

if node.insert_loc is not None:
visit_call.keywords.append(
self.sync(
ast3.keyword(
arg="insert_loc",
value=cast(ast3.expr, node.insert_loc.gen.py_ast[0]),
if node.insert_loc is not None:
visit_call.keywords.append(
self.sync(
ast3.keyword(
arg="insert_loc",
value=cast(ast3.expr, node.insert_loc.gen.py_ast[0]),
)
)
)
)

node.gen.py_ast = [
(
self.sync(
ast3.If(
test=self.sync(
ast3.UnaryOp(
op=self.sync(ast3.Not()),
operand=visit_call,
)
),
body=cast(list[ast3.stmt], node.else_body.gen.py_ast),
orelse=[],
node.gen.py_ast = [
(
self.sync(
ast3.If(
test=self.sync(
ast3.UnaryOp(
op=self.sync(ast3.Not()),
operand=visit_call,
)
),
body=cast(list[ast3.stmt], node.else_body.gen.py_ast),
orelse=[],
)
)
if node.else_body
else self.sync(ast3.Expr(value=visit_call))
)
if node.else_body
else self.sync(ast3.Expr(value=visit_call))
)
]
]

def exit_disengage_stmt(self, node: uni.DisengageStmt) -> None:
loc = self.sync(
Expand Down
5 changes: 5 additions & 0 deletions jac/jaclang/compiler/unitree.py
Original file line number Diff line number Diff line change
Expand Up @@ -2807,11 +2807,13 @@ def __init__(
self,
insert_loc: Optional[Expr],
target: Expr,
genai_call: Optional[Expr],
else_body: Optional[ElseStmt],
kid: Sequence[UniNode],
) -> None:
self.insert_loc = insert_loc
self.target = target
self.genai_call = genai_call
UniNode.__init__(self, kid=kid)
WalkerStmtOnlyNode.__init__(self)
AstElseBodyNode.__init__(self, else_body=else_body)
Expand All @@ -2830,6 +2832,9 @@ def normalize(self, deep: bool = False) -> bool:
new_kid.append(self.insert_loc)
new_kid.append(self.gen_token(Tok.COLON))
new_kid.append(self.target)
if self.genai_call:
new_kid.append(self.gen_token(Tok.KW_BY))
new_kid.append(self.genai_call)
if self.else_body:
new_kid.append(self.else_body)
else:
Expand Down
Loading