GEO Content Optimizer

August 19, 2026 · View on GitHub

Python 3.12+ License: MIT CI

中文 | English

Feed enterprise documents → auto-produce high-quality original articles optimized for AI model vector databases (Doubao, Kimi, DeepSeek, Qwen, GPT), with AI-generation traces removed.

GEO (Generative Engine Optimization) content optimization agent — cross-platform, zero platform dependency. Unlike traditional SEO targeting search engine crawlers, GEO targets AI summarizers and answer engines: making AI models "readable, willing to cite, and traceable."


Features

Core Capabilities

CapabilityDescription
📄 Multi-format Document ParsingPDF / Word / Excel / PPT / TXT — extract plain text in one click
🔑 7-type Keyword MiningCore search terms, long-tail scenario terms, geographic entity terms, audience tag terms, problem description terms, brand-specific terms, scenario trigger terms — schema-enforced (Pydantic), ranked by priority formula
✍️ 7-layer Architecture Content GenerationEE-A-T authority framework + ACES conclusion-first paradigm + 3-paragraph Hook opening + semantic entity density network
🧹 8-dimension AI Trace RemovalSentence structure / paragraph rhythm / word choice / emotional density / evidence source / perfection / personalization / keyword density — 3-level grading
🛡️ Stage 5 Quality GateExtreme-word lint, brand traceability density, structural completeness, hallucination guard (claim source verification), optional LLM-as-Judge scoring
📏 Length Auto-calibrationLLMs can't count — the pipeline measures length program-side and runs targeted expand/shrink passes until "≥1800字" / "约1500字" / "不超过2000字" requirements land inside the ±10% tolerance band (max 2 passes)
🌐 Web Search EnrichmentTavily / Bing RSS / DuckDuckGo 3-engine auto-switch, works in China
📝 Word Document ExportMarkdown → formatted Word (.docx), supporting headings / tables / lists / code blocks
🧩 MCP Protocol IntegrationOne-line config to connect Claude Desktop / Claude Code / WorkBuddy and other AI tools
📦 Smart ChunkingAuto-split long texts (≤60000 chars/chunk, 500-char overlap), merge before downstream

6 Usage Modes

CLI Interactive Mode  →  Terminal chat, great for quick trials
Command-line Direct   →  --file/--text params, great for batch scripts
Streamlit Web UI      →  Browser-based, great for non-technical users
HTTP API              →  FastAPI service, great for system integration
MCP Server            →  Connect to Claude and other AI tools, conversational invocation
DSH Plugin            →  Native DeepSeek Harness tools (mcp__geo__*), see docs/dsh-integration.md

Architecture

Input Document → [Stage 0 Web Search (optional)] → [Stage 1 Clean & Slice] → [Stage 2 Keyword Extraction] → [Stage 3 Content Generation] → [Stage 4 AI Trace Removal] → [Stage 5 Quality Gate] → Final Article + Word Document
StageFunctionCore Method
Stage 0Web Search (optional)Auto-extract key concepts, 3-engine parallel search enrichment, expand document material
Stage 1Document Clean & SliceFilter noise, semantic chunking (paragraph/sentence boundaries), extract brand info; long texts chunked in parallel
Stage 2Keyword Extraction7 keyword types (schema-enforced via structured output) + priority formula (search volume × relevance × competition) 3-tier ranking
Stage 3Content GenerationEE-A-T authority framework + ACES conclusion-first + 7-layer writing architecture + semantic entity density + FAQ / citation-ready fact block
Stage 4AI Trace Removal8-dimension de-AI + 3-level grading (light / medium / deep) + self-check checklist
Stage 5Quality GateExtreme-word lint + brand traceability density + structural completeness + hallucination guard + optional LLM-as-Judge scoring (advisory by default, does not block output; with strict=True a sub-threshold article is marked "❌ not publishable" at the top)

Quick Start

1. Install

# Clone the repo
git clone https://github.com/wangzhuo-coding/geo-content-optimizer.git
cd geo-content-optimizer

# Create virtual environment
python -m venv .venv
source .venv/bin/activate  # Linux/macOS
# .venv\Scripts\activate    # Windows

# Install core dependencies
pip install -e .

Optional features (install as needed):

