Spaces:
Sleeping
Sleeping
Добавлен токенизатор для корректной обрезки запроса.
Browse files- Dockerfile +20 -8
- llm/common.py +2 -0
- llm/deepinfra_api.py +33 -2
- llm/vllm_api.py +1 -1
- main.py +61 -3
- prompts/gettable.py +46 -43
Dockerfile
CHANGED
@@ -3,15 +3,14 @@ FROM python:3.10-slim-bullseye
|
|
3 |
|
4 |
# Set Python to use unbuffered mode
|
5 |
ENV PYTHONUNBUFFERED=1
|
6 |
-
|
7 |
ENV PATH="/var/www/.local/bin:${PATH}"
|
8 |
|
9 |
# Create a non-root user
|
10 |
RUN useradd -m -u 1000 -U -s /bin/bash myuser
|
11 |
|
12 |
-
# Install dependencies
|
13 |
-
RUN apt-get update && \
|
14 |
-
|
15 |
rm -rf /var/lib/apt/lists/*
|
16 |
|
17 |
# Set the working directory in the container
|
@@ -26,12 +25,25 @@ RUN chown -R myuser:myuser /var/www
|
|
26 |
USER myuser
|
27 |
|
28 |
# Copy the current directory contents into the container at /var/www
|
29 |
-
COPY . /var/www
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
30 |
|
31 |
-
|
|
|
|
|
32 |
|
33 |
# Expose the port
|
34 |
-
EXPOSE
|
35 |
|
36 |
# Run FastAPI app with Uvicorn
|
37 |
-
CMD uvicorn main:app --host 0.0.0.0 --port
|
|
|
3 |
|
4 |
# Set Python to use unbuffered mode
|
5 |
ENV PYTHONUNBUFFERED=1
|
|
|
6 |
ENV PATH="/var/www/.local/bin:${PATH}"
|
7 |
|
8 |
# Create a non-root user
|
9 |
RUN useradd -m -u 1000 -U -s /bin/bash myuser
|
10 |
|
11 |
+
# Install system dependencies
|
12 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
13 |
+
python3-pip python3-dev git && \
|
14 |
rm -rf /var/lib/apt/lists/*
|
15 |
|
16 |
# Set the working directory in the container
|
|
|
25 |
USER myuser
|
26 |
|
27 |
# Copy the current directory contents into the container at /var/www
|
28 |
+
COPY --chown=myuser:myuser . /var/www
|
29 |
+
|
30 |
+
# Install dependencies
|
31 |
+
RUN pip install --no-cache-dir -r requirements.txt && \
|
32 |
+
pip install --no-cache-dir transformers sentencepiece
|
33 |
+
|
34 |
+
# Define tokenizer name
|
35 |
+
ARG TOKENIZER_NAME=unsloth/Llama-3.3-70B-Instruct
|
36 |
+
ENV TOKENIZER_NAME=${TOKENIZER_NAME}
|
37 |
+
|
38 |
+
ARG APP_PORT=7860
|
39 |
+
ENV APP_PORT=${APP_PORT}
|
40 |
|
41 |
+
# Download the tokenizer and store it in the image
|
42 |
+
RUN python -c "from transformers import AutoTokenizer; \
|
43 |
+
AutoTokenizer.from_pretrained('${TOKENIZER_NAME}')"
|
44 |
|
45 |
# Expose the port
|
46 |
+
EXPOSE ${APP_PORT}
|
47 |
|
48 |
# Run FastAPI app with Uvicorn
|
49 |
+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", APP_PORT]
|
llm/common.py
CHANGED
@@ -31,11 +31,13 @@ class LlmParams(BaseModel):
|
|
31 |
"""
|
32 |
url: str
|
33 |
model: Optional[str] = Field(None, description="Предполагается, что для локального API этот параметр не будет указываться, т.к. будем брать первую модель из списка потому, что модель доступна всего одна. Для deepinfra такой подход не подойдет и модель нужно задавать явно.")
|
|
|
34 |
type: Optional[str] = None
|
35 |
default: Optional[bool] = None
|
36 |
template: Optional[str] = None
|
37 |
predict_params: Optional[LlmPredictParams] = None
|
38 |
api_key: Optional[str] = None
|
|
|
39 |
|
40 |
class LlmApiProtocol(Protocol):
|
41 |
async def tokenize(self, prompt: str) -> Optional[dict]:
|
|
|
31 |
"""
|
32 |
url: str
|
33 |
model: Optional[str] = Field(None, description="Предполагается, что для локального API этот параметр не будет указываться, т.к. будем брать первую модель из списка потому, что модель доступна всего одна. Для deepinfra такой подход не подойдет и модель нужно задавать явно.")
|
34 |
+
tokenizer: Optional[str] = Field(None, description="При использовании стороннего API, не поддерживающего токенизацию, будет использован AutoTokenizer для модели из этого поля. Используется в случае, если название модели в API не совпадает с оригинальным названием на Huggingface.")
|
35 |
type: Optional[str] = None
|
36 |
default: Optional[bool] = None
|
37 |
template: Optional[str] = None
|
38 |
predict_params: Optional[LlmPredictParams] = None
|
39 |
api_key: Optional[str] = None
|
40 |
+
context_length: Optional[int] = None
|
41 |
|
42 |
class LlmApiProtocol(Protocol):
|
43 |
async def tokenize(self, prompt: str) -> Optional[dict]:
|
llm/deepinfra_api.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1 |
import json
|
2 |
from typing import Optional, List
|
3 |
import httpx
|
|
|
4 |
from llm.common import LlmParams, LlmApi
|
5 |
|
6 |
class DeepInfraApi(LlmApi):
|
@@ -11,6 +12,9 @@ class DeepInfraApi(LlmApi):
|
|
11 |
def __init__(self, params: LlmParams):
|
12 |
super().__init__()
|
13 |
super().set_params(params)
|
|
|
|
|
|
|
14 |
|
15 |
async def get_models(self) -> List[str]:
|
16 |
"""
|
@@ -70,10 +74,37 @@ class DeepInfraApi(LlmApi):
|
|
70 |
return actual_prompt
|
71 |
|
72 |
async def tokenize(self, prompt: str) -> Optional[dict]:
|
73 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
74 |
|
75 |
async def detokenize(self, tokens: List[int]) -> Optional[str]:
|
76 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
77 |
|
78 |
async def create_request(self, prompt: str, system_prompt: str = None) -> dict:
|
79 |
"""
|
|
|
1 |
import json
|
2 |
from typing import Optional, List
|
3 |
import httpx
|
4 |
+
from transformers import AutoTokenizer
|
5 |
from llm.common import LlmParams, LlmApi
|
6 |
|
7 |
class DeepInfraApi(LlmApi):
|
|
|
12 |
def __init__(self, params: LlmParams):
|
13 |
super().__init__()
|
14 |
super().set_params(params)
|
15 |
+
print('Tokenizer initialization.')
|
16 |
+
self.tokenizer = AutoTokenizer.from_pretrained(params.tokenizer if params.tokenizer is not None else params.model)
|
17 |
+
print(f"Tokenizer initialized for model {params.model}.")
|
18 |
|
19 |
async def get_models(self) -> List[str]:
|
20 |
"""
|
|
|
74 |
return actual_prompt
|
75 |
|
76 |
async def tokenize(self, prompt: str) -> Optional[dict]:
|
77 |
+
"""
|
78 |
+
Токенизирует входной текстовый промпт.
|
79 |
+
|
80 |
+
Args:
|
81 |
+
prompt (str): Текст, который нужно токенизировать.
|
82 |
+
Returns:
|
83 |
+
dict: Словарь с токенами и их количеством или None в случае ошибки.
|
84 |
+
"""
|
85 |
+
try:
|
86 |
+
tokens = self.tokenizer.encode(prompt, add_special_tokens=True)
|
87 |
+
|
88 |
+
return {"result": tokens, "num_tokens": len(tokens), "max_length": self.params.context_length}
|
89 |
+
except Exception as e:
|
90 |
+
print(f"Tokenization error: {e}")
|
91 |
+
return None
|
92 |
|
93 |
async def detokenize(self, tokens: List[int]) -> Optional[str]:
|
94 |
+
"""
|
95 |
+
Детокенизирует список токенов обратно в строку.
|
96 |
+
|
97 |
+
Args:
|
98 |
+
tokens (List[int]): Список токенов, который нужно преобразовать в текст.
|
99 |
+
Returns:
|
100 |
+
str: Восстановленный текст или None в случае ошибки.
|
101 |
+
"""
|
102 |
+
try:
|
103 |
+
text = self.tokenizer.decode(tokens, skip_special_tokens=True)
|
104 |
+
return text
|
105 |
+
except Exception as e:
|
106 |
+
print(f"Detokenization error: {e}")
|
107 |
+
return None
|
108 |
|
109 |
async def create_request(self, prompt: str, system_prompt: str = None) -> dict:
|
110 |
"""
|
llm/vllm_api.py
CHANGED
@@ -109,7 +109,7 @@ class LlmApi(LlmApi):
|
|
109 |
if response.status_code == 200:
|
110 |
data = response.json()
|
111 |
if "tokens" in data:
|
112 |
-
return {"tokens": data["tokens"], "
|
113 |
elif response.status_code == 404:
|
114 |
print("Tokenization endpoint not found (404).")
|
115 |
else:
|
|
|
109 |
if response.status_code == 200:
|
110 |
data = response.json()
|
111 |
if "tokens" in data:
|
112 |
+
return {"tokens": data["tokens"], "max_length": data.get("max_model_len")}
|
113 |
elif response.status_code == 404:
|
114 |
print("Tokenization endpoint not found (404).")
|
115 |
else:
|
main.py
CHANGED
@@ -17,14 +17,17 @@ load_dotenv()
|
|
17 |
LLM_API_URL = os.getenv("LLM_API_URL", "https://api.deepinfra.com")
|
18 |
LLM_API_KEY = os.getenv("DEEPINFRA_API_KEY", "")
|
19 |
LLM_NAME = os.getenv("LLM_NAME", "meta-llama/Llama-3.3-70B-Instruct-Turbo")
|
|
|
20 |
|
21 |
default_llm_params = LlmParams(
|
22 |
url=LLM_API_URL,
|
23 |
api_key=LLM_API_KEY,
|
24 |
model=LLM_NAME,
|
|
|
|
|
25 |
predict_params=LlmPredictParams(
|
26 |
temperature=0.15, top_p=0.95, min_p=0.05, seed=42,
|
27 |
-
repetition_penalty=1.2, presence_penalty=1.1,
|
28 |
)
|
29 |
)
|
30 |
llm_api = DeepInfraApi(default_llm_params)
|
@@ -42,10 +45,58 @@ class TextRequest(BaseModel):
|
|
42 |
text: str
|
43 |
projects: list[str] = []
|
44 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
45 |
@app.post("/extracttable")
|
46 |
async def extracttable_route(request: TextRequest):
|
47 |
prompt = gettable.USER_PROMPT.format(query=request.text)
|
48 |
-
|
|
|
|
|
49 |
|
50 |
result = {"response": None, "error": None, "raw": response}
|
51 |
|
@@ -63,6 +114,12 @@ async def extracttable_route(request: TextRequest):
|
|
63 |
try:
|
64 |
result["response"] = json.loads(json_str)
|
65 |
result["raw"] = prefix.strip()
|
|
|
|
|
|
|
|
|
|
|
|
|
66 |
except json.JSONDecodeError as e:
|
67 |
result["error"] = f"Ошибка декодирования JSON: {e}"
|
68 |
|
@@ -73,7 +130,8 @@ def health():
|
|
73 |
return {"status": "ok"}
|
74 |
|
75 |
async def generate_response(prompt):
|
76 |
-
|
|
|
77 |
|
78 |
@app.post("/getsummary")
|
79 |
async def getsummary_route(request: TextRequest):
|
|
|
17 |
LLM_API_URL = os.getenv("LLM_API_URL", "https://api.deepinfra.com")
|
18 |
LLM_API_KEY = os.getenv("DEEPINFRA_API_KEY", "")
|
19 |
LLM_NAME = os.getenv("LLM_NAME", "meta-llama/Llama-3.3-70B-Instruct-Turbo")
|
20 |
+
TOKENIZER_NAME = os.getenv("TOKENIZER_NAME", "unsloth/Llama-3.3-70B-Instruct")
|
21 |
|
22 |
default_llm_params = LlmParams(
|
23 |
url=LLM_API_URL,
|
24 |
api_key=LLM_API_KEY,
|
25 |
model=LLM_NAME,
|
26 |
+
tokenizer=TOKENIZER_NAME,
|
27 |
+
context_length=130000,
|
28 |
predict_params=LlmPredictParams(
|
29 |
temperature=0.15, top_p=0.95, min_p=0.05, seed=42,
|
30 |
+
repetition_penalty=1.2, presence_penalty=1.1, n_predict=6000
|
31 |
)
|
32 |
)
|
33 |
llm_api = DeepInfraApi(default_llm_params)
|
|
|
45 |
text: str
|
46 |
projects: list[str] = []
|
47 |
|
48 |
+
async def trim_prompt(prompt: str, system_prompt: str):
|
49 |
+
|
50 |
+
result = await llm_api.tokenize(prompt+system_prompt)
|
51 |
+
result_system = await llm_api.tokenize(system_prompt)
|
52 |
+
|
53 |
+
# в случае ошибки при токенизации, вернем исходную строку безопасной длины
|
54 |
+
if result["result"] is None or result_system is None:
|
55 |
+
return prompt[llm_api.params.context_length / 3]
|
56 |
+
|
57 |
+
#вероятно, часть уходит на форматирование чата, надо проверить
|
58 |
+
max_length = result["max_length"] - len(result["result"]) - len(result_system["result"]) - llm_api.params.predict_params.n_predict
|
59 |
+
|
60 |
+
detokenized_str = await llm_api.detokenize(result["result"][:max_length])
|
61 |
+
|
62 |
+
# в случае ошибки при детокенизации, вернем исходную строку безопасной длины
|
63 |
+
if detokenized_str is None:
|
64 |
+
return prompt[llm_api.params.context_length / 3]
|
65 |
+
|
66 |
+
return detokenized_str
|
67 |
+
|
68 |
+
|
69 |
+
def validate_json_format(data):
|
70 |
+
""" Проверяет, соответствует ли JSON ожидаемому формату. """
|
71 |
+
if not isinstance(data, list):
|
72 |
+
return "JSON должен быть списком объектов."
|
73 |
+
|
74 |
+
# Возможно, в дальнейшем стоит описать менее детально, пока так для отладки
|
75 |
+
for item in data:
|
76 |
+
if not isinstance(item, dict):
|
77 |
+
return "Элементы списка должны быть объектами (dict)."
|
78 |
+
if "name" not in item or "data" not in item:
|
79 |
+
return "Каждый объект должен содержать ключи 'name' и 'data'."
|
80 |
+
if not isinstance(item["name"], str):
|
81 |
+
return "'name' должен быть строкой."
|
82 |
+
if not isinstance(item["data"], dict):
|
83 |
+
return "'data' должен быть объектом (dict)."
|
84 |
+
if "columns" not in item["data"] or "rows" not in item["data"]:
|
85 |
+
return "'data' должен содержать 'columns' и 'rows'."
|
86 |
+
if not isinstance(item["data"]["columns"], list) or not all(isinstance(col, str) for col in item["data"]["columns"]):
|
87 |
+
return "'columns' должен быть списком строк."
|
88 |
+
if not isinstance(item["data"]["rows"], list) or not all(isinstance(row, list) for row in item["data"]["rows"]):
|
89 |
+
return "'rows' должен быть списком списков."
|
90 |
+
|
91 |
+
return None # Ошибок нет
|
92 |
+
|
93 |
+
|
94 |
@app.post("/extracttable")
|
95 |
async def extracttable_route(request: TextRequest):
|
96 |
prompt = gettable.USER_PROMPT.format(query=request.text)
|
97 |
+
system_prompt=gettable.SYSTEM_PROMPT
|
98 |
+
prompt = await trim_prompt(prompt, system_prompt)
|
99 |
+
response = await llm_api.predict(prompt, system_prompt=system_prompt)
|
100 |
|
101 |
result = {"response": None, "error": None, "raw": response}
|
102 |
|
|
|
114 |
try:
|
115 |
result["response"] = json.loads(json_str)
|
116 |
result["raw"] = prefix.strip()
|
117 |
+
validation_error = validate_json_format(result["response"])
|
118 |
+
if validation_error:
|
119 |
+
result["error"] = validation_error
|
120 |
+
else:
|
121 |
+
result["response"] = result["response"]
|
122 |
+
result["raw"] = prefix.strip()
|
123 |
except json.JSONDecodeError as e:
|
124 |
result["error"] = f"Ошибка декодирования JSON: {e}"
|
125 |
|
|
|
130 |
return {"status": "ok"}
|
131 |
|
132 |
async def generate_response(prompt):
|
133 |
+
prompt = await trim_prompt(prompt)
|
134 |
+
return await llm_api.predict(prompt)
|
135 |
|
136 |
@app.post("/getsummary")
|
137 |
async def getsummary_route(request: TextRequest):
|
prompts/gettable.py
CHANGED
@@ -33,7 +33,8 @@ SYSTEM_PROMPT="""
|
|
33 |
(2) 'пункт 2'
|
34 |
(3) 'пункт 3'
|
35 |
(4) 'пункт 4'
|
36 |
-
(5)
|
|
|
37 |
####
|
38 |
Далее будет пример номер 1. Не используй данные из примера, он указывает только на логику твоей работы
|
39 |
####
|
@@ -47,25 +48,19 @@ SYSTEM_PROMPT="""
|
|
47 |
(4) Заборный 6-723 - это судя по всему объект, что имеет статус 'реализация'. ДСГ 4 - это объект, что имеет длину 8 и ширину 2, которая измеряется в метрах. Штраб - что-то, что имеет напряжение 150. ЗТТ - аббревиатура, которая имеет в качестве параметров давление 1 - 150 и давление 2 - 206.4 (это с плавающей запятой значение). ЛКТР - это что-то, что находится в стадии 'в завершении'.
|
48 |
(5) Нужно не забыть каждое числовое значение написать в числовом формате. И не забыть о показателях с точкой.
|
49 |
(6) JSON
|
50 |
-
{
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
"
|
56 |
-
"
|
57 |
-
|
58 |
-
"
|
59 |
-
"
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
"Давление 2":"206.4"
|
64 |
-
},
|
65 |
-
"ЛКТР":{
|
66 |
-
"Статус":"в завершении"
|
67 |
-
}
|
68 |
-
}
|
69 |
####
|
70 |
Далее будет пример номер 2. Не используй данные из примера, он указывает только на логику твоей работы
|
71 |
####
|
@@ -79,21 +74,15 @@ SYSTEM_PROMPT="""
|
|
79 |
(4) В данной записи есть только объект "труба", у которого есть два экземпляра в массиве. Параметрами являются номер, длина, сечение и давление воды. Не ясно единиц измерения данных параметров. Обе трубы имеют номер по ГОСТ 3. Первая труба имеет длину 7, сечение 8 и давление 70.69 (это число с плавающей точкой). Вторая труба имеет длину 6, сечение как у первой 8, давление 106.
|
80 |
(5) Нужно не забыть каждое числовое значение написать в числовом формате. И не забыть о показателях с точкой.
|
81 |
(6) JSON
|
82 |
-
{
|
83 |
-
"Труба"
|
84 |
-
|
85 |
-
"Длина"
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
"Длина":"6",
|
92 |
-
"Сечение":"8",
|
93 |
-
"Давление":"106"
|
94 |
-
}
|
95 |
-
]
|
96 |
-
}
|
97 |
####
|
98 |
Далее будет пример номер 3. Не используй данные из примера, он указывает только на логику твоей работы
|
99 |
####
|
@@ -107,17 +96,31 @@ SYSTEM_PROMPT="""
|
|
107 |
(4) В данной записи есть параметр города цвет - белый. А также показатели температуры погоды, не понятна система отсчёта, но погода считается в градусах. Записано что погода -5. Количество детей 8 штук. И статус детей - они "играют в снежки".
|
108 |
(5) Нужно не забыть каждое числовое значение написать в числовом формате.
|
109 |
(6) JSON
|
110 |
-
{
|
111 |
-
"Город"
|
112 |
-
|
|
|
|
|
|
|
|
|
113 |
},
|
114 |
-
|
115 |
-
|
|
|
|
|
|
|
|
|
|
|
116 |
},
|
117 |
-
|
118 |
-
|
119 |
-
|
|
|
|
|
|
|
|
|
120 |
}
|
|
|
121 |
####
|
122 |
Далее будет настоящая запись, которую требуется разобрать.
|
123 |
####
|
|
|
33 |
(2) 'пункт 2'
|
34 |
(3) 'пункт 3'
|
35 |
(4) 'пункт 4'
|
36 |
+
(5) 'пункт 5'
|
37 |
+
(6) JSON 'пункт 6'"
|
38 |
####
|
39 |
Далее будет пример номер 1. Не используй данные из примера, он указывает только на логику твоей работы
|
40 |
####
|
|
|
48 |
(4) Заборный 6-723 - это судя по всему объект, что имеет статус 'реализация'. ДСГ 4 - это объект, что имеет длину 8 и ширину 2, которая измеряется в метрах. Штраб - что-то, что имеет напряжение 150. ЗТТ - аббревиатура, которая имеет в качестве параметров давление 1 - 150 и давление 2 - 206.4 (это с плавающей запятой значение). ЛКТР - это что-то, что находится в стадии 'в завершении'.
|
49 |
(5) Нужно не забыть каждое числовое значение написать в числовом формате. И не забыть о показателях с точкой.
|
50 |
(6) JSON
|
51 |
+
[{
|
52 |
+
name: "Датчики",
|
53 |
+
data: {
|
54 |
+
columns: ["Наименование", "Статус", "Длина, в метрах", "Ширина, в метрах", "Напряжение", "Давление 1", "Давление 2"],
|
55 |
+
rows: [
|
56 |
+
["Заборный 6-723", "реализация", null, null, null, null, null],
|
57 |
+
["ДСГ 4", null, 8, 2, null, null, null],
|
58 |
+
["Штраб", null, null, null, 150, null, null],
|
59 |
+
["ЗТТ", null, null, null, null, 150, 206.4],
|
60 |
+
["ЛКТР", "в завершении", null, null, null, null, null]
|
61 |
+
]
|
62 |
+
}
|
63 |
+
}]
|
|
|
|
|
|
|
|
|
|
|
|
|
64 |
####
|
65 |
Далее будет пример номер 2. Не используй данные из примера, он указывает только на логику твоей работы
|
66 |
####
|
|
|
74 |
(4) В данной записи есть только объект "труба", у которого есть два экземпляра в массиве. Параметрами являются номер, длина, сечение и давление воды. Не ясно единиц измерения данных параметров. Обе трубы имеют номер по ГОСТ 3. Первая труба имеет длину 7, сечение 8 и давление 70.69 (это число с плавающей точкой). Вторая труба имеет длину 6, сечение как у первой 8, давление 106.
|
75 |
(5) Нужно не забыть каждое числовое значение написать в числовом формате. И не забыть о показателях с точкой.
|
76 |
(6) JSON
|
77 |
+
[{
|
78 |
+
name: "Труба",
|
79 |
+
data: {
|
80 |
+
columns: ["Номер по ГОСТ", "Длина", "Сечение", "Давление"],
|
81 |
+
rows: [
|
82 |
+
[3, 7, 8, 70.69],
|
83 |
+
[3, 6, 8, 106],
|
84 |
+
]
|
85 |
+
}]
|
|
|
|
|
|
|
|
|
|
|
|
|
86 |
####
|
87 |
Далее будет пример номер 3. Не используй данные из примера, он указывает только на логику твоей работы
|
88 |
####
|
|
|
96 |
(4) В данной записи есть параметр города цвет - белый. А также показатели температуры погоды, не понятна система отсчёта, но погода считается в градусах. Записано что погода -5. Количество детей 8 штук. И статус детей - они "играют в снежки".
|
97 |
(5) Нужно не забыть каждое числовое значение написать в числовом формате.
|
98 |
(6) JSON
|
99 |
+
[{
|
100 |
+
name: "Город",
|
101 |
+
data: {
|
102 |
+
columns: ["Цвет"],
|
103 |
+
rows: [
|
104 |
+
["белый"]
|
105 |
+
]
|
106 |
},
|
107 |
+
{
|
108 |
+
name: "Погода",
|
109 |
+
data: {
|
110 |
+
columns: ["Температура, градус"],
|
111 |
+
rows: [
|
112 |
+
[-5]
|
113 |
+
]
|
114 |
},
|
115 |
+
{
|
116 |
+
name: "Дети",
|
117 |
+
data: {
|
118 |
+
columns: ["К��личество", "Статус"],
|
119 |
+
rows: [
|
120 |
+
[8, "Играют в снежки"]
|
121 |
+
]
|
122 |
}
|
123 |
+
]
|
124 |
####
|
125 |
Далее будет настоящая запись, которую требуется разобрать.
|
126 |
####
|