Skip to content

Commit 3026740

Browse files
update version (#13)
1 parent dabfb0e commit 3026740

5 files changed

Lines changed: 257 additions & 218 deletions

File tree

grafi_dev/cli.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def run(
5252
host: str = "127.0.0.1",
5353
port: int = 8080,
5454
assistant_name: str = "assistant",
55-
is_async: bool = True,
55+
is_sequential: bool = True,
5656
open_browser: bool = True,
5757
):
5858
"""Run the assistant in *script* and launch the web UI."""
@@ -72,7 +72,7 @@ def run(
7272

7373
# Pass the assistant instance directly to create_app
7474
uvicorn.run(
75-
lambda: create_app(assistant=assistant, is_async=is_async), # type: ignore
75+
lambda: create_app(assistant=assistant, is_sequential=is_sequential), # type: ignore
7676
factory=True, # <─ tells Uvicorn to call it
7777
host=host,
7878
port=port,

grafi_dev/frontend/index.html

Lines changed: 83 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@
319319
let selected = { type: null, id: null };
320320

321321
// build graph & interaction
322-
let cy, wf;
322+
let cy, wf, assistant_json;
323323
(async () => {
324324
assistant_json = await (await fetch("/workflow")).json();
325325
renderYAML(panes.Info, assistant_json);
@@ -331,9 +331,8 @@
331331
);
332332
Object.values(wf.nodes).forEach((n) => {
333333
elems.push({ data: { id: n.name, label: n.name, type: "node" } });
334-
if (n.command) {
335-
const commandName = Object.keys(n.command)[0];
336-
const toolName = n.command[commandName].name || commandName;
334+
if (n.tool) {
335+
const toolName = n.tool.name;
337336
const toolId = `${n.name}#tool#${toolName}`;
338337

339338
elems.push({ data: { id: toolId, label: toolName, type: "tool" } });
@@ -352,51 +351,44 @@
352351
},
353352
});
354353
}
355-
n.publish_to.forEach((t) =>
354+
n.publish_to.forEach((topicName) =>
356355
elems.push({
357356
data: {
358-
id: `${n.name}->${t.name}`,
357+
id: `${n.name}->${topicName}`,
359358
source: n.name,
360-
target: t.name,
359+
target: topicName,
361360
},
362361
})
363362
);
364363
n.subscribed_expressions.forEach((expr) => {
365364
// Recursively extract all topics from the expression tree
366365
function extractTopics(expression) {
367366
const topics = [];
368-
367+
368+
// If this expression has a topic field, it's the topic name (string)
369369
if (expression.topic) {
370370
topics.push(expression.topic);
371371
}
372-
372+
373+
// Check left side of expression (for AND/OR operations)
373374
if (expression.left) {
374-
if (expression.left.topic) {
375-
topics.push(expression.left.topic);
376-
} else {
377-
// Recursively extract from left expression
378-
topics.push(...extractTopics(expression.left));
379-
}
375+
topics.push(...extractTopics(expression.left));
380376
}
381-
377+
378+
// Check right side of expression (for AND/OR operations)
382379
if (expression.right) {
383-
if (expression.right.topic) {
384-
topics.push(expression.right.topic);
385-
} else {
386-
// Recursively extract from right expression
387-
topics.push(...extractTopics(expression.right));
388-
}
380+
topics.push(...extractTopics(expression.right));
389381
}
390-
382+
391383
return topics;
392384
}
393-
385+
394386
const allTopics = extractTopics(expr);
395-
allTopics.forEach((topic) => {
387+
allTopics.forEach((topicName) => {
396388
elems.push({
397389
data: {
398-
id: `${topic.name}->${n.name}`,
399-
source: topic.name,
390+
id: `${topicName}->${n.name}`,
391+
source: topicName,
400392
target: n.name,
401393
},
402394
});
@@ -625,7 +617,7 @@
625617
});
626618

627619
// node/tool/topic click
628-
cy.on("tap", "node", async (evt) => {
620+
cy.on("tap", "node", (evt) => {
629621
// remove prior highlights
630622
cy.elements().removeClass("highlighted");
631623
// highlight this element
@@ -636,15 +628,53 @@
636628
if (currentTab === "Info") {
637629
if (typ === "tool") {
638630
const node_name = raw.split("#tool#")[0]; // Extract node name from node_name#tool#tool_name
639-
renderYAML(panes.Info, wf.nodes[node_name].command);
631+
renderYAML(panes.Info, wf.nodes[node_name].tool);
640632
} else if (typ === "node") {
641633
renderYAML(panes.Info, wf.nodes[raw]);
642634
} else {
643635
renderYAML(panes.Info, wf.topics[raw]);
644636
}
645637
} else if (currentTab === "Event") {
646-
showTab("Event");
647-
await document.querySelector('[data-tab="Event"]').onclick();
638+
// Manually trigger the event filtering and rendering
639+
let evs = eventsCache[convSelect.value] || [];
640+
641+
// apply element filters
642+
if (selected.type === "node") {
643+
evs = evs.filter(
644+
(e) =>
645+
["NodeInvoke", "NodeRespond"].includes(e.event_type) &&
646+
e.name === selected.id
647+
);
648+
}
649+
if (selected.type === "tool") {
650+
const toolName = selected.id.split("#tool#")[1];
651+
evs = evs.filter(
652+
(e) =>
653+
["ToolInvoke", "ToolRespond"].includes(e.event_type) &&
654+
e.name === toolName
655+
);
656+
}
657+
if (selected.type === "topic") {
658+
evs = evs.filter(
659+
(e) =>
660+
["PublishToTopic", "ConsumeFromTopic"].includes(e.event_type) &&
661+
e.name === selected.id
662+
);
663+
}
664+
if (reqSelect.value !== "All") {
665+
evs = evs.filter(
666+
(e) => e.invoke_context.assistant_request_id === reqSelect.value
667+
);
668+
}
669+
670+
// render according to drop‑down choice
671+
switch (eventView.value) {
672+
case "formed":
673+
renderFormedEvents(evs);
674+
break;
675+
default:
676+
renderRawEvents(evs);
677+
}
648678
}
649679
});
650680
})();
@@ -691,13 +721,21 @@
691721
return "";
692722
};
693723

694-
const collect = (arr) =>
695-
(arr || []).map(msgText).filter(Boolean).join(" | ");
696-
const collectNested = (items) =>
697-
(items || [])
698-
.flatMap((it) => collect(it.data ?? []).split(" | "))
724+
const collect = (arr) => {
725+
if (!Array.isArray(arr)) return "";
726+
return arr.map(msgText).filter(Boolean).join(" | ");
727+
};
728+
const collectNested = (items) => {
729+
if (!Array.isArray(items)) return "";
730+
return items
731+
.flatMap((it) => {
732+
const data = it.data ?? it;
733+
if (!Array.isArray(data)) return [];
734+
return collect(data).split(" | ");
735+
})
699736
.filter(Boolean)
700737
.join(" | ");
738+
};
701739

702740
const level = (e) => {
703741
if (e.event_type.startsWith("Assistant")) return 0;
@@ -712,6 +750,7 @@
712750
0: "bg-sky-50",
713751
1: "bg-indigo-50",
714752
2: "bg-emerald-50",
753+
2.5: "bg-teal-50",
715754
3: "bg-amber-50",
716755
};
717756

@@ -884,11 +923,15 @@
884923
.filter((e) => e.event_type === "AssistantRespond")
885924
.map((e) => {
886925
const inp = e.input_data
887-
? e.input_data.map((msg) => ({ content: msg.content }))
888-
: [];
889-
const out = e.output_data
890-
? e.output_data.map((msg) => ({ content: msg.content }))
926+
? e.input_data.data.map((msg) => ({ content: msg.content }))
891927
: [];
928+
const out = Array.isArray(e.output_data)
929+
? e.output_data.flatMap(ev =>
930+
Array.isArray(ev.data)
931+
? ev.data.map(msg => ({ content: msg.content }))
932+
: []
933+
)
934+
: [];
892935
return { input_data: inp, output_data: out };
893936
});
894937
renderHistory(panes.History, hist);

grafi_dev/server.py

Lines changed: 27 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -34,25 +34,26 @@ class ChatReply(BaseModel):
3434
messages: Messages
3535

3636

37-
def _to_messages(msgs_in: List[MsgIn]) -> Messages:
38-
from grafi.common.models.message import Message
39-
40-
return [Message(role=m.role, content=m.content) for m in msgs_in]
41-
42-
43-
def _invoke_context(conv_id: str, req_id: str):
37+
def _to_publish_topic_event(conv_id: str, req_id: str, msgs_in: List[MsgIn]):
38+
from grafi.common.events.topic_events.publish_to_topic_event import (
39+
PublishToTopicEvent,
40+
)
4441
from grafi.common.models.invoke_context import InvokeContext
42+
from grafi.common.models.message import Message
4543

46-
return InvokeContext(
47-
conversation_id=conv_id,
48-
assistant_request_id=req_id,
49-
invoke_id=uuid.uuid4().hex,
44+
return PublishToTopicEvent(
45+
invoke_context=InvokeContext(
46+
conversation_id=conv_id,
47+
assistant_request_id=req_id,
48+
invoke_id=uuid.uuid4().hex,
49+
),
50+
data=[Message(role=m.role, content=m.content) for m in msgs_in],
5051
)
5152

5253

5354
# ---------- conversation helpers ----------------------------------------
54-
def get_conversation_ids():
55-
evs = container.event_store.get_events()
55+
async def get_conversation_ids():
56+
evs = await container.event_store.get_events()
5657
conv_ids = {e.invoke_context.conversation_id for e in evs}
5758
return sorted(
5859
conv_ids,
@@ -62,8 +63,8 @@ def get_conversation_ids():
6263
)
6364

6465

65-
def get_request_ids(conv_id: str):
66-
evs = container.event_store.get_conversation_events(conv_id)
66+
async def get_request_ids(conv_id: str):
67+
evs = await container.event_store.get_conversation_events(conv_id)
6768
req_ids = {e.invoke_context.assistant_request_id for e in evs}
6869
return sorted(
6970
req_ids,
@@ -74,25 +75,20 @@ def get_request_ids(conv_id: str):
7475

7576

7677
# ---------- FastAPI factory ---------------------------------------------
77-
def create_app(assistant: Assistant, is_async: bool = True) -> FastAPI:
78+
def create_app(assistant: Assistant, is_sequential: bool = True) -> FastAPI:
7879
api = FastAPI(title="Graphite-Dev API")
7980

8081
@api.post("/chat", response_model=ChatReply)
8182
async def chat(req: ChatRequest):
8283
try:
8384
out: Messages = []
84-
if is_async:
85-
86-
async for messages in assistant.a_invoke(
87-
_invoke_context(req.conversation_id, req.assistant_request_id),
88-
_to_messages(req.messages),
89-
):
90-
out.extend(messages)
91-
else:
92-
out = assistant.invoke(
93-
_invoke_context(req.conversation_id, req.assistant_request_id),
94-
_to_messages(req.messages),
95-
)
85+
async for event in assistant.invoke(
86+
_to_publish_topic_event(
87+
req.conversation_id, req.assistant_request_id, req.messages
88+
),
89+
is_sequential=is_sequential,
90+
):
91+
out.extend(event.data)
9692
logger.info(out)
9793
return ChatReply(messages=out)
9894
except Exception as exc:
@@ -107,7 +103,7 @@ async def chat(req: ChatRequest):
107103
async def events_convo_dump(conv_id: str):
108104
return [
109105
e.model_dump() # type: ignore
110-
for e in container.event_store.get_conversation_events(conv_id)
106+
for e in await container.event_store.get_conversation_events(conv_id)
111107
]
112108

113109
@api.get("/workflow", response_model=dict)
@@ -116,11 +112,11 @@ async def workflow():
116112

117113
@api.get("/conversations", response_model=list[str])
118114
async def list_convs():
119-
return get_conversation_ids()
115+
return await get_conversation_ids()
120116

121117
@api.get("/conversations/{conv_id}/requests", response_model=list[str])
122118
async def list_reqs(conv_id: str):
123-
return get_request_ids(conv_id)
119+
return await get_request_ids(conv_id)
124120

125121
ui_dir = Path(__file__).parent / "frontend"
126122
api.mount("/", StaticFiles(directory=ui_dir, html=True), name="ui")

pyproject.toml

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,18 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "grafi-dev"
7-
version = "0.0.7"
7+
version = "0.0.9"
88
description = "Run a grafi Assistant locally with a live workflow graph & trace viewer"
99
authors = [{ name = "Craig Li", email = "craig@binome.dev" }]
1010
readme = "README.md"
11-
requires-python = ">=3.10,<3.13"
11+
requires-python = ">=3.11"
1212
dependencies = [
1313
"fastapi>=0.115.12",
14-
"grafi>=0.0.21",
14+
"grafi>=0.0.31",
1515
"typer>=0.15.3",
1616
"uvicorn>=0.34.2",
1717
]
1818

19-
20-
21-
2219
[project.scripts]
2320
grafi-dev = "grafi_dev.cli:app"
2421

0 commit comments

Comments
 (0)