pip install -e ".[mcp]"       # MCP Server (connect to Claude etc.)
pip install -e ".[webui]"     # Streamlit Web UI
pip install -e ".[postgres]"  # PostgreSQL persistence
pip install -e ".[s3]"        # S3 file storage
pip install -e ".[all]"       # All optional features

Windows users: Run setup.bat for one-click install. In China, use a mirror: pip install -e . -i https://pypi.tuna.tsinghua.edu.cn/simple

2. Configure API Key

cp .env.example .env  # Windows: copy .env.example .env

Edit .env and fill in your LLM API Key:

OPENAI_API_KEY=sk-xxx               # Your API Key (required)
OPENAI_BASE_URL=https://api.openai.com/v1   # API endpoint
OPENAI_MODEL_NAME=gpt-4o            # Model name

Compatible API providers:

ProviderBase URLModel Examples
OpenAIhttps://api.openai.com/v1gpt-4o
DeepSeekhttps://api.deepseek.comdeepseek-chat
Doubao (ByteDance)https://ark.cn-beijing.volces.com/api/v3doubao-seed-2-0-pro
Qwen (Alibaba)https://dashscope.aliyuncs.com/compatible-mode/v1qwen-max
Kimi (Moonshot)https://api.moonshot.cn/v1moonshot-v1-8k

3. Run

Mode 1: CLI Interactive Mode

python -m src          # Simplest
python src/cli.py      # Equivalent
run.bat                # Windows one-click

Mode 2: Command-line Direct Processing

# Input text, auto-save to output/ directory
python src/cli.py --text "Your document content..."

# Process file + brand info
python src/cli.py --file document.pdf --brand "Brand Name, Website URL"

# Enable web search + export Word document
python src/cli.py --file doc.pdf --search --docx

# Full features: file + brand + web search + Word export
python src/cli.py --file doc.pdf --brand "Huawei, https://www.huawei.com" --search --docx
ParameterDescription
--text "content"Input text directly
--file pathInput file (PDF/Word/Excel/PPT/TXT)
--brand "info"Supplementary brand information
--searchEnable web search (adds 1-2 min)
--docxExport Word document (.docx)
--output pathSpecify Markdown output path
--verboseShow detailed logs

Mode 3: Web UI

pip install -e ".[webui]"
streamlit run src/web_ui.py

Upload files, paste text, toggle web search and Word export, watch real-time progress in the browser.

Mode 4: HTTP API Service

python -m uvicorn src.main:app --host 127.0.0.1 --port 5000
EndpointMethodDescription
/runPOSTSynchronous Agent execution
/stream_runPOSTSSE streaming execution
/cancel/{run_id}POSTCancel task
/v1/chat/completionsPOSTOpenAI-compatible endpoint
/healthGETHealth check
/graph_parameterGETAgent graph parameters

Swagger docs: Visit http://127.0.0.1:5000/docs after launch.

Example call:

curl -X POST http://127.0.0.1:5000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"messages": [{"role": "user", "content": "Optimize this document..."}]}'

Mode 5: MCP Server (Connect to Claude and other AI tools)

pip install -e ".[mcp]"

# stdio mode (Claude Desktop / Claude Code / WorkBuddy)
python src/mcp_server.py

# SSE HTTP mode (Web clients)
python src/mcp_server.py --transport sse --port 5001

Configure Claude Desktop (%APPDATA%\Claude\claude_desktop_config.json):

Important: Do NOT use the env field in MCP config to pass API Keys! Claude Code's env overrides the entire environment (including the essential Windows PATH), causing Python to fail loading system DLLs with error code -32000. Configure API Keys in the project .env file instead — MCP Server reads it automatically on startup.

{
  "mcpServers": {
    "geo-content-optimizer": {
      "command": "python",
      "args": ["src/mcp_server.py"],
      "cwd": "/path/to/geo-content-optimizer"
    }
  }
}

Configure WorkBuddy (~/.workbuddy/mcp.json):

{
  "mcpServers": {
    "geo-content-optimizer": {
      "command": "/path/to/geo-content-optimizer/.venv/Scripts/python.exe",
      "args": ["src/mcp_server.py"],
      "cwd": "/path/to/geo-content-optimizer"
    }
  }
}

MCP Tools:

