{"slug": "how-i-replaced-llm-calls-with-coding-agent-calls-and-saved-money", "title": "How I replaced LLM calls with coding agent calls and saved money", "summary": "A developer proposes replacing LLM API calls with coding agent calls to reduce costs, citing that coding assistants like Codex, Cursor Pro, and GitHub Copilot Pro cost significantly less per million tokens than traditional LLM APIs. The developer provides a Python wrapper to integrate Claude Code CLI as a drop-in replacement for LLM calls, enabling the use of cheaper agent-based inference for building AI agents.", "body_md": "When building an AI agent, you need LLM calls. It can be done via a remote API or a local API, but either way you need to do it. Whether the agent is a simple conversational agent or a ReAct agent with a bunch of tools, whether it's using a complex graph or a simple RAG, it must be based on the concept of sending prompts to the language model.\n\nBut, what if we replace the language model with... another agent? Let's say we already have a smart agent with a bunch of tools that can handle complex problems. Why not use it to build a new agent on top of it? This way we can focus on the specific custom functionality we want to achieve, while already having the common functionalities covered by the underlying agent.\n\nYou might think this must be expensive. You get a better performance, so you have to pay for it, right? Well, not necessarily. The catch is that the coding assistants are actually surprisingly cheap when compared to API prices. They offer much more than raw LLM calls, they offer amazing agent functionality, but the cost is actually lower, and it's not a small difference. The cost of LLM calls per 1M tokens is usually between $2 and $7. For coding assistant subscriptions, it's a bit more tricky to calculate because you pay for monthly subscription, but it can be still converted to per 1M tokens cost, and from what LLM just told me it is around $0.08 to $2. That's a huge difference! And the complex agents are cheaper than raw LLM's!\n\naccording to ChatGPT:\n\n| Service | Cost per 1M tokens |\n|---|---|\n| Codex / ChatGPT coding plan | ~$0.08 |\n| Cursor Pro | ~$0.08–0.25 |\n| GitHub Copilot Pro | ~$0.10–0.30 |\n| Claude Pro / Claude Code | ~$0.74 |\n| GPT-5 API | ~$2.1 |\n| Claude Sonnet API | ~$4.2 |\n| Claude Opus API | ~$7.0 |\n\naccording to Claude:\n\n| Service | Cost per 1M tokens |\n|---|---|\n| Haiku 4.5 (API) | $1.80 |\n| Sonnet 5 (API, intro thru Aug 31 '26) | $3.60 |\n| Sonnet 5 (API, standard) | $5.40 |\n| Opus 4.8 (API) | $9.00 |\n| Fable 5 (API) | $18.00 |\n| Claude Code — Pro | ~$1.10–$2.15 (est.) |\n| Claude Code — Max 5x | ~$2.10–$4.15 (est.) |\n| Claude Code — Max 20x | ~$2–$4 (est.) |\n\nSo, why not use the coding agents to build new agents? The only problem is that they don't have API. But, this is a problem that can be solved by writing a simple wrapper to the agent that will communicate with it via CLI. We just have to install the agent on the machine from which we want to call it and use the wrapper. Simple and brilliant. Here's the code:\n\n```\n\"\"\"Claude Code CLI — complete integration module.\n\nProvides plain-text and structured LLM calls via the local `claude` binary,\nplus a LangChain-compatible adapter for use with make_llm().\n\nRequires the `claude` binary on PATH.\n\"\"\"\n\nimport json\nimport subprocess\nfrom typing import Generic, TypeVar\n\nfrom langchain_core.messages import HumanMessage, SystemMessage\nfrom pydantic import BaseModel\n\nT = TypeVar(\"T\", bound=BaseModel)\n\n_TIMEOUT = 120\n\n# ── Core subprocess layer ─────────────────────────────────────────────────────\n\ndef cli_call(system: str, user: str) -> str:\n    \"\"\"Run a plain-text prompt through the Claude CLI and return the response.\"\"\"\n    return _run(system=system, user=user)\n\ndef cli_call_structured(system: str, user: str, output_type: type[T]) -> T:\n    \"\"\"Run a structured prompt through the Claude CLI and parse into output_type.\"\"\"\n    schema = json.dumps(output_type.model_json_schema())\n    raw = _run(system=system, user=user, json_schema=schema)\n    return output_type.model_validate_json(raw)\n\ndef _run(system: str, user: str, json_schema: str | None = None) -> str:\n    cmd = [\n        \"claude\",\n        \"--print\",\n        \"--no-session-persistence\",\n        \"--tools\", \"\",\n    ]\n    if system:\n        cmd += [\"--system-prompt\", system]\n    if json_schema:\n        cmd += [\"--output-format\", \"json\", \"--json-schema\", json_schema]\n    cmd.append(user)\n\n    result = subprocess.run(\n        cmd, capture_output=True, text=True, encoding=\"utf-8\", timeout=_TIMEOUT\n    )\n    if result.returncode != 0:\n        stderr = (result.stderr or \"\").strip()\n        raise RuntimeError(f\"claude CLI exited {result.returncode}: {stderr}\")\n\n    stdout = (result.stdout or \"\").strip()\n    if not stdout:\n        raise RuntimeError(\"claude CLI returned empty output\")\n\n    if json_schema:\n        envelope = json.loads(stdout)\n        payload = envelope.get(\"result\", stdout)\n        return payload if isinstance(payload, str) else json.dumps(payload)\n\n    return stdout\n\n# ── LangChain-compatible adapter ──────────────────────────────────────────────\n\nclass CliResponse:\n    def __init__(self, content: str) -> None:\n        self.content = content\n\nclass ClaudeCliStructured(Generic[T]):\n    def __init__(self, output_type: type[T]) -> None:\n        self._type = output_type\n\n    def invoke(self, messages: list) -> T:\n        system, user = _extract_messages(messages)\n        return cli_call_structured(system=system, user=user, output_type=self._type)\n\nclass ClaudeCli:\n    \"\"\"LangChain-compatible adapter that delegates to the claude CLI binary.\"\"\"\n\n    def invoke(self, messages: list) -> CliResponse:\n        system, user = _extract_messages(messages)\n        return CliResponse(content=cli_call(system=system, user=user))\n\n    def with_structured_output(self, output_type: type[T]) -> ClaudeCliStructured[T]:\n        return ClaudeCliStructured(output_type)\n\ndef _extract_messages(messages: list) -> tuple[str, str]:\n    system = \"\"\n    user = \"\"\n    for m in messages:\n        if isinstance(m, SystemMessage):\n            system = m.content if isinstance(m.content, str) else \"\"\n        elif isinstance(m, HumanMessage):\n            user = m.content if isinstance(m.content, str) else \"\"\n    return system, user\n```\n\n", "url": "https://wpnews.pro/news/how-i-replaced-llm-calls-with-coding-agent-calls-and-saved-money", "canonical_source": "https://dev.to/popiol/how-i-replaced-llm-calls-with-coding-agent-calls-and-saved-money-10p7", "published_at": "2026-07-10 21:46:15+00:00", "updated_at": "2026-07-10 22:13:08.765323+00:00", "lang": "en", "topics": ["large-language-models", "ai-agents", "developer-tools", "ai-infrastructure"], "entities": ["Codex", "Cursor Pro", "GitHub Copilot Pro", "Claude Code", "Claude Pro", "GPT-5", "Claude Sonnet", "Claude Opus"], "alternates": {"html": "https://wpnews.pro/news/how-i-replaced-llm-calls-with-coding-agent-calls-and-saved-money", "markdown": "https://wpnews.pro/news/how-i-replaced-llm-calls-with-coding-agent-calls-and-saved-money.md", "text": "https://wpnews.pro/news/how-i-replaced-llm-calls-with-coding-agent-calls-and-saved-money.txt", "jsonld": "https://wpnews.pro/news/how-i-replaced-llm-calls-with-coding-agent-calls-and-saved-money.jsonld"}}