Skip to main content

Visvo AI

visvoai-core

tests PyPI license: MIT

The VisvoAI™ agent↔tools loop on LangGraph, done right — the ~1k lines every agent product ends up writing, already hardened by two real consumers.

An AI agent is a loop — the model thinks, calls a tool, reads the result, thinks again, and finally answers. Writing that loop well is surprisingly hard: endless loops, repeated calls, messy endings. visvoai-core is that loop, written once and written well, with clean hooks to plug in your own tools, storage, and rules.

It is deliberately not a framework — no datastore, no web layer, no auth. The same code carries a full terminal agent (visvoai-cli) and a hosted platform, so the hooks you'd use are the hooks we use.

pip install visvoai-core            # pulls langgraph + langchain-core only
pip install "visvoai-core[sqlite]"  # + durable SQLite memory (AsyncSqliteSaver),
                                    #   with checkpoint versions that fit together

Core is provider-agnostic — it takes any LangChain BaseChatModel (core itself never needs an API key; the model you pass in carries its own — e.g. GEMINI_API_KEY via visvoai-ai). Pair it with visvoai-ai for a unified provider layer, or bring your own model.

Sixty seconds to a working agent

from visvoai.core.runtime import AgentRuntime
from visvoai.ai import build_chat_model            # pip install "visvoai-ai[gemini]"
from langchain_core.tools import tool

@tool
def read_file(path: str) -> str:
    """Read a file and return its contents."""
    return open(path).read()

tools = [read_file]
graph = AgentRuntime().build_graph(
    model=build_chat_model("gemini:gemini-2.5-flash"),
    core_tools=tools,
    system_prompt="You are a code assistant.",
)

# a standard LangGraph app — invoke it, or stream events for a live UI
# inside an async function (or asyncio.run(...) — see examples/01)
from visvoai.core import ask
answer = await ask(graph, "What's in pyproject.toml?")
print(result["messages"][-1].content)

Building a product on this? BUILD-YOUR-OWN.md is the whole recipe — the four decisions, a Slack-bot skeleton, and which hook to override when the defaults aren't enough.

What the loop gives you that raw LangGraph doesn't

  • A soft step cap with clean finalize — at the budget, the model is re-invoked without tools and instructed to answer. Your users get a coherent final message instead of a GraphRecursionError.
  • Duplicate-call blocking — the model can't burn rounds re-issuing the identical tool call.
  • Semantic tool retrieval — when you have too many tools to bind at all (MCP fleets, plugin ecosystems), find_tools + per-round retrieval bind only what's relevant to the current request.
  • A tool lifecycle, not just functions — declare config, write _execute(), and registration/validation/persistence hooks come free.

Defining tools — four ways, pick per tool

build_graph takes them all, mixed freely; normalization to the loop's internal currency happens once at the boundary, never in your files.

1 · A plain typed function — schema from type hints, description from the docstring; a Google-style Args: section becomes per-argument descriptions in the schema the model sees. No framework imports; async works the same way.

def word_count(text: str) -> int:
    """Count the words in a piece of text."""
    return len(text.split())

def fetch_status(url: str, timeout: int = 10) -> str:
    """Check whether a URL is up.

    Args:
        url: The full URL to probe, including scheme.
        timeout: Seconds to wait before giving up.
    """
    ...

graph = AgentRuntime().build_graph(model=model, core_tools=[word_count],
                                   system_prompt="You are ...")

2 · The lifecycle class — for tools that want declared config, auto-registration, and persistence hooks (start→complete/error recorded in your datastore via ToolPersistence; the default is a no-op):

from pydantic import BaseModel
from visvoai.core.tools import BaseAgentTool, tool_config
from visvoai.core.results import ToolResult

class EchoArgs(BaseModel):
    text: str

@tool_config(is_core=True, routing_hint="Use to echo text back.")
class EchoTool(BaseAgentTool):
    name = "echo"
    description = "Echo the input back."
    args_schema = EchoArgs

    def _execute(self, tool_call_id: str, **kwargs):
        return ToolResult.success(self.name, kwargs["text"])

Pass the class (or an instance) straight into core_tools — execution runs through the full lifecycle. This is the same pattern the CLI and a hosted platform build their internal tools on.

