Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
90 changes: 90 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,96 @@
<a href="https://github.com/marimo-team/marimo/blob/main/LICENSE"><img src="https://img.shields.io/pypi/l/marimo" /></a>
</p>

# Marimo Agents

Marimo Agents is an experimental extension of Marimo pioneering **agentic notebooks**, allowing you to run
notebooks with AI agents as first-class cells.

To use it, you need to install the `marimo-agents` package:

```bash
pip install marimo-agents
```

## But why?

Research agents are a great way to explore data and hypotheses. In a chat interface, though, the messages are in an immutable order, since each "message" in a "conversation" is dependent on the previous ones. This means that queries made by a user and data collected by an agent must remain in the same order in a chat.

However, in research, you may find that some queries lead to nowhere, or you may want to reorganize evidence you collect from different queries to the agent to form a cohesive narrative from your data. This is exactly what the notebook interface enables. Further, agentic notebooks allow for you to interleave markdown annotations and your own python code with agent calls for a richer data exploration and analysis workflow.

## Usage

An **Agent** is at its core a function that takes in a **Prompt** and returns a **Response**.

You can define and register an agent as follows (note that marimo-agents shares the same namespace as marimo, so your marimo import is preserved):

```python
import marimo

__generated_with = "0.0.4"
app = marimo.App(width="medium")


@app.cell
def _():
import marimo as mo
agent = mo.ai.agents.Agent(
name="My Agent",
# This can be a LangChain chain, LangGraph invocation, or any callable that takes in a prompt and returns a response
run_fn=lambda prompt: f"You said: {prompt}!",
)
mo.ai.agents.register_agent(agent)

@app.cell
async def _():
await mo.ai.agents.run_agent("What is the capital of France?")
```

This will render as:

<img src="https://raw.githubusercontent.com/riyavsinha/marimo/main/docs/_static/basic-agent.png" width="700px" />

You will notice that the `await mo.ai.agents.run_agent()` cell is rendered specially in the UI as just a plain text input.

An option has also been added to the New Cell list at the bottom to immediately add a new agent cell.


## Generating Chat Suggestions

You can also generate chat suggestions for your agent by providing a `suggestions_fn` to the `Agent` constructor.

In the above example using:

```python
import asyncio
async def suggestions_fn():
await asyncio.sleep(3)
return [
mo.ai.agents.Suggestion(
id="1",
title="What is the capital of Greece?",
description="Since you asked about Europe, here's a related question",
type=mo.ai.agents.SuggestionType.PROMPT_IDEA
),
]
agent = mo.ai.agents.Agent(
name="My Agent",
run_fn=lambda prompt: f"You said: {prompt}!",
# This can be generated by a LangChain chain, LangGraph invocation, or any callable that takes in a prompt and returns a list of suggestions
suggestions_fn=suggestions_fn
)
```

This will render as:

<img src="https://raw.githubusercontent.com/riyavsinha/marimo/main/docs/_static/agent-suggestions.png" width="700px" />

The suggestions are rendered in a new panel to the right of the agent cell. These are generated as a background task, so they can happen after the agent cell has returned its response.

---

# Original README

**marimo** is a reactive Python notebook: run a cell or interact with a UI
element, and marimo automatically runs dependent cells (or <a href="#expensive-notebooks">marks them as stale</a>), keeping code and outputs
consistent. marimo notebooks are stored as pure Python, executable as scripts,
Expand Down
25 changes: 25 additions & 0 deletions README_ext.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Marimo LLM Agent Extension

This fork is a modified version of Marimo with support for executing LLM agents from cells. Some features may interfere with Marimo's original functionality, so use with caution.

## Feature overview

### Agent Registry

The agent registry is a new feature that allows users to register LLM agents (e.g. LangChain, LangGraph) with the Marimo UI. The cell input can be set as a plain-text input to the agent, with the cell output representing the agent's response.

Usage:


### Background Datasource Variable Registration

> [!CAUTION]
> This function may cause unintended bugs in Marimo's reactivity, since
> defined variables cannot be statically analyzed. Also, this can be
> confusing for users if used inappropriately to flood the global scope.
> Please be mindful of this function.

This feature allows LLM agents designed to work with this version of Marimo
to emit variables to the global scope. This is useful for agents that
make tool calls and want to implicitly assign intermediate fetched data to
variables.
Binary file added docs/_static/agent-suggestions.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/_static/basic-agent.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
"@xterm/xterm": "^5.5.0",
"ag-grid-community": "^32.3.3",
"ag-grid-react": "^32.3.3",
"ai": "^4.1.34",
"ai": "^5.0.0",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This is a major version upgrade for the ai package, from v4 to v5. Major versions often introduce breaking changes, and Snyk has flagged this as a breaking change.