ToolParametersDescription
parse_documentfile_path: strParse PDF / Word(.docx) / TXT / CSV / Excel(.xlsx) / PPT(.pptx) files (legacy .doc/.xls/.ppt not guaranteed)
run_geo_pipelinecleaned_text, brand_info="", enable_search=false, user_requirements="", output_docx=""Execute 4-stage GEO pipeline
web_searchquery: str, max_results=5Web search
search_and_enrichtext: str, brand_info="", max_queries=3Extract concepts + search + merge

After configuration, invoke by chatting directly in Claude/WorkBuddy:

User: Optimize this Huawei Cloud whitepaper for me
Claude: → Calls parse_document to parse the file
        → Calls run_geo_pipeline to execute 4-stage pipeline
        → Returns the final article

Mode 6: DSH Plugin (native DeepSeek Harness tools)

This project is also a dsh-plugin: the repo ships a dsh-plugin/ package; once installed into a DeepSeek Harness web profile via dsh plugin add, the harness spawns the project's FastMCP server and registers its 4 tools as native model tools mcp__geo__parse_document / run_geo_pipeline / web_search / search_and_enrich (auto-reconnect on crash; long-task timeout preset to 10 minutes).

# Prereqs: install deps and point the working instance at YOUR clone (see dsh-plugin/README.md)
#   python -m venv .venv && .venv\Scripts\python -m pip install -e ".[mcp]"
#   scripts\dsh-plugin-setup.ps1   (Linux/macOS: bash scripts/dsh-plugin-setup.sh)

# Install the plugin (mutually exclusive with a direct mcp-geo row in profiles/web/cordis.patch.yml;
# replace <your-clone> with the actual path)
dsh plugin --profile web add <your-clone>/dsh-plugin
dsh web restart   # after restart, mcp__geo__* tools appear in new sessions

The companion "GEO 创作" agent preset is vendored under dsh-plugin/preset/; install notes, portability, and acceptance checks live in docs/dsh-integration.md and docs/dsh-validation.md. The working instance is chosen by the GEO_PROJECT_ROOT / GEO_PYTHON environment variables (pointed at your clone; startup fails loudly when they are missing instead of silently registering zero tools); API keys are read from the instance's .env (mcp_server.py calls load_dotenv itself).


User Requirements & Dual Gate (Optional)

run_geo_pipeline accepts a user_requirements parameter (content direction / target audience / word count / style / keywords / platform, etc.). When supplied:

  • Writing (Stage 3/4) reconciles user requirements via the three-tier priority: Tier 0 compliance/fact/black-hat red lines -> refuse and give a compliant alternative; Tier 1 GEO floor (EE-A-T/ACES/semantic entity density) -> seek win-win, preserve GEO when forced to choose; Tier 2 other requirements -> maximize satisfaction within constraints
  • Scoring (Stage 5) adds an independent second axis "user-requirement satisfaction" (0-100%); together with the 100-point GEO baseline it forms a dual gate: GEO passes AND satisfaction >= 80% to be publishable; compliance-conflicting requirements are refused + given alternatives and excluded from the satisfaction rate (advisory by default; with run_geo_pipeline(strict=True) a sub-threshold article is explicitly flagged)

Omitting user_requirements leaves behavior unchanged (second axis inactive), consistent with the geo-writer / geo-scorer skills in the ecosystem.

Length Auto-calibration (R3)

LLMs have no stable sense of character counts — the same "≥1800字" request can produce 1200~4700 chars. The pipeline therefore does not trust the model's self-reported length and calibrates program-side instead:

  1. Parse a length target from user_requirements: ≥1800字 (minimum) / 不超过2500字 (maximum) / 约1500字 (around) / 1800-2500字 (range);
  2. After Stage 4 (de-AI rewriting), measure the output with len(text);
  3. When it falls outside the tolerance band (default ±10%, PIPELINE_LENGTH_TOLERANCE), run a targeted expand/shrink pass, up to PIPELINE_MAX_LENGTH_ATTEMPTS (default 2) rounds, stopping as soon as it converges;
  4. If it still misses after the cap, log honestly and accept the residual deviation — no infinite loop.

With no length requirement present, this step is skipped entirely (behavior identical to the previous version).

Runtime Tuning

