Skip to content

Commit 16fd067

Browse files
committed
Enrich truncated OpenRouter descriptions
1 parent cbbcc32 commit 16fd067

3 files changed

Lines changed: 148 additions & 2 deletions

File tree

internal/adminapi/handlers.go

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import (
1414
"telegram-agent/internal/llm"
1515
)
1616

17+
const maxVisibleDescriptionEnrich = 8
18+
1719
// --- View data ---
1820

1921
type uiRole struct {
@@ -474,6 +476,7 @@ func (s *Server) buildIndexData(r *http.Request) indexData {
474476
attachMarketSignals(models, marketSignals)
475477
attachModelTelemetry(models, s.loadModelTelemetry(ctx5, catalogProv))
476478
attachModelChecks(models, catalogProv, s.loadModelChecks(ctx5))
479+
s.enrichVisibleDescriptions(ctx5, catalogProv, models)
477480
decorateModelDisplay(models, preset)
478481
sections := buildModelSections(models, true)
479482
filters := uiFilters{
@@ -591,7 +594,6 @@ func (s *Server) buildIndexData(r *http.Request) indexData {
591594
attachMarketSignals(models, marketSignals)
592595
attachModelTelemetry(models, s.loadModelTelemetry(ctx5, catalogProv))
593596
attachModelChecks(models, catalogProv, s.loadModelChecks(ctx5))
594-
decorateModelDisplay(models, "")
595597
asc := sortDir == "asc"
596598
sort.Slice(models, func(i, j int) bool {
597599
var less bool
@@ -633,6 +635,8 @@ func (s *Server) buildIndexData(r *http.Request) indexData {
633635
}
634636
return !less
635637
})
638+
s.enrichVisibleDescriptions(ctx5, catalogProv, models)
639+
decorateModelDisplay(models, "")
636640

637641
return indexData{
638642
Routing: s.buildRouting(),
@@ -643,6 +647,39 @@ func (s *Server) buildIndexData(r *http.Request) indexData {
643647
}
644648
}
645649

650+
func (s *Server) enrichVisibleDescriptions(ctx context.Context, provider string, models []uiModel) {
651+
if provider != "openrouter" || s.capStore == nil {
652+
return
653+
}
654+
remaining := maxVisibleDescriptionEnrich
655+
for i := range models {
656+
if remaining <= 0 {
657+
return
658+
}
659+
if !llm.OpenRouterDescriptionLooksTruncated(models[i].Description) {
660+
continue
661+
}
662+
remaining--
663+
full, err := llm.FetchOpenRouterModelDescription(ctx, models[i].ID, models[i].Description)
664+
if err != nil {
665+
s.logger.Debug("openrouter description enrichment failed", "model", models[i].ID, "err", err)
666+
continue
667+
}
668+
if len(full) <= len(models[i].Description) {
669+
continue
670+
}
671+
models[i].Description = full
672+
caps := s.lookupCapsFor(ctx, provider, models[i].ID)
673+
caps.Description = full
674+
if caps.Name == "" {
675+
caps.Name = models[i].Name
676+
}
677+
if err := s.capStore.PutCapabilities(ctx, provider, models[i].ID, caps); err != nil {
678+
s.logger.Debug("openrouter description cache update failed", "model", models[i].ID, "err", err)
679+
}
680+
}
681+
}
682+
646683
// orBlendedPrice mirrors AA's 3:1 input/output weighting so prices are
647684
// directly comparable across sources.
648685
func orBlendedPrice(promptPrice, completionPrice float64) float64 {

internal/llm/capabilities.go

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7+
"html"
78
"io"
89
"net/http"
10+
"net/url"
11+
"regexp"
912
"strconv"
1013
"strings"
1114
"time"
@@ -62,7 +65,11 @@ type ConfigurableProvider interface {
6265

6366
// --- OpenRouter /models fetcher ---
6467

65-
var openRouterHTTPClient = &http.Client{Timeout: 20 * time.Second}
68+
var (
69+
openRouterHTTPClient = &http.Client{Timeout: 20 * time.Second}
70+
openRouterPageBaseURL = "https://openrouter.ai"
71+
openRouterDescriptionPayload = regexp.MustCompile(`\\"description\\":\\"((?:\\\\.|[^\\"])*)\\"`)
72+
)
6673

6774
// FetchOpenRouterModels pulls the full catalog from OpenRouter and returns a
6875
// map keyed by model id. Prices are normalised to USD per 1M tokens (OpenRouter
@@ -133,6 +140,85 @@ func parseOpenRouterModels(body []byte) (map[string]Capabilities, error) {
133140
return out, nil
134141
}
135142

143+
// OpenRouterDescriptionLooksTruncated reports descriptions that already arrive
144+
// from OpenRouter with an ellipsis. Those are upstream-shortened strings, not a
145+
// UI clamp, so callers may optionally enrich them from the model page payload.
146+
func OpenRouterDescriptionLooksTruncated(desc string) bool {
147+
desc = strings.TrimSpace(desc)
148+
return strings.HasSuffix(desc, "...") || strings.HasSuffix(desc, "…")
149+
}
150+
151+
// FetchOpenRouterModelDescription fetches a model page and extracts the longest
152+
// matching description from its SSR payload. OpenRouter's public /models API can
153+
// return shortened descriptions ending in "...", while the model page payload
154+
// usually carries the full text.
155+
func FetchOpenRouterModelDescription(ctx context.Context, modelID, currentDescription string) (string, error) {
156+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, openRouterModelPageURL(modelID), nil)
157+
if err != nil {
158+
return "", err
159+
}
160+
resp, err := openRouterHTTPClient.Do(req)
161+
if err != nil {
162+
return "", fmt.Errorf("openrouter model page: %w", err)
163+
}
164+
defer resp.Body.Close()
165+
166+
body, err := io.ReadAll(io.LimitReader(resp.Body, 4*1024*1024))
167+
if err != nil {
168+
return "", fmt.Errorf("openrouter model page: read: %w", err)
169+
}
170+
if resp.StatusCode != http.StatusOK {
171+
return "", fmt.Errorf("openrouter model page: HTTP %d: %s", resp.StatusCode, string(body))
172+
}
173+
full := extractOpenRouterPageDescription(body, currentDescription)
174+
if full == "" {
175+
return "", fmt.Errorf("openrouter model page: full description not found")
176+
}
177+
return full, nil
178+
}
179+
180+
func openRouterModelPageURL(modelID string) string {
181+
parts := strings.Split(modelID, "/")
182+
for i := range parts {
183+
parts[i] = url.PathEscape(parts[i])
184+
}
185+
return strings.TrimRight(openRouterPageBaseURL, "/") + "/" + strings.Join(parts, "/")
186+
}
187+
188+
func extractOpenRouterPageDescription(body []byte, currentDescription string) string {
189+
current := normalizeDescriptionText(currentDescription)
190+
prefix := strings.TrimSpace(strings.TrimSuffix(strings.TrimSuffix(current, "..."), "…"))
191+
if len(prefix) > 120 {
192+
prefix = prefix[:120]
193+
}
194+
var best string
195+
for _, m := range openRouterDescriptionPayload.FindAllSubmatch(body, -1) {
196+
if len(m) != 2 {
197+
continue
198+
}
199+
raw := string(m[1])
200+
decoded, err := strconv.Unquote(`"` + raw + `"`)
201+
if err != nil {
202+
continue
203+
}
204+
decoded = normalizeDescriptionText(html.UnescapeString(decoded))
205+
if decoded == "" || len(decoded) <= len(current) || OpenRouterDescriptionLooksTruncated(decoded) {
206+
continue
207+
}
208+
if prefix != "" && !strings.HasPrefix(decoded, prefix) {
209+
continue
210+
}
211+
if len(decoded) > len(best) {
212+
best = decoded
213+
}
214+
}
215+
return best
216+
}
217+
218+
func normalizeDescriptionText(s string) string {
219+
return strings.Join(strings.Fields(strings.TrimSpace(s)), " ")
220+
}
221+
136222
func containsStr(s []string, target string) bool {
137223
for _, v := range s {
138224
if strings.EqualFold(v, target) {

internal/llm/capabilities_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,26 @@ func TestCapabilitiesFree(t *testing.T) {
128128
})
129129
}
130130
}
131+
132+
func TestExtractOpenRouterPageDescriptionUsesFullPayload(t *testing.T) {
133+
current := "NVIDIA Nemotron 3 Nano Omni is a 30B-A3B open multimodal model designed to function as a perception and context sub-agent in enterprise agent systems. It accepts text, image, video, and..."
134+
full := "NVIDIA Nemotron 3 Nano Omni is a 30B-A3B open multimodal model designed to function as a perception and context sub-agent in enterprise agent systems. It accepts text, image, video, and audio inputs and produces text output, enabling agents to perceive and reason across modalities in a single inference loop."
135+
body := []byte(`prefix {\"description\":\"Short page meta.\"} middle {\"description\":\"` + full + `\"} suffix`)
136+
137+
got := extractOpenRouterPageDescription(body, current)
138+
if got != full {
139+
t.Fatalf("description mismatch:\nwant %q\ngot %q", full, got)
140+
}
141+
}
142+
143+
func TestOpenRouterDescriptionLooksTruncated(t *testing.T) {
144+
if !OpenRouterDescriptionLooksTruncated("accepts text, image, video, and...") {
145+
t.Fatal("ASCII ellipsis should be treated as truncated")
146+
}
147+
if !OpenRouterDescriptionLooksTruncated("accepts text, image, video, and…") {
148+
t.Fatal("unicode ellipsis should be treated as truncated")
149+
}
150+
if OpenRouterDescriptionLooksTruncated("Full sentence.") {
151+
t.Fatal("complete sentence should not be treated as truncated")
152+
}
153+
}

0 commit comments

Comments
 (0)