GEO Content Optimizer
August 19, 2026 · View on GitHub
中文 | 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
| Capability | Description |
|---|---|
| 📄 Multi-format Document Parsing | PDF / Word / Excel / PPT / TXT — extract plain text in one click |
| 🔑 7-type Keyword Mining | Core 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 Generation | EE-A-T authority framework + ACES conclusion-first paradigm + 3-paragraph Hook opening + semantic entity density network |
| 🧹 8-dimension AI Trace Removal | Sentence structure / paragraph rhythm / word choice / emotional density / evidence source / perfection / personalization / keyword density — 3-level grading |
| 🛡️ Stage 5 Quality Gate | Extreme-word lint, brand traceability density, structural completeness, hallucination guard (claim source verification), optional LLM-as-Judge scoring |
| 📏 Length Auto-calibration | LLMs 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 Enrichment | Tavily / Bing RSS / DuckDuckGo 3-engine auto-switch, works in China |
| 📝 Word Document Export | Markdown → formatted Word (.docx), supporting headings / tables / lists / code blocks |
| 🧩 MCP Protocol Integration | One-line config to connect Claude Desktop / Claude Code / WorkBuddy and other AI tools |
| 📦 Smart Chunking | Auto-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
| Stage | Function | Core Method |
|---|---|---|
| Stage 0 | Web Search (optional) | Auto-extract key concepts, 3-engine parallel search enrichment, expand document material |
| Stage 1 | Document Clean & Slice | Filter noise, semantic chunking (paragraph/sentence boundaries), extract brand info; long texts chunked in parallel |
| Stage 2 | Keyword Extraction | 7 keyword types (schema-enforced via structured output) + priority formula (search volume × relevance × competition) 3-tier ranking |
| Stage 3 | Content Generation | EE-A-T authority framework + ACES conclusion-first + 7-layer writing architecture + semantic entity density + FAQ / citation-ready fact block |
| Stage 4 | AI Trace Removal | 8-dimension de-AI + 3-level grading (light / medium / deep) + self-check checklist |
| Stage 5 | Quality Gate | Extreme-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.batfor 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:
| Provider | Base URL | Model Examples |
|---|---|---|
| OpenAI | https://api.openai.com/v1 | gpt-4o |
| DeepSeek | https://api.deepseek.com | deepseek-chat |
| Doubao (ByteDance) | https://ark.cn-beijing.volces.com/api/v3 | doubao-seed-2-0-pro |
| Qwen (Alibaba) | https://dashscope.aliyuncs.com/compatible-mode/v1 | qwen-max |
| Kimi (Moonshot) | https://api.moonshot.cn/v1 | moonshot-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
| Parameter | Description |
|---|---|
--text "content" | Input text directly |
--file path | Input file (PDF/Word/Excel/PPT/TXT) |
--brand "info" | Supplementary brand information |
--search | Enable web search (adds 1-2 min) |
--docx | Export Word document (.docx) |
--output path | Specify Markdown output path |
--verbose | Show 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
| Endpoint | Method | Description |
|---|---|---|
/run | POST | Synchronous Agent execution |
/stream_run | POST | SSE streaming execution |
/cancel/{run_id} | POST | Cancel task |
/v1/chat/completions | POST | OpenAI-compatible endpoint |
/health | GET | Health check |
/graph_parameter | GET | Agent 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
envfield in MCP config to pass API Keys! Claude Code'senvoverrides the entire environment (including the essential WindowsPATH), causing Python to fail loading system DLLs with error code -32000. Configure API Keys in the project.envfile 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:
| Tool | Parameters | Description |
|---|---|---|
parse_document | file_path: str | Parse PDF / Word(.docx) / TXT / CSV / Excel(.xlsx) / PPT(.pptx) files (legacy .doc/.xls/.ppt not guaranteed) |
run_geo_pipeline | cleaned_text, brand_info="", enable_search=false, user_requirements="", output_docx="" | Execute 4-stage GEO pipeline |
web_search | query: str, max_results=5 | Web search |
search_and_enrich | text: str, brand_info="", max_queries=3 | Extract 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:
- Parse a length target from
user_requirements:≥1800字(minimum) /不超过2500字(maximum) /约1500字(around) /1800-2500字(range); - After Stage 4 (de-AI rewriting), measure the output with
len(text); - When it falls outside the tolerance band (default ±10%,
PIPELINE_LENGTH_TOLERANCE), run a targeted expand/shrink pass, up toPIPELINE_MAX_LENGTH_ATTEMPTS(default 2) rounds, stopping as soon as it converges; - 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 startupfor 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
| Variable | Required | Default | Description |
|---|---|---|---|
OPENAI_API_KEY | Yes | — | LLM API Key |
OPENAI_BASE_URL | No | https://api.openai.com/v1 | API endpoint |
OPENAI_MODEL_NAME | No | gpt-4o | Model name |
AGENT_TEMPERATURE | No | 0.7 | Agent temperature |
AGENT_MAX_TOKENS | No | 32768 | Agent max tokens |
PIPELINE_MAX_CHUNK_CHARS | No | 60000 | Max chars per chunk (semantic chunking) |
PIPELINE_LLM_MAX_RETRIES | No | 3 | LLM retries (transient network/rate-limit only) |
PIPELINE_LLM_TIMEOUT | No | 600 | Pipeline LLM call timeout (seconds) |
PIPELINE_LLM_MODEL | No | empty (same as OPENAI_MODEL_NAME) | Pipeline-specific model |
PIPELINE_MAX_CONCURRENCY | No | 4 | Parallelism for chunk processing / web search |
PIPELINE_MAX_INPUT_CHARS | No | 200000 | Upper bound (chars) on pipeline input; oversized text is truncated with a warning. 0 disables (token-cost control) |
PIPELINE_STAGE3_MAX_SLICES | No | 0 | Max slices embedded in the Stage-3 prompt (top-K truncation, lowers token cost). 0 = unlimited |
PIPELINE_LENGTH_TOLERANCE | No | 0.10 | Character-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_ATTEMPTS | No | 2 | Max 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_JUDGE | No | true | Enable Stage 5 LLM scoring (off = deterministic lint only, zero token cost, faster runs) |
PIPELINE_QUALITY_THRESHOLD | No | 80 | GEO 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_QUERIES | No | 3 | Max search queries extracted per text for web enrichment (lower = shorter web-search phase) |
PIPELINE_GATE_HARD_DATA | No | false | Optional 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_REWRITE | No | 0.8 / 0.4 | Stage 3 generation / Stage 4 rewrite temperature (set 0 for near-deterministic runs) |
PIPELINE_REQUIREMENT_SATISFACTION_THRESHOLD | No | 80 | User-requirement satisfaction pass line (dual-gate second-axis gate, active only when user_requirements is supplied) |
GEO_API_KEY | No | empty | HTTP write-endpoint auth (requires Authorization: Bearer; binds to 127.0.0.1 by default, prints a startup warning when unset) |
TAVILY_API_KEY | No | - | Tavily search key (omit = free engines) |
PGDATABASE_URL | No | — | PostgreSQL connection (enables persistence) |
S3_* | No | — | S3 storage config |
HTTP_PORT | No | 5000 | HTTP service port |
HTTP_HOST | No | 127.0.0.1 | HTTP bind address (localhost by default; set 0.0.0.0 + GEO_API_KEY to expose) |
MCP_SSE_HOST | No | 127.0.0.1 | MCP SSE bind address (localhost by default) |
HTTP_RATE_LIMIT_PER_MINUTE | No | 60 | Max requests per client IP per minute (write endpoints; 0 disables) |
MCP_SSE_PORT | No | 5001 | MCP SSE mode port |
Development Quality Gates
Install dev tooling with pip install -e ".[dev]", then:
| Tool | Command | Purpose |
|---|---|---|
| pytest + pytest-cov | pytest | Full 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) |
| ruff | ruff check src tests | Lint + import ordering |
| bandit | bandit -r src -ll | Static 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
| Layer | Technology |
|---|---|
| Agent Framework | LangChain + LangGraph (create_react_agent) |
| LLM Calls | ChatOpenAI (compatible with all OpenAI APIs) |
| Web API | FastAPI + Uvicorn |
| Web UI | Streamlit (optional) |
| MCP Protocol | FastMCP (connect to Claude etc.) |
| CLI | argparse + asyncio |
| Retry Mechanism | tenacity (exponential backoff + JSON self-repair) |
| Persistence | PostgreSQL (optional) + S3 (optional) |
Contributing
Contributions welcome!
- Fork this repo
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit changes (
git commit -m 'Add amazing feature') - Push the branch (
git push origin feature/amazing-feature) - 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.