A single run = 5 sequential LLM stages + optional LLM judge + optional web search. To make runs faster:

  • PIPELINE_QUALITY_JUDGE=false: skip the LLM judge (deterministic lint only, saves one LLM round-trip);
  • PIPELINE_SEARCH_MAX_QUERIES=1: extract only 1 search keyword for web enrichment (default 3);
  • PIPELINE_MAX_LENGTH_ATTEMPTS=1: cap length calibration at 1 correction pass (default 2).

Structured-Output Degradation

If the log shows with_structured_output failed ... response_format type is unavailable now, your model/endpoint does not support native JSON-Schema structured output. The pipeline automatically degrades to "free-form text + Pydantic repair parsing" (functional, but Stage 1/2 schema enforcement is weaker). Prefer a model or OpenAI-compatible endpoint that supports response_format=json_schema.

Long-running Deployment & Self-Healing (R7 B6)

By default the MCP server is spawned per-session by the client (Claude Code, stdio) — no daemon needed. For long-running deployment (servers/dev boxes), use a process supervisor for auto-restart on crash:

  • PM2 (cross-platform): npm i -g pm2 && pm2 start scripts/mcp_pm2.config.js (config: autorestart + exponential backoff + memory-limit restart; pm2 save && pm2 startup for boot persistence). Windows uses .venv/Scripts/python.exe, Linux/macOS change to .venv/bin/python (see comments in the config file).
  • systemd (Linux): sudo cp scripts/mcp_mcp_server.service /etc/systemd/system/ && sudo systemctl enable --now geo-mcp-server (Restart=always, restarts 3s after crash).

Note: after a -32000 Connection closed, auto-reconnect is a client-side behavior (Claude Code retries); server-side responsiveness during long tasks is handled in code via async + asyncio.to_thread (event loop never blocks).

Environment Variables