I was unable to find any public documentation, changelog, or migration guide for version 5.0.0 of this package. The public npm registry for the ai package (Vercel AI SDK) only lists versions up to 3.x.

The application uses the useCompletion hook from ai/react. It's possible that the API of this hook has changed. For example, the experimental_throttle option might have been removed or renamed in this new major version.

Given the lack of documentation and the high risk of breaking changes, I recommend thorough testing of all AI-related features before merging this PR. If you have access to a changelog or migration guide, please share it.

"ansi_up": "^6.0.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
Expand Down
2 changes: 1 addition & 1 deletion frontend/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 21 additions & 1 deletion frontend/src/components/editor/cell/code/language-toggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ import { MarkdownIcon, PythonIcon } from "./icons";
import { Button } from "@/components/ui/button";
import { Tooltip } from "@/components/ui/tooltip";
import type { LanguageAdapter } from "@/core/codemirror/language/types";
import { DatabaseIcon } from "lucide-react";
import { BotIcon, DatabaseIcon } from "lucide-react";
import { useMemo } from "react";
import { MarkdownLanguageAdapter } from "@/core/codemirror/language/markdown";
import { SQLLanguageAdapter } from "@/core/codemirror/language/sql";
import { Functions } from "@/utils/functions";
import { AIAgentLanguageAdapter } from "@/core/codemirror/language/ai";

interface LanguageTogglesProps {
editorView: EditorView | null;
Expand All @@ -33,6 +34,10 @@ export const LanguageToggles: React.FC<LanguageTogglesProps> = ({
() => new SQLLanguageAdapter().isSupported(code) || code.trim() === "",
[code],
);
const canUseAgent = useMemo(
() => new AIAgentLanguageAdapter().isSupported(code) || code.trim() === "",
[code],
);

return (
<div className="absolute right-3 top-2 z-20 flex hover-action gap-1">
Expand Down Expand Up @@ -83,6 +88,21 @@ export const LanguageToggles: React.FC<LanguageTogglesProps> = ({
displayName="Python"
onAfterToggle={Functions.NOOP}
/>
<LanguageToggle
editorView={editorView}
currentLanguageAdapter={currentLanguageAdapter}
canSwitchToLanguage={canUseAgent && currentLanguageAdapter === "python"}
icon={
<BotIcon
color={"var(--sky-11)"}
strokeWidth={2.5}
className="w-4 h-4"
/>
}
toType="agent"
displayName="Agent"
onAfterToggle={onAfterToggle}
/>
</div>
);
};
Expand Down
71 changes: 71 additions & 0 deletions frontend/src/components/editor/chrome/panels/suggestions-panel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import React from "react";
import { useAtomValue } from "jotai";
import {
MessageCircleQuestionIcon,
MessageCircleWarningIcon,
LightbulbIcon,
} from "lucide-react";
import { suggestionsAtom } from "@/core/suggestions/state";
import { cn } from "@/utils/cn";
import { PanelEmptyState } from "./empty-state";
import { useCellActions } from "@/core/cells/cells";
import { useLastFocusedCellId } from "@/core/cells/focus";

export const SuggestionsPanel: React.FC = () => {
const suggestions = useAtomValue(suggestionsAtom);
const { createNewCell } = useCellActions();
const lastFocusedCellId = useLastFocusedCellId();

if (!suggestions.length) {
return (
<PanelEmptyState
icon={<LightbulbIcon />}
title="No suggestions"
description="There are currently no suggestions available."
/>
);
}

const handleSuggestionClick = (title: string) => {
createNewCell({
code: `await mo.ai.agents.run_agent("${title}")`,
before: false,
cellId: lastFocusedCellId ?? "__end__",
});
};

return (
<div className="flex flex-col gap-3 p-4 overflow-y-auto">
{suggestions.map((suggestion) => (
<div
key={suggestion.id}
className={cn(
"rounded-lg border p-4 transition-colors cursor-pointer",
"hover:border-border-hover",
suggestion.type === "prompt_warning" &&
"border-orange-500/50 bg-orange-500/10",
suggestion.type === "prompt_idea" &&
"border-blue-500/50 bg-blue-500/10",
)}
onClick={() => handleSuggestionClick(suggestion.title)}
>
<div className="flex items-start gap-2">
<div className="flex-shrink-0">
{suggestion.type === "prompt_warning" ? (
<MessageCircleWarningIcon className="h-6 w-6" />
) : (
<MessageCircleQuestionIcon className="h-6 w-6" />
)}
</div>
<div className="flex-1">
<h3 className="font-medium">{suggestion.title}</h3>
<p className="mt-1 text-sm text-muted-foreground">
{suggestion.description}
</p>
</div>
</div>
</div>
))}
</div>
);
};
10 changes: 9 additions & 1 deletion frontend/src/components/editor/chrome/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
BoxIcon,
BotMessageSquareIcon,
ActivityIcon,
LightbulbIcon,
} from "lucide-react";

export type PanelType =
Expand All @@ -30,7 +31,8 @@ export type PanelType =
| "datasources"
| "scratchpad"
| "chat"
| "logs";
| "logs"
| "suggestions";

export interface PanelDescriptor {
type: PanelType;
Expand Down Expand Up @@ -115,6 +117,12 @@ export const PANELS: PanelDescriptor[] = [
tooltip: "Scratchpad",
position: "sidebar",
},
{
type: "suggestions",
Icon: LightbulbIcon,
tooltip: "View suggestions",
position: "sidebar",
},
{
type: "errors",
Icon: XCircleIcon,
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/components/editor/chrome/wrapper/app-chrome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { PackagesPanel } from "../panels/packages-panel";
import { ChatPanel } from "@/components/chat/chat-panel";
import { TooltipProvider } from "@radix-ui/react-tooltip";
import { TracingPanel } from "../panels/tracing-panel";
import { SuggestionsPanel } from "../panels/suggestions-panel";

const LazyTerminal = React.lazy(() => import("@/components/terminal/terminal"));

Expand Down Expand Up @@ -155,6 +156,7 @@ export const AppChrome: React.FC<PropsWithChildren> = ({ children }) => {
{selectedPanel === "chat" && <ChatPanel />}
{selectedPanel === "logs" && <LogsPanel />}
{selectedPanel === "tracing" && <TracingPanel />}
{selectedPanel === "suggestions" && <SuggestionsPanel />}
</TooltipProvider>
</div>
</Suspense>
Expand Down
28 changes: 28 additions & 0 deletions frontend/src/components/editor/renderers/CellArray.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { useDeleteCellCallback } from "../cell/useDeleteCell";
import { cn } from "@/utils/cn";
import { Button } from "@/components/ui/button";
import {
BotIcon,
DatabaseIcon,
SparklesIcon,
SquareCodeIcon,
Expand All @@ -37,6 +38,7 @@ import { aiEnabledAtom, autoInstantiateAtom } from "@/core/config/config";
import { useAtomValue } from "jotai";
import { useBoolean } from "@/hooks/useBoolean";
import { AddCellWithAI } from "../ai/add-cell-with-ai";
import { AIAgentLanguageAdapter } from "@/core/codemirror/language/ai";
import type { Milliseconds } from "@/utils/time";
import { SQLLanguageAdapter } from "@/core/codemirror/language/sql";
import { MarkdownLanguageAdapter } from "@/core/codemirror/language/markdown";
Expand Down Expand Up @@ -343,6 +345,32 @@ const AddCellButtons: React.FC<{
SQL
</Button>
</Tooltip>
<Tooltip
content={
// aiEnabled ? null : <span>Enable via settings under AI Assist</span>
null
}
delayDuration={100}
asChild={false}
>
<Button
className={buttonClass}
variant="text"
size="sm"
// disabled={!aiEnabled}
onClick={() => {
maybeAddMarimoImport(autoInstantiate, createNewCell);
createNewCell({
cellId: { type: "__end__", columnId },
before: false,
code: new AIAgentLanguageAdapter().defaultCode,
});
}}
>
<BotIcon className="mr-2 size-4 flex-shrink-0" />
Chat
</Button>
</Tooltip>
<Tooltip
content={
aiEnabled ? null : <span>Enable via settings under AI Assist</span>
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/core/codemirror/language/LanguageAdapters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { LanguageAdapter, LanguageAdapterType } from "./types";
import { PythonLanguageAdapter } from "./python";
import { MarkdownLanguageAdapter } from "./markdown";
import { SQLLanguageAdapter } from "./sql";
import { AIAgentLanguageAdapter } from "./ai";

export const LanguageAdapters: Record<
LanguageAdapterType,
Expand All @@ -11,6 +12,7 @@ export const LanguageAdapters: Record<
python: () => new PythonLanguageAdapter(),
markdown: () => new MarkdownLanguageAdapter(),
sql: () => new SQLLanguageAdapter(),
agent: () => new AIAgentLanguageAdapter(),
};

export function getLanguageAdapters() {
Expand Down
Loading