3 · Anything LangChain — already have @tool functions or StructuredTools? They pass through untouched, and every LangChain integration ever written is usable as-is.

4 · MCP servers — out-of-process tools in any language; connect them at the consumer layer (the CLI ships this: visvoai mcp add ...).

Mix them in one list; as_tool / as_tools_map are exported if you need the normalization yourself:

from visvoai.core import as_tools_map
tools = [word_count, EchoTool, some_langchain_tool]
graph = AgentRuntime().build_graph(model=model, core_tools=tools,
                                   system_prompt="You are ...")

The extension seams

Want approval gates? A Postgres audit trail? Your own state fields? Each is one override — these are the same hooks our CLI and platform use:

Everything is subclass + inject; there is nothing to fork.

Seam Override to get
AgentRuntime._extend_graph() extra graph nodes — approval gates, background tasks, custom routers
AgentRuntime._build_agent_node() your own model-calling node (e.g. per-turn assembled system prompts)
AgentRuntime._get_checkpointer() durable graph state — a checkpointer is LangGraph's saved-state store, what gives the agent memory across turns
AgentRuntime._get_interrupt_nodes() human-in-the-loop interrupt points
RuntimeContext (subclass) your state carried to every tool — auth, sessions, registries
AgentState (TypedDict inheritance) + _get_state_class() your fields in the graph state
ToolPersistence (implement) tool-call records in your datastore
LLMPersistence (implement) per-call model usage/cost records

This is exactly how the two real consumers differ: the CLI overrides the agent node for per-turn context assembly; the hosted platform adds HITL and background-task nodes, a Postgres persistence pair, and an auth-carrying context — same runtime, no forks.

Migrating from LangChain

Incremental, by design: your existing @tool functions and StructuredTools pass into build_graph(core_tools=[...]) untouched, and any BaseChatModel you already construct works as the model. Migrate the loop first (keep your tools), then simplify tools to plain functions at your own pace. Nothing to rewrite on day one.

When not to use this

If you want hundreds of integrations, chains, and a batteries ecosystem, use LangChain/LangGraph directly — that's what they're for. visvoai-core is for when you're building a product on the loop and want the sharp edges (recursion deaths, runaway rounds, tool sprawl, lifecycle plumbing) already filed down.

Examples

examples/07_everything_together.py is a whole product in 180 lines — retrieval choosing tools, memory resolving "restart it", an audit row appearing in SQLite — and it runs with no API key. The examples ladder then takes each idea one file at a time, four of them keyless.

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

visvoai_core-0.4.0.tar.gz (33.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

visvoai_core-0.4.0-py3-none-any.whl (24.6 kB view details)

Uploaded Python 3

File details

Details for the file visvoai_core-0.4.0.tar.gz.

File metadata

  • Download URL: visvoai_core-0.4.0.tar.gz
  • Upload date:
  • Size: 33.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for visvoai_core-0.4.0.tar.gz
Algorithm Hash digest
SHA256 bf71c1d327372cb6c83a021ef55dac5277cc22291344bf10609163fd9750d6c4
MD5 5016f8711e487cd6353c8b7ba0065ef3
BLAKE2b-256 334900650b5e0d55b7b187c36b496009c95bd7a4864b26b66a467b7d2d407fd6

See more details on using hashes here.

Provenance

The following attestation bundles were made for visvoai_core-0.4.0.tar.gz:

Publisher: publish.yml on VisvoAI/visvoai

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file visvoai_core-0.4.0-py3-none-any.whl.

File metadata

  • Download URL: visvoai_core-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 24.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for visvoai_core-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ad8c8b78220ff310365e93efc9ba6ab3c8066dde0b3b508dbfceeb521a9b5e86
MD5 9fd77560414017a41dff80bde746adcf
BLAKE2b-256 75d4b9a3dd19a93be9dfad25f6412e86f3d7a5c16ebd27ff565051f8d557332f

See more details on using hashes here.

Provenance

The following attestation bundles were made for visvoai_core-0.4.0-py3-none-any.whl:

Publisher: publish.yml on VisvoAI/visvoai

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.4.0 This release

2 files

0.0.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page