Введение в GraphQL и Graphene
GraphQL — это современный язык запросов к API и среда выполнения, разработанные Facebook в 2012 году для решения проблем традиционных REST API. Graphene — это мощная Python-библиотека, предоставляющая полную реализацию GraphQL-спецификации с поддержкой типизированных схем, мутаций и интеграции с популярными ORM-системами.
Что такое GraphQL и зачем он нужен
GraphQL представляет собой декларативный подход к получению данных, где клиент точно описывает, какие данные ему нужны. В отличие от REST, где сервер определяет структуру ответа, GraphQL позволяет клиенту самостоятельно формировать запросы и получать именно те данные, которые требуются.
Ключевые преимущества GraphQL перед REST
Единая точка входа
GraphQL использует один URL-эндпоинт для всех операций, что упрощает архитектуру API и устраняет необходимость версионирования через URL.
Гибкость запросов
Клиент самостоятельно определяет структуру ответа, запрашивая только необходимые поля и связанные данные.
Устранение избыточности данных
Проблемы over-fetching (получение лишних данных) и under-fetching (недостаток данных) решаются на уровне запроса.
Объединение связанных данных
Возможность получить данные из нескольких связанных сущностей в одном запросе, что снижает количество обращений к серверу.
Строгая типизация
Схема GraphQL статически типизирована, что обеспечивает валидацию запросов и автодополнение в IDE.
Автоматическая документация
Схема служит живой документацией API, доступной через интроспекцию.
Установка и настройка Graphene
Базовая установка
pip install graphene
Интеграция с веб-фреймворками
Для Flask:
pip install flask-graphql
Для Django:
pip install graphene-django
Для FastAPI:
pip install starlette-graphene3
Интеграция с базами данных
Для SQLAlchemy:
pip install graphene-sqlalchemy
Для Django ORM:
pip install graphene-django
Создание базового GraphQL-сервера
Пример с Flask
from flask import Flask
from flask_graphql import GraphQLView
import graphene
app = Flask(__name__)
class Query(graphene.ObjectType):
hello = graphene.String(name=graphene.String(default_value="мир"))
def resolve_hello(self, info, name):
return f"Привет, {name}!"
schema = graphene.Schema(query=Query)
app.add_url_rule('/graphql', view_func=GraphQLView.as_view(
'graphql',
schema=schema,
graphiql=True # Включает GraphiQL интерфейс
))
if __name__ == '__main__':
app.run(debug=True)
Пример с Django
# settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'graphene_django',
'myapp',
]
GRAPHENE = {
'SCHEMA': 'myproject.schema.schema'
}
# urls.py
from django.urls import path
from graphene_django.views import GraphQLView
urlpatterns = [
path('graphql/', GraphQLView.as_view(graphiql=True)),
]
Определение типов и схем в Graphene
Создание пользовательских типов
import graphene
class User(graphene.ObjectType):
id = graphene.ID()
name = graphene.String()
email = graphene.String()
age = graphene.Int()
is_active = graphene.Boolean()
# Вычисляемые поля
full_name = graphene.String()
def resolve_full_name(self, info):
return f"{self.first_name} {self.last_name}"
class Post(graphene.ObjectType):
id = graphene.ID()
title = graphene.String()
content = graphene.String()
author = graphene.Field(User)
created_at = graphene.DateTime()
Определение схемы запросов
class Query(graphene.ObjectType):
user = graphene.Field(User, id=graphene.ID(required=True))
users = graphene.List(User, limit=graphene.Int(default_value=10))
def resolve_user(self, info, id):
# Логика получения пользователя по ID
return User(id=id, name="Иван Петров", email="ivan@example.com")
def resolve_users(self, info, limit):
# Логика получения списка пользователей
return [User(id=i, name=f"Пользователь {i}") for i in range(1, limit+1)]
Запросы (Queries): структура и обработка
Простые запросы
{
user(id: "1") {
id
name
email
}
}
Запросы с алиасами
{
firstUser: user(id: "1") {
name
}
secondUser: user(id: "2") {
name
}
}
Вложенные запросы
class User(graphene.ObjectType):
id = graphene.ID()
name = graphene.String()
posts = graphene.List(lambda: Post)
def resolve_posts(self, info):
# Получение постов пользователя
return [Post(title="Первый пост"), Post(title="Второй пост")]
{
user(id: "1") {
name
posts {
title
content
}
}
}
Мутации (Mutations): изменение данных
Создание мутации
class CreateUser(graphene.Mutation):
class Arguments:
name = graphene.String(required=True)
email = graphene.String(required=True)
# Поля возвращаемого результата
ok = graphene.Boolean()
user = graphene.Field(User)
errors = graphene.List(graphene.String)
def mutate(self, info, name, email):
# Валидация
if not email or '@' not in email:
return CreateUser(ok=False, errors=["Некорректный email"])
# Создание пользователя
user = User(name=name, email=email)
return CreateUser(ok=True, user=user)
class UpdateUser(graphene.Mutation):
class Arguments:
id = graphene.ID(required=True)
name = graphene.String()
email = graphene.String()
user = graphene.Field(User)
def mutate(self, info, id, name=None, email=None):
# Логика обновления пользователя
user = User(id=id, name=name, email=email)
return UpdateUser(user=user)
class Mutation(graphene.ObjectType):
create_user = CreateUser.Field()
update_user = UpdateUser.Field()
Выполнение мутации
mutation {
createUser(name: "Иван Петров", email: "ivan@example.com") {
ok
user {
id
name
email
}
errors
}
}
Работа с аргументами в запросах и мутациях
Типы аргументов
class Query(graphene.ObjectType):
users = graphene.List(
User,
first=graphene.Int(default_value=10),
skip=graphene.Int(default_value=0),
search=graphene.String(),
is_active=graphene.Boolean(default_value=True)
)
def resolve_users(self, info, first, skip, search=None, is_active=True):
# Логика фильтрации и пагинации
users = [] # Получение из базы данных
if search:
users = [u for u in users if search.lower() in u.name.lower()]
if is_active is not None:
users = [u for u in users if u.is_active == is_active]
return users[skip:skip+first]
Использование InputObjectType
class UserInput(graphene.InputObjectType):
name = graphene.String(required=True)
email = graphene.String(required=True)
age = graphene.Int()
class CreateUserWithInput(graphene.Mutation):
class Arguments:
user_data = UserInput(required=True)
user = graphene.Field(User)
def mutate(self, info, user_data):
user = User(**user_data)
return CreateUserWithInput(user=user)
Резолверы (resolvers): логика обработки данных
Стандартные резолверы
class User(graphene.ObjectType):
id = graphene.ID()
name = graphene.String()
email = graphene.String()
posts_count = graphene.Int()
def resolve_posts_count(self, info):
# Подсчет количества постов пользователя
return len(getattr(self, 'posts', []))
def resolve_email(self, info):
# Проверка прав доступа
user = info.context.get('user')
if user and user.id == self.id:
return self.email
return None # Скрыть email для других пользователей
Асинхронные резолверы
import asyncio
class Query(graphene.ObjectType):
user = graphene.Field(User, id=graphene.ID())
async def resolve_user(self, info, id):
# Асинхронное получение данных
await asyncio.sleep(0.1) # Имитация запроса к базе данных
return User(id=id, name="Асинхронный пользователь")
Подключение к базе данных
Интеграция с SQLAlchemy
from sqlalchemy import create_engine, Column, Integer, String, DateTime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from graphene_sqlalchemy import SQLAlchemyObjectType
Base = declarative_base()
class UserModel(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String)
email = Column(String)
created_at = Column(DateTime)
class User(SQLAlchemyObjectType):
class Meta:
model = UserModel
interfaces = (graphene.relay.Node,)
class Query(graphene.ObjectType):
users = graphene.List(User)
def resolve_users(self, info):
query = User.get_query(info)
return query.all()
Интеграция с Django ORM
from django.db import models
from graphene_django import DjangoObjectType
class UserModel(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField()
created_at = models.DateTimeField(auto_now_add=True)
class User(DjangoObjectType):
class Meta:
model = UserModel
fields = ("id", "name", "email", "created_at")
posts_count = graphene.Int()
def resolve_posts_count(self, info):
return self.posts.count()
Работа с вложенными объектами и связями
Определение связей
class Post(graphene.ObjectType):
id = graphene.ID()
title = graphene.String()
content = graphene.String()
author = graphene.Field(lambda: User)
tags = graphene.List(lambda: Tag)
def resolve_author(self, info):
return User(id=self.author_id, name="Автор поста")
def resolve_tags(self, info):
return [Tag(name="Python"), Tag(name="GraphQL")]
class User(graphene.ObjectType):
id = graphene.ID()
name = graphene.String()
posts = graphene.List(Post)
def resolve_posts(self, info):
return [Post(id=1, title="Первый пост", author_id=self.id)]
Решение проблемы N+1 запросов
from promise import Promise
from promise.dataloader import DataLoader
class UserLoader(DataLoader):
def batch_load_fn(self, user_ids):
# Загрузка всех пользователей одним запросом
users = UserModel.objects.filter(id__in=user_ids)
user_map = {user.id: user for user in users}
return Promise.resolve([user_map.get(user_id) for user_id in user_ids])
class Post(graphene.ObjectType):
author = graphene.Field(User)
def resolve_author(self, info):
return info.context['user_loader'].load(self.author_id)
Использование Graphene-Django
Настройка проекта
# settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'graphene_django',
'myapp',
]
GRAPHENE = {
'SCHEMA': 'myproject.schema.schema',
'MIDDLEWARE': [
'graphene_django.debug.DjangoDebugMiddleware',
],
}
Создание схемы
# schema.py
import graphene
from graphene_django import DjangoObjectType
from .models import User, Post
class UserType(DjangoObjectType):
class Meta:
model = User
fields = "__all__"
class PostType(DjangoObjectType):
class Meta:
model = Post
fields = "__all__"
class Query(graphene.ObjectType):
users = graphene.List(UserType)
posts = graphene.List(PostType)
def resolve_users(self, info):
return User.objects.all()
def resolve_posts(self, info):
return Post.objects.select_related('author').all()
schema = graphene.Schema(query=Query)
Интеграция с Flask и FastAPI
Flask с GraphQL
from flask import Flask, request, jsonify
from flask_graphql import GraphQLView
import graphene
app = Flask(__name__)
class Query(graphene.ObjectType):
hello = graphene.String()
def resolve_hello(self, info):
user = info.context.get('user')
return f"Привет, {user.name if user else 'гость'}!"
schema = graphene.Schema(query=Query)
def get_context():
return {'user': getattr(request, 'user', None)}
app.add_url_rule('/graphql', view_func=GraphQLView.as_view(
'graphql',
schema=schema,
graphiql=True,
get_context=get_context
))
FastAPI с GraphQL
from fastapi import FastAPI
from starlette_graphene3 import GraphQLApp
import graphene
app = FastAPI()
class Query(graphene.ObjectType):
hello = graphene.String()
def resolve_hello(self, info):
return "Привет из FastAPI!"
schema = graphene.Schema(query=Query)
app.mount("/graphql", GraphQLApp(schema=schema))
Авторизация и аутентификация
Передача контекста
from flask import g
from functools import wraps
def login_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if not g.user:
raise Exception("Требуется авторизация")
return f(*args, **kwargs)
return decorated_function
class Query(graphene.ObjectType):
my_profile = graphene.Field(User)
@login_required
def resolve_my_profile(self, info):
user = info.context.get('user')
return User(id=user.id, name=user.name)
Права доступа на уровне полей
class User(graphene.ObjectType):
id = graphene.ID()
name = graphene.String()
email = graphene.String()
def resolve_email(self, info):
current_user = info.context.get('user')
if current_user and (current_user.id == self.id or current_user.is_admin):
return self.email
return None
Обработка ошибок и валидация
Пользовательские исключения
class ValidationError(Exception):
def __init__(self, message, field=None):
self.message = message
self.field = field
super().__init__(self.message)
class CreateUser(graphene.Mutation):
class Arguments:
name = graphene.String(required=True)
email = graphene.String(required=True)
user = graphene.Field(User)
errors = graphene.List(graphene.String)
def mutate(self, info, name, email):
errors = []
if len(name) < 2:
errors.append("Имя должно содержать минимум 2 символа")
if '@' not in email:
errors.append("Некорректный формат email")
if errors:
return CreateUser(errors=errors)
try:
# Создание пользователя
user = User(name=name, email=email)
return CreateUser(user=user)
except Exception as e:
return CreateUser(errors=[str(e)])
Глобальная обработка ошибок
class CustomMiddleware:
def resolve(self, next, root, info, **args):
try:
return next(root, info, **args)
except ValidationError as e:
return {"error": e.message, "field": e.field}
except Exception as e:
return {"error": "Внутренняя ошибка сервера"}
Тестирование GraphQL API
Тестирование с pytest
import pytest
import json
from graphene.test import Client
def test_user_query():
client = Client(schema)
query = '''
{
user(id: "1") {
id
name
}
}
'''
result = client.execute(query)
assert result['data']['user']['id'] == "1"
assert result['data']['user']['name'] == "Тестовый пользователь"
def test_create_user_mutation():
client = Client(schema)
mutation = '''
mutation {
createUser(name: "Новый пользователь", email: "test@example.com") {
ok
user {
name
email
}
}
}
'''
result = client.execute(mutation)
assert result['data']['createUser']['ok'] == True
assert result['data']['createUser']['user']['name'] == "Новый пользователь"
Тестирование с Flask
import pytest
from flask import Flask
from flask_graphql import GraphQLView
@pytest.fixture
def app():
app = Flask(__name__)
app.add_url_rule('/graphql', view_func=GraphQLView.as_view('graphql', schema=schema))
return app
def test_graphql_endpoint(app):
client = app.test_client()
query = {
"query": "{ user(id: \"1\") { name } }"
}
response = client.post('/graphql',
data=json.dumps(query),
content_type='application/json')
assert response.status_code == 200
data = json.loads(response.data)
assert 'data' in data
Производительность и оптимизация
Пагинация
class Query(graphene.ObjectType):
users = graphene.List(
User,
first=graphene.Int(default_value=10),
skip=graphene.Int(default_value=0)
)
def resolve_users(self, info, first, skip):
return User.objects.all()[skip:skip+first]
Relay-совместимая пагинация
from graphene import relay
class User(graphene.ObjectType):
class Meta:
interfaces = (relay.Node,)
id = graphene.ID()
name = graphene.String()
class Query(graphene.ObjectType):
users = relay.ConnectionField(User)
def resolve_users(self, info, **args):
return User.objects.all()
Кэширование
from functools import lru_cache
class Query(graphene.ObjectType):
users = graphene.List(User)
@lru_cache(maxsize=128)
def resolve_users(self, info):
return User.objects.all()
Инструменты и IDE для работы с GraphQL
GraphiQL
Встроенный интерфейс для тестирования запросов:
# Flask
GraphQLView.as_view('graphql', schema=schema, graphiql=True)
# Django
GraphQLView.as_view(graphiql=True)
Дополнительные инструменты
- GraphQL Playground - современная альтернатива GraphiQL
- Postman - поддержка GraphQL запросов
- Insomnia - специализированный REST/GraphQL клиент
- Apollo Client DevTools - расширение для браузера
- VSCode GraphQL - расширения для автодополнения и подсветки синтаксиса
Примеры практического применения
Единый API для разных клиентов
class Query(graphene.ObjectType):
# Полная информация для веб-клиента
user_detailed = graphene.Field(User, id=graphene.ID())
# Краткая информация для мобильного приложения
user_mobile = graphene.Field(User, id=graphene.ID())
def resolve_user_detailed(self, info, id):
return User.objects.select_related('profile', 'settings').get(id=id)
def resolve_user_mobile(self, info, id):
return User.objects.only('id', 'name', 'avatar').get(id=id)
Агрегация данных из разных источников
class Query(graphene.ObjectType):
dashboard = graphene.Field(DashboardType, user_id=graphene.ID())
def resolve_dashboard(self, info, user_id):
# Данные из базы данных
user = User.objects.get(id=user_id)
# Данные из внешнего API
external_data = fetch_external_data(user_id)
# Данные из кэша
cached_stats = get_cached_stats(user_id)
return DashboardType(
user=user,
external_info=external_data,
statistics=cached_stats
)
Полное описание библиотеки Graphene
Основные модули и компоненты
| Модуль | Описание |
|---|---|
graphene.ObjectType |
Базовый класс для создания GraphQL объектов |
graphene.Schema |
Главная схема, объединяющая запросы, мутации и подписки |
graphene.Field |
Определение поля в объекте |
graphene.List |
Список объектов определенного типа |
graphene.NonNull |
Поле, которое не может быть null |
graphene.Mutation |
Базовый класс для мутаций |
graphene.InputObjectType |
Тип для входных данных |
graphene.Interface |
Интерфейс для общих полей |
graphene.Union |
Объединение нескольких типов |
graphene.Enum |
Перечисление возможных значений |
Скалярные типы
| Тип | Описание | Python тип |
|---|---|---|
graphene.String |
Строка | str |
graphene.Int |
Целое число | int |
graphene.Float |
Число с плавающей точкой | float |
graphene.Boolean |
Логический тип | bool |
graphene.ID |
Уникальный идентификатор | str |
graphene.Date |
Дата | datetime.date |
graphene.DateTime |
Дата и время | datetime.datetime |
graphene.Time |
Время | datetime.time |
graphene.Decimal |
Десятичное число | decimal.Decimal |
graphene.JSONString |
JSON строка | dict |
Декораторы и утилиты
| Декоратор/Утилита | Описание |
|---|---|
@staticmethod |
Статический резолвер |
@classmethod |
Классовый резолвер |
graphene.resolve_only_args |
Упрощенный декоратор для резолверов |
graphene.Context |
Контекст выполнения запроса |
graphene.ResolveInfo |
Информация о текущем запросе |
Методы Schema
| Метод | Описание |
|---|---|
schema.execute(query, context=None, variables=None) |
Выполнение GraphQL запроса |
schema.execute_async(query, context=None, variables=None) |
Асинхронное выполнение запроса |
schema.get_type(name) |
Получение типа по имени |
schema.get_graphql_type(name) |
Получение GraphQL типа |
Интеграционные пакеты
| Пакет | Описание |
|---|---|
graphene-django |
Интеграция с Django |
graphene-sqlalchemy |
Интеграция с SQLAlchemy |
graphene-mongo |
Интеграция с MongoDB |
flask-graphql |
Интеграция с Flask |
starlette-graphene3 |
Интеграция с FastAPI/Starlette |
Часто задаваемые вопросы
Что такое Graphene и чем он отличается от других GraphQL библиотек?
Graphene — это наиболее зрелая и функциональная библиотека для создания GraphQL API на Python. Она отличается декларативным подходом к определению схем, обширной экосистемой интеграций и активным сообществом разработчиков.
Как Graphene решает проблему N+1 запросов?
Graphene поддерживает DataLoader паттерн для батчинга запросов, а также интеграцию с ORM для оптимизации запросов к базе данных через select_related() и prefetch_related() в Django или eager loading в SQLAlchemy.
Можно ли использовать Graphene с асинхронными фреймворками?
Да, Graphene поддерживает асинхронные резолверы и может работать с FastAPI, Starlette и другими асинхронными фреймворками через соответствующие интеграции.
Как обеспечить безопасность GraphQL API?
Рекомендуется использовать ограничения глубины запросов, таймауты, валидацию входных данных, аутентификацию на уровне резолверов и анализ сложности запросов для предотвращения DoS-атак.
Поддерживает ли Graphene GraphQL подписки?
Да, Graphene поддерживает подписки через graphene.ObjectType с использованием WebSocket или Server-Sent Events для real-time обновлений.
Как тестировать GraphQL API, созданный с помощью Graphene?
Graphene предоставляет встроенный тестовый клиент graphene.test.Client, который позволяет выполнять запросы без HTTP-сервера. Также можно использовать стандартные тестовые инструменты фреймворков.
Можно ли использовать Graphene для создания микросервисной архитектуры?
Да, Graphene подходит для микросервисов. Можно создать GraphQL Gateway, который агрегирует данные из разных сервисов, или использовать GraphQL Federation для объединения схем.
Как мигрировать с REST API на GraphQL с использованием Graphene?
Миграция может быть постепенной: сначала создать GraphQL эндпоинты параллельно с REST, затем постепенно переводить клиентов на GraphQL. Graphene позволяет легко обернуть существующую бизнес-логику в GraphQL резолверы.
Полный справочник методов и функций Graphene
| Категория | Компонент/Метод | Описание | Пример использования |
|---|---|---|---|
| Основные классы | graphene.ObjectType |
Базовый класс для GraphQL объектов | class User(graphene.ObjectType): pass |
graphene.Schema |
Главная схема API | schema = graphene.Schema(query=Query) |
|
graphene.Field |
Определение поля | name = graphene.Field(graphene.String) |
|
graphene.List |
Список объектов | users = graphene.List(User) |
|
graphene.NonNull |
Обязательное поле | id = graphene.NonNull(graphene.ID) |
|
| Скалярные типы | graphene.String |
Строковый тип | name = graphene.String() |
graphene.Int |
Целочисленный тип | age = graphene.Int() |
|
graphene.Float |
Число с плавающей точкой | price = graphene.Float() |
|
graphene.Boolean |
Логический тип | is_active = graphene.Boolean() |
|
graphene.ID |
Уникальный идентификатор | id = graphene.ID() |
|
graphene.Date |
Дата | birth_date = graphene.Date() |
|
graphene.DateTime |
Дата и время | created_at = graphene.DateTime() |
|
graphene.JSONString |
JSON строка | metadata = graphene.JSONString() |
|
| Мутации | graphene.Mutation |
Базовый класс мутаций | class CreateUser(graphene.Mutation): pass |
Arguments |
Аргументы мутации | class Arguments: name = graphene.String() |
|
mutate() |
Метод выполнения мутации | def mutate(self, info, **args): pass |
|
| Входные типы | graphene.InputObjectType |
Тип для входных данных | class UserInput(graphene.InputObjectType): pass |
graphene.Argument |
Аргумент поля | user = graphene.Field(User, id=graphene.Argument(graphene.ID)) |
|
| Интерфейсы и объединения | graphene.Interface |
Интерфейс | class Node(graphene.Interface): pass |
graphene.Union |
Объединение типов | class SearchResult(graphene.Union): pass |
|
graphene.Enum |
Перечисление | class Status(graphene.Enum): ACTIVE = 1 |
|
| Резолверы | resolve_<field>() |
Резолвер поля | def resolve_name(self, info): return self.name |
info.context |
Контекст запроса | user = info.context.get('user') |
|
info.field_name |
Имя текущего поля | field = info.field_name |
|
info.parent_type |
Родительский тип | parent = info.parent_type |
|
| Выполнение запросов | schema.execute() |
Синхронное выполнение | result = schema.execute(query) |
schema.execute_async() |
Асинхронное выполнение | result = await schema.execute_async(query) |
|
| Relay | graphene.relay.Node |
Relay Node интерфейс | class User(graphene.ObjectType): class Meta: interfaces = (graphene.relay.Node,) |
graphene.relay.Connection |
Relay Connection | users = graphene.relay.ConnectionField(User) |
|
graphene.relay.ClientIDMutation |
Relay мутация | class CreateUser(graphene.relay.ClientIDMutation): pass |
|
| Подписки | graphene.ObjectType |
Подписки | class Subscription(graphene.ObjectType): pass |
| Middleware | Middleware classes | Промежуточное ПО | class AuthMiddleware: def resolve(self, next, root, info, **args): pass |
| Утилиты | graphene.is_type() |
Проверка типа | if graphene.is_type(obj, User): pass |
graphene.get_type() |
Получение типа | user_type = graphene.get_type(User) |
|
| Валидация | Custom validators | Пользовательская валидация | def validate_email(email): return '@' in email |
| Кэширование | Field-level caching | Кэширование на уровне полей | @lru_cache(maxsize=128) |
| Батчинг | DataLoader pattern | Батчинг запросов | user_loader = DataLoader(batch_load_users) |
| Интроспекция | Schema introspection | Интроспекция схемы | schema.get_type_map() |
| Отладка | Debug middleware | Отладочное ПО | DjangoDebugMiddleware |
Graphene предоставляет мощный и гибкий инструментарий для создания современных GraphQL API на Python, поддерживая все основные возможности GraphQL спецификации и предоставляя удобные интеграции с популярными веб-фреймворками и ORM-системами.
Настоящее и будущее развития ИИ: классической математики уже недостаточно
Эксперты предупредили о рисках фейковой благотворительности с помощью ИИ
В России разработали универсального ИИ-агента для роботов и индустриальных процессов