VariableRequiredDefaultDescription
OPENAI_API_KEYYesLLM API Key
OPENAI_BASE_URLNohttps://api.openai.com/v1API endpoint
OPENAI_MODEL_NAMENogpt-4oModel name
AGENT_TEMPERATURENo0.7Agent temperature
AGENT_MAX_TOKENSNo32768Agent max tokens
PIPELINE_MAX_CHUNK_CHARSNo60000Max chars per chunk (semantic chunking)
PIPELINE_LLM_MAX_RETRIESNo3LLM retries (transient network/rate-limit only)
PIPELINE_LLM_TIMEOUTNo600Pipeline LLM call timeout (seconds)
PIPELINE_LLM_MODELNoempty (same as OPENAI_MODEL_NAME)Pipeline-specific model
PIPELINE_MAX_CONCURRENCYNo4Parallelism for chunk processing / web search
PIPELINE_MAX_INPUT_CHARSNo200000Upper bound (chars) on pipeline input; oversized text is truncated with a warning. 0 disables (token-cost control)
PIPELINE_STAGE3_MAX_SLICESNo0Max slices embedded in the Stage-3 prompt (top-K truncation, lowers token cost). 0 = unlimited
PIPELINE_LENGTH_TOLERANCENo0.10Character-count calibration tolerance (±10%). When a length requirement is detected in user_requirements (e.g. "≥1800字" / "约1500字"), the pipeline measures length program-side and runs targeted expand/shrink passes until the output lands in the band
PIPELINE_MAX_LENGTH_ATTEMPTSNo2Max correction passes for length calibration (LLMs can't count; converge via program-side measurement + targeted correction, then accept residual deviation with a warning)
PIPELINE_QUALITY_JUDGENotrueEnable Stage 5 LLM scoring (off = deterministic lint only, zero token cost, faster runs)
PIPELINE_QUALITY_THRESHOLDNo80GEO baseline pass line (default 80, aligned with the project's GEO≥80 publish rule; wired into the judge prompt, and used as the dual-gate GEO gate)
PIPELINE_SEARCH_MAX_QUERIESNo3Max search queries extracted per text for web enrichment (lower = shorter web-search phase)
PIPELINE_GATE_HARD_DATANofalseOptional gate: flag when significant source numbers (>=1000) never appear in the article ("hard-data blurring"; off by default to avoid false positives)
PIPELINE_TEMPERATURE_GEN / PIPELINE_TEMPERATURE_REWRITENo0.8 / 0.4Stage 3 generation / Stage 4 rewrite temperature (set 0 for near-deterministic runs)
PIPELINE_REQUIREMENT_SATISFACTION_THRESHOLDNo80User-requirement satisfaction pass line (dual-gate second-axis gate, active only when user_requirements is supplied)
GEO_API_KEYNoemptyHTTP write-endpoint auth (requires Authorization: Bearer; binds to 127.0.0.1 by default, prints a startup warning when unset)
TAVILY_API_KEYNo-Tavily search key (omit = free engines)
PGDATABASE_URLNoPostgreSQL connection (enables persistence)
S3_*NoS3 storage config
HTTP_PORTNo5000HTTP service port
HTTP_HOSTNo127.0.0.1HTTP bind address (localhost by default; set 0.0.0.0 + GEO_API_KEY to expose)
MCP_SSE_HOSTNo127.0.0.1MCP SSE bind address (localhost by default)
HTTP_RATE_LIMIT_PER_MINUTENo60Max requests per client IP per minute (write endpoints; 0 disables)
MCP_SSE_PORTNo5001MCP SSE mode port

Development Quality Gates

Install dev tooling with pip install -e ".[dev]", then:

ToolCommandPurpose
pytest + pytest-covpytestFull test suite + coverage report (currently --cov-fail-under=65 regression floor; ECC target 80% is a follow-up once web_ui callbacks / main.py HTTP edges are covered)
ruffruff check src testsLint + import ordering
banditbandit -r src -llStatic security scan (medium+ severity only)

These three steps run automatically on every push / PR via GitHub Actions (.github/workflows/ci.yml).


Project Structure

geo-content-optimizer/
├── config/
│   └── agent_llm_config.json      # Agent System Prompt + model config
├── scripts/                        # Run scripts (Linux/macOS)
├── src/
│   ├── __main__.py                 # python -m src entry
│   ├── main.py                     # FastAPI HTTP service
│   ├── cli.py                      # CLI interactive mode
│   ├── mcp_server.py               # MCP Server (connect to Claude etc.)
│   ├── web_ui.py                   # Streamlit Web UI
│   ├── agents/
│   │   └── agent.py                # Agent build (LangGraph) + AgentState
│   ├── tools/
│   │   ├── geo_pipeline.py         # Core: 4-stage pipeline + smart chunking + Word export
│   │   └── web_search.py           # Web search (Tavily/Bing/DuckDuckGo)
│   ├── utils/
│   │   ├── file/
│   │   │   └── file.py             # File parsing (PDF/Word/Excel/PPT/TXT)
│   │   └── docx_export.py          # Markdown → Word document conversion
│   └── storage/
│       ├── memory/                 # In-memory persistence (default)
│       ├── database/               # PostgreSQL (optional)
│       └── s3/                     # S3 storage (optional)
├── run.bat                         # Windows one-click launch
├── setup.bat                       # Windows one-click install
├── dsh-plugin/                     # DSH plugin package (dsh plugin add; includes preset/ copy)
├── docs/                           # Docs: dsh-integration.md (integration) / dsh-validation.md (validation flow)
├── scripts/                        # incl. sync_dsh_instance.ps1 (DSH working-instance sync)
├── .env.example                    # Environment variable template
├── pyproject.toml                  # Dependency config
├── LICENSE                         # MIT License
└── README.md

Tech Stack

LayerTechnology
Agent FrameworkLangChain + LangGraph (create_react_agent)
LLM CallsChatOpenAI (compatible with all OpenAI APIs)
Web APIFastAPI + Uvicorn
Web UIStreamlit (optional)
MCP ProtocolFastMCP (connect to Claude etc.)
CLIargparse + asyncio
Retry Mechanismtenacity (exponential backoff + JSON self-repair)
PersistencePostgreSQL (optional) + S3 (optional)

Contributing

Contributions welcome!

  1. Fork this repo
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit changes (git commit -m 'Add amazing feature')
  4. Push the branch (git push origin feature/amazing-feature)
  5. Create a Pull Request

Dev environment setup:

python -m venv .venv
pip install -e ".[all]"
pip install pytest pytest-asyncio

Changelog

See CHANGELOG.md for the full release history.

License

MIT License — Free to use, modify, and distribute.