Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions docs/docs/communityhub/release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ This document provides a summary of new features, improvements, and bug fixes in

## jaclang 0.8.10 / jac-cloud 0.2.10 / byllm 0.4.5 (Unreleased)

- **LLM-Powered Graph Traversal (`visit by`)**: Introduced `visit [-->] by llm()` syntax enabling walkers to make intelligent traversal decisions. The LLM analyzes the semantic context of available nodes and selects which ones to visit based on the walker's purpose, bringing AI-powered decision making to graph navigation.

## jaclang 0.8.9 / jac-cloud 0.2.9 / byllm 0.4.4 (Latest Release)

- **Typed Context Blocks (OSP)**: Fully implemented typed context blocks (`-> NodeType { }` and `-> WalkerType { }`) for Object-Spatial Programming, enabling conditional code execution based on runtime types.
Expand Down
5 changes: 3 additions & 2 deletions jac-byllm/byllm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

from byllm.llm import Model
from byllm.mtir import MTIR
from byllm.plugin import JacMachine
from byllm.types import Image, MockToolCall, Video

by = JacMachine.by
from jaclang.runtimelib.machine import JacMachineInterface

by = JacMachineInterface.by

__all__ = ["by", "Image", "MockToolCall", "Model", "MTIR", "Video"]
40 changes: 20 additions & 20 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 @@ -25,23 +31,17 @@ def call_llm(model: Model, mtir: MTIR) -> object:

@staticmethod
@hookimpl
def by(model: Model) -> Callable:
"""Python library mode decorator for Jac's by llm() syntax."""

def _decorator(caller: Callable) -> Callable:
def _wrapped_caller(*args: object, **kwargs: object) -> object:
invoke_args: dict[int | str, object] = {}
for i, arg in enumerate(args):
invoke_args[i] = arg
for key, value in kwargs.items():
invoke_args[key] = value
mtir = MTIR.factory(
caller=caller,
args=invoke_args,
call_params=model.llm_connector.call_params,
)
return model.invoke(mtir=mtir)

return _wrapped_caller

return _decorator
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)
75 changes: 75 additions & 0 deletions jac-byllm/byllm/visit_by.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""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 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]:
"""Call LLM to analyze semantics and determine the next nodes to visit."""

@_.by(model=model)
def _get_where_to_visit_next(
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 []

return _get_where_to_visit_next(walker, current_node, connected_nodes, description)


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/tests/fixtures/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(model_name="mockllm", outputs=[[0, 2]]);

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/tests/fixtures/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("mockllm", outputs=[[1]]);

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, color: " + self.color);
}
}

node Bike {
has color: str;
can act with showroom_visitor entry {
print("Bike, color: " + self.color);
}
}

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;
}
21 changes: 20 additions & 1 deletion jac-byllm/tests/test_byllm.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,4 +252,23 @@ def test_enum_without_value(self) -> None:
sys.stdout = sys.__stdout__
stdout_value = captured_output.getvalue()
self.assertIn("YES", stdout_value)
self.assertIn("NO", stdout_value)
self.assertIn("NO", stdout_value)

def test_visit_by(self) -> None:
"""Test the visit by functionality."""
captured_output = io.StringIO()
sys.stdout = captured_output
jac_import("vehicle_finder1", base_path=self.fixture_abs_path("./visit_by"))
sys.stdout = sys.__stdout__
stdout_value = captured_output.getvalue()
self.assertIn("Car: A sleek sedan with modern features.", stdout_value)
self.assertIn("Van: A spacious van ideal for family trips.", stdout_value)

def test_visit_by_with_node_filtering(self) -> None:
"""Test the visit by functionality with node filtering."""
captured_output = io.StringIO()
sys.stdout = captured_output
jac_import("vehicle_finder2", base_path=self.fixture_abs_path("./visit_by"))
sys.stdout = sys.__stdout__
stdout_value = captured_output.getvalue()
self.assertIn("Car, color: Blue", stdout_value)
2 changes: 1 addition & 1 deletion jac/jaclang/compiler/jac.lark
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,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
Loading