Интеграция Copilot-подобного ассистента для IDE
AI-ассистент для IDE — это не просто автодополнение на стероидах. Это система, которая держит в контексте весь проект: открытые файлы, историю изменений, схему БД, тесты. Правильно построенный ассистент понимает, что вы пишете функцию регистрации пользователя в Django-проекте с PostgreSQL, и предлагает код, совместимый с вашими моделями и конвенциями — а не абстрактный пример из Stack Overflow.
Архитектура IDE-ассистента
Полноценный Copilot-подобный ассистент состоит из нескольких слоёв:
Context Collector — собирает релевантный контекст: текущий файл, импорты, связанные файлы, cursor position, выделенный код, clipboard.
LSP Bridge — взаимодействует с Language Server Protocol для получения AST, типов, определений. Tree-sitter позволяет парсить код в AST без запуска компилятора.
Retrieval Engine — семантический поиск по кодовой базе. Embeddings для кода (CodeBERT, text-embedding-3-small) + векторное хранилище.
LLM Gateway — маршрутизация запросов: быстрая модель для inline completion, мощная для chat/refactoring.
Response Renderer — форматирование вывода: diff для рефакторинга, ghost text для completion, markdown для chat.
Continue.dev — open-source основа
Continue.dev — наиболее зрелая open-source альтернатива GitHub Copilot. Поддерживает VS Code и JetBrains, конфигурируется через ~/.continue/config.json.
{
"models": [
{
"title": "Claude 3.5 Sonnet",
"provider": "anthropic",
"model": "claude-sonnet-4-5",
"apiKey": "$ANTHROPIC_API_KEY"
},
{
"title": "Ollama Qwen2.5-Coder",
"provider": "ollama",
"model": "qwen2.5-coder:7b",
"apiBase": "http://localhost:11434"
}
],
"tabAutocompleteModel": {
"title": "Autocomplete",
"provider": "ollama",
"model": "qwen2.5-coder:1.5b"
},
"contextProviders": [
{"name": "code", "params": {}},
{"name": "docs", "params": {}},
{"name": "diff", "params": {}},
{"name": "terminal", "params": {}},
{"name": "problems", "params": {}},
{"name": "folder", "params": {}},
{"name": "codebase", "params": {}}
],
"slashCommands": [
{"name": "edit", "description": "Edit highlighted code"},
{"name": "comment", "description": "Write comments for the code"},
{"name": "tests", "description": "Write unit tests"},
{"name": "share", "description": "Export the chat session"}
]
}
Ключевая особенность: tabAutocompleteModel использует быструю локальную модель (1.5B параметров), а чат — мощную облачную. Latency inline completion: 80–150 мс на Qwen2.5-Coder 1.5B через Ollama.
Кастомный контекст-провайдер
Continue.dev позволяет писать кастомные context providers для специфических источников данных:
// ~/.continue/config.ts
import { ContinueConfig, IContextProvider, ContextProviderDescription } from "@continuedev/core";
class DatabaseSchemaProvider implements IContextProvider {
get description(): ContextProviderDescription {
return {
title: "db",
displayTitle: "Database Schema",
description: "Current database schema",
type: "normal",
};
}
async getContextItems(query: string, extras: any) {
const schema = await fetchDatabaseSchema(); // ваш API
return [{
name: "Database Schema",
description: "Current DB schema",
content: schema,
}];
}
}
class JiraContextProvider implements IContextProvider {
get description(): ContextProviderDescription {
return {
title: "jira",
displayTitle: "Jira Ticket",
description: "Fetch Jira ticket by ID",
type: "query",
};
}
async getContextItems(query: string, extras: any) {
// query = "PROJ-123"
const ticket = await fetchJiraTicket(query);
return [{
name: `Jira ${query}`,
description: ticket.summary,
content: `**${ticket.summary}**\n\n${ticket.description}\n\nАcceptance Criteria:\n${ticket.acceptance_criteria}`,
}];
}
}
export function modifyConfig(config: ContinueConfig): ContinueConfig {
config.contextProviders = [
...(config.contextProviders || []),
new DatabaseSchemaProvider(),
new JiraContextProvider(),
];
return config;
}
Inline Completion через Language Server Protocol
Для встраивания в любой LSP-совместимый редактор (Neovim, Emacs, Helix):
from pygls.server import LanguageServer
from lsprotocol.types import (
TEXT_DOCUMENT_COMPLETION,
CompletionParams,
CompletionList,
CompletionItem,
CompletionItemKind,
)
from anthropic import Anthropic
import asyncio
server = LanguageServer("ai-completion-server", "v0.1")
anthropic_client = Anthropic()
@server.feature(TEXT_DOCUMENT_COMPLETION)
async def completions(params: CompletionParams):
document = server.workspace.get_document(params.text_document.uri)
# Получаем контекст: 50 строк до курсора
lines = document.lines
cursor_line = params.position.line
prefix = "\n".join(lines[max(0, cursor_line - 50):cursor_line + 1])
suffix = "\n".join(lines[cursor_line + 1:cursor_line + 10])
# Fill-in-the-middle prompt
prompt = f"<fim_prefix>{prefix}<fim_suffix>{suffix}<fim_middle>"
# Используем быструю модель для completion
response = anthropic_client.messages.create(
model="claude-haiku-4-5",
max_tokens=150,
messages=[{
"role": "user",
"content": f"Complete this code (return only the completion, no explanation):\n{prompt}"
}]
)
completion_text = response.content[0].text.strip()
return CompletionList(
is_incomplete=False,
items=[CompletionItem(
label=completion_text[:50] + "..." if len(completion_text) > 50 else completion_text,
kind=CompletionItemKind.Snippet,
insert_text=completion_text,
detail="AI Suggestion",
)]
)
if __name__ == "__main__":
server.start_io()
Codebase Indexing с Tree-sitter
Семантический поиск по кодовой базе — основа контекстно-зависимых подсказок:
from tree_sitter import Language, Parser
from tree_sitter_languages import get_language, get_parser
from openai import OpenAI
import chromadb
from pathlib import Path
client = OpenAI()
chroma_client = chromadb.PersistentClient(path="./.codebase_index")
collection = chroma_client.get_or_create_collection("code_chunks")
def extract_functions(file_path: str, language: str) -> list[dict]:
"""Извлекает функции/методы через Tree-sitter AST"""
parser = get_parser(language)
with open(file_path) as f:
source = f.read()
tree = parser.parse(source.encode())
# Tree-sitter query для Python функций
lang = get_language(language)
query = lang.query("""
(function_definition
name: (identifier) @func_name
body: (block) @func_body) @func_def
(class_definition
name: (identifier) @class_name
body: (block) @class_body) @class_def
""")
captures = query.captures(tree.root_node)
functions = []
for node, capture_name in captures:
if capture_name == "func_def":
func_text = source[node.start_byte:node.end_byte]
functions.append({
"file": file_path,
"type": "function",
"code": func_text,
"start_line": node.start_point[0],
})
return functions
def index_codebase(project_root: str):
"""Индексирует всю кодовую базу"""
project = Path(project_root)
all_chunks = []
for py_file in project.rglob("*.py"):
if "migrations" in str(py_file) or "__pycache__" in str(py_file):
continue
chunks = extract_functions(str(py_file), "python")
all_chunks.extend(chunks)
# Batch embedding
batch_size = 100
for i in range(0, len(all_chunks), batch_size):
batch = all_chunks[i:i + batch_size]
response = client.embeddings.create(
model="text-embedding-3-small",
input=[chunk["code"][:2000] for chunk in batch]
)
collection.add(
ids=[f"{c['file']}:{c['start_line']}" for c in batch],
embeddings=[e.embedding for e in response.data],
documents=[c["code"] for c in batch],
metadatas=[{"file": c["file"], "line": c["start_line"]} for c in batch],
)
print(f"Indexed {len(all_chunks)} code chunks")
def find_relevant_code(query: str, k: int = 5) -> list[str]:
"""Находит похожий код для контекста"""
response = client.embeddings.create(
model="text-embedding-3-small",
input=query
)
results = collection.query(
query_embeddings=[response.data[0].embedding],
n_results=k,
)
return results["documents"][0]
Chat-режим с проектным контекстом
class IDEChatAssistant:
"""Полноценный chat-ассистент с проектным контекстом"""
def __init__(self, project_root: str):
self.project_root = project_root
self.client = Anthropic()
index_codebase(project_root)
async def chat(
self,
user_message: str,
current_file: str,
selected_code: str = None,
conversation_history: list = None,
) -> str:
# Собираем контекст
context_parts = []
# Текущий файл
if current_file:
with open(current_file) as f:
file_content = f.read()
context_parts.append(f"## Текущий файл: {current_file}\n```python\n{file_content[:3000]}\n```")
# Выделенный код
if selected_code:
context_parts.append(f"## Выделенный код\n```python\n{selected_code}\n```")
# Похожий код из кодовой базы
relevant = find_relevant_code(user_message, k=3)
if relevant:
context_parts.append("## Похожий код из проекта\n" + "\n\n".join(relevant))
system_prompt = f"""Ты — опытный инженер, работающий над проектом.
{chr(10).join(context_parts)}
Правила:
- Код пиши в стиле существующей кодовой базы
- Используй те же зависимости, что уже есть в проекте
- Объясняй архитектурные решения кратко
- Если изменяешь существующий код — показывай diff"""
messages = conversation_history or []
messages.append({"role": "user", "content": user_message})
response = self.client.messages.create(
model="claude-sonnet-4-5",
max_tokens=4096,
system=system_prompt,
messages=messages,
)
return response.content[0].text
Практический кейс: внедрение в команду из 12 разработчиков
Стартовое состояние: команда использовала GitHub Copilot ($19/мес на разработчика), жаловалась на нерелевантные подсказки — Copilot не знал внутренних паттернов Django-проекта с 800+ моделями.
Решение: Continue.dev + локальный Ollama для autocomplete + Claude через API для chat/refactoring + кастомный context provider с индексом кодовой базы.
Инфраструктура: сервер с RTX 4090 (Qwen2.5-Coder 7B для autocomplete), API Claude для сложных запросов.
Результаты через 2 месяца:
- Принятие inline suggestions: 23% (Copilot) → 41% (кастомный)
- Среднее время написания типового CRUD endpoint: 52 мин → 31 мин
- Задачи типа "написать тест для этой функции": 100% ручные → 70% автоматические
- Стоимость: $228/мес (Copilot) → ~$85/мес (Ollama server amortized + Claude API)
Ключевой фактор улучшения acceptance rate: context provider с индексом кодовой базы давал модели реальные примеры из проекта, а не абстрактный код.
Локальные модели для completion
Для команд с требованиями к конфиденциальности кода — полностью локальный стек:
| Модель | Размер | Latency (RTX 3080) | Качество |
|---|---|---|---|
| Qwen2.5-Coder 1.5B | 1.5B | 50–80 мс | Базовое |
| Qwen2.5-Coder 7B | 7B | 150–250 мс | Хорошее |
| DeepSeek-Coder 6.7B | 6.7B | 140–230 мс | Хорошее |
| CodeLlama 13B | 13B | 350–500 мс | Высокое |
Для inline completion критична latency < 200 мс — пользователь замечает задержку. Поэтому для FIM (fill-in-the-middle) используют модели до 7B.
Сроки
- Continue.dev + конфигурация моделей + базовые context providers: 2–3 дня
- Кастомные context providers (БД, Jira, документация): 1 неделя
- Индексирование кодовой базы + семантический поиск: 1–2 недели
- LSP-сервер для нестандартного редактора: 2–3 недели
- Итого с онбордингом команды: 3–5